@datamoc/mw_games 0.39.1 → 0.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,26 @@ the public API may still change between minor versions.
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.40.0] - 2026-10-15
11
+
12
+ A game that plays sampled MIDI no longer ships a whole SoundFont: the asset
13
+ compiler subsets the bank to the instruments its tracks actually use, and the
14
+ package now carries a small WTFPL General MIDI bank to subset from.
15
+
16
+ ### Added
17
+
18
+ - Build-time SoundFont subsetting: `compileResources`, `emitPage` and `mwgPage` now
19
+ embed `.mid`/`.midi` and `.sf2` assets, and every embedded SoundFont is subset to
20
+ the programs the embedded MIDI files actually use (`collectSoundFontUsage` over
21
+ the shipped tracks, drums as bank 128). `soundFont: false` embeds a font whole,
22
+ `soundFontUsage` (or `--soundfont-usage=bank:program,...`) names the programs
23
+ explicitly, and a font with no shipped MIDI embeds whole. An unreadable MIDI or
24
+ SoundFont fails the build by name instead of shipping silently. The result and
25
+ both CLIs report each font's kept programs and byte saving: the reference bank
26
+ `data/microgm-opt.sf2` (2.6 MB, 137 presets, WTFPL, shipped in the npm package
27
+ for a game to copy into its assets) embeds as 25 KB for a piano-only track.
28
+
29
+
10
30
  ## [0.39.1] - 2026-10-15
11
31
 
12
32
  Fixes the 0.39.0 publish gate on Linux: the WebP skip-shrink test asserted one
@@ -0,0 +1,20 @@
1
+ # microgm-opt SoundFont provenance
2
+
3
+ `microgm-opt.sf2` is a small General MIDI SoundFont (2,629,640 bytes, 137 presets)
4
+ supplied for this repository as the smallest usable GM bank found so far. Its INFO
5
+ chunk names the bank `microgm-opt`, the engineer `michael`, the sound engine
6
+ `EMU8000`, the comment `gm soundfont for tiny devices`, and the tool `Polyphone`.
7
+
8
+ Licence: WTFPL (Do What The F*** You Want To Public License,
9
+ https://www.wtfpl.net/), confirmed by the project owner in October 2026. The
10
+ file's own INFO chunk states no licence, so this note is the record of that
11
+ confirmation. The bank keeps its WTFPL terms; it is not relicensed under this
12
+ project's MPL-2.0.
13
+
14
+ The bank ships in the published npm package (`package.json`'s `files` list), so
15
+ a game copies `data/microgm-opt.sf2` from the installed package into its own
16
+ asset folder and lets the build do the rest: `tools/compile-resources` embeds
17
+ only the programs the game's shipped MIDI files use, so a game that plays a
18
+ handful of instruments carries tens of kilobytes of the bank instead of the
19
+ whole file (see `tests/compile-resources.test.ts`, which subsets this bank
20
+ against a three-program MIDI file).
Binary file
@@ -1,4 +1,4 @@
1
- var mw_games=(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=Object.create,n=Object.defineProperty,r=Object.getOwnPropertyDescriptor,i=Object.getOwnPropertyNames,a=Object.getPrototypeOf,o=Object.prototype.hasOwnProperty,s=(e,t,n)=>()=>{if(n)throw n[0];try{return e&&(t=e(e=0)),t}catch(e){throw n=[e],e}},c=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports),l=(e,t)=>{let r={};for(var i in e)n(r,i,{get:e[i],enumerable:!0});return t||n(r,Symbol.toStringTag,{value:`Module`}),r},u=(e,t,a,s)=>{if(t&&typeof t==`object`||typeof t==`function`)for(var c=i(t),l=0,u=c.length,d;l<u;l++)d=c[l],!o.call(e,d)&&d!==a&&n(e,d,{get:(e=>t[e]).bind(null,d),enumerable:!(s=r(t,d))||s.enumerable});return e},d=(e,r,i)=>(i=e==null?{}:t(a(e)),u(r||!e||!e.__esModule||!o.call(e,`default`)?n(i,`default`,{value:e,enumerable:!0}):i,e)),f=`0.39.1`,p=class{constructor(e=!1){this.listeners=[],this.stackMode=e}add(e){this.listeners.includes(e)||(this.stackMode?this.listeners.unshift(e):this.listeners.push(e))}remove(e){let t=this.listeners.indexOf(e);t!==-1&&this.listeners.splice(t,1)}removeAll(){this.listeners.length=0}get size(){return this.listeners.length}dispatch(e){let t=this.listeners.slice();for(let n of t)if(this.listeners.includes(n)&&n(e)===!0)return!0;return!1}},m=class{constructor(){this.onDestroy=new p,this.destroyed=!1}update(e){}resize(e,t){}onSuspend(){}onResume(e){}destroy(){this.destroyed||(this.destroyed=!0,this.onDestroy.dispatch(),this.onDestroy.removeAll(),this.teardown())}teardown(){}get isDestroyed(){return this.destroyed}},h=class{constructor(){this.scenes=[]}get current(){return this.scenes.length===0?null:this.scenes[this.scenes.length-1]}get depth(){return this.scenes.length}replace(e){for(let e of this.scenes)e.destroy();this.scenes=[e],e.create()}push(e){this.current?.onSuspend(),this.scenes.push(e),e.create()}pop(e){if(this.scenes.length<=1)throw Error(`cannot pop the last scene off the stack - switchScene somewhere instead`);this.scenes.pop()?.destroy(),this.current?.onResume(e)}update(e){this.current?.update(e)}resize(e,t){for(let n of this.scenes)n.resize(e,t)}destroy(){for(let e of this.scenes)e.destroy();this.scenes=[]}},g=class{constructor(){this.items=new Map}register(e,t){if(this.items.has(e))throw Error(`"${e}" is already registered`);this.items.set(e,t)}get(e){let t=this.items.get(e);if(t===void 0)throw Error(`no registration named "${e}"`);return t}has(e){return this.items.has(e)}list(){return[...this.items.keys()]}};function _(){let e=1,t=new Map;return{get size(){return t.size},put(n){let r=e++;return t.set(r,n),r},get(e){let n=t.get(e);if(n===void 0)throw Error(`unknown handle ${e}`);return n},drop(e){t.delete(e)},clear(){t.clear()},with(n,r){let i=e++;t.set(i,n);try{return r(i)}finally{t.delete(i)}}}}var v=class{constructor(){this.order=[],this.registry=new g}add(e,t){this.registry.register(e.name,e),this.order.push(e),e.create?.(t)}has(e){return this.registry.has(e)}get(e){return this.registry.get(e)}update(e,t){for(let n of this.order)n.update?.(e,t)}resize(e,t,n){for(let r of this.order)r.resize?.(e,t,n)}onSuspend(e){for(let t of this.order)t.onSuspend?.(e)}onResume(e,t){for(let n of this.order)n.onResume?.(e,t)}destroy(e){for(let t=this.order.length-1;t>=0;t--)this.order[t].destroy?.(e);this.order=[],this.registry=new g}},y={debug:0,info:1,warn:2,error:3},b=class{constructor(e,t={}){if(!e)throw Error(`a logger needs a category`);this.category=e,this.level=t.level??`debug`,this.sink=t.sink??x}setLevel(e){this.level=e}debug(e,t){this.write(`debug`,e,t)}info(e,t){this.write(`info`,e,t)}warn(e,t){this.write(`warn`,e,t)}error(e,t){this.write(`error`,e,t)}write(e,t,n){y[e]<y[this.level]||this.sink({level:e,category:this.category,message:t,data:n,time:Date.now()})}};function x(e){let t=`[${e.category}] ${e.message}`;e.level===`debug`?console.debug(t,e.data??``):e.level===`info`?console.info(t,e.data??``):e.level===`warn`?console.warn(t,e.data??``):console.error(t,e.data??``)}var S=class{constructor(){this.hooks=[]}on(e,t,n){this.hooks.push({event:e,handler:t,source:n})}off(e){this.hooks=this.hooks.filter(t=>t.handler!==e)}offSource(e){this.hooks=this.hooks.filter(t=>t.source!==e)}emit(e,...t){for(let n of[...this.hooks])n.event===e&&n.handler(...t)}get size(){return this.hooks.length}clear(){this.hooks=[]}},C=l({Generator:()=>j,MersenneTwister:()=>he,RandomStreams:()=>ge,active:()=>ee,chance:()=>A,element:()=>re,float:()=>D,int:()=>O,normalRange:()=>k,pop:()=>T,push:()=>w,range:()=>ne,reset:()=>te,shuffle:()=>oe,weighted:()=>ie,weightedKey:()=>ae,withSeed:()=>E});function ee(){return le()}function w(e){let t=new j(e);return ce.push(t),t}function T(){if(ce.length===1)throw Error(`the base random generator cannot be popped`);ce.pop()}function E(e,t){w(e);try{return t()}finally{T()}}function te(){ce.length=0,ce.push(new j)}function D(e,t,n){let r=n??le();return e===void 0?r.float():t===void 0?r.float()*e:e+r.float()*(t-e)}function O(e,t,n){let r=n??le();return t===void 0?r.int(e):e+r.int(t-e)}function ne(e,t,n){return e+(n??le()).int(t-e+1)}function k(e,t,n){let r=n??le();return e+Math.floor((r.float()+r.float())*(t-e+1)/2)}function A(e,t){return(t??le()).float()<e}function re(e,t){return e.length>0?e[(t??le()).int(e.length)]:null}function ie(e,t){let n=0;for(let t of e)n+=t;if(n<=0)return null;let r=(t??le()).float()*n;for(let t=0;t<e.length;t++)if(r-=e[t],r<0)return t;return e.length-1}function ae(e,t){let n=[...e.keys()],r=ie(n.map(t=>e.get(t)??0),t);return r===null?null:n[r]}function oe(e,t){for(let n=e.length-1;n>0;n--){let r=(t??le()).int(n+1);[e[n],e[r]]=[e[r],e[n]]}return e}function se(e){let t=2166136261;for(let n=0;n<e.length;n++)t^=e.charCodeAt(n),t=Math.imul(t,16777619)>>>0;return t>>>0}var j,ce,le,ue,de,fe,pe,me,he,ge,_e=s((()=>{j=class{constructor(e){this.s0=0,this.s1=0,this.s2=0,this.s3=0,this.seed=e===void 0?Math.random()*4294967296>>>0:e>>>0;let t=this.seed,n=()=>{t=t+2654435769>>>0;let e=t;return e=Math.imul(e^e>>>16,569420461),e=Math.imul(e^e>>>15,1935289751),(e^e>>>15)>>>0};this.s0=n(),this.s1=n(),this.s2=n(),this.s3=n(),(this.s0|this.s1|this.s2|this.s3)===0&&(this.s0=1)}nextUint32(){let e=Math.imul(this.rotl(Math.imul(this.s1,5),7),9)>>>0>>>0,t=this.s1<<9>>>0;return this.s2^=this.s0,this.s3^=this.s1,this.s1^=this.s2,this.s0^=this.s3,this.s2^=t,this.s3=this.rotl(this.s3,11),e}float(){return this.nextUint32()/4294967296}int(e){if(e<=0)return 0;let t=4294967296-4294967296%e,n=this.nextUint32();for(;n>=t;)n=this.nextUint32();return n%e}getState(){return[this.s0,this.s1,this.s2,this.s3]}setState(e){[this.s0,this.s1,this.s2,this.s3]=e}rotl(e,t){return(e<<t|e>>>32-t)>>>0}},ce=[new j],le=()=>ce[ce.length-1],ue=624,de=397,fe=2567483615,pe=2147483648,me=2147483647,he=class{constructor(e=5489){this.state=new Uint32Array(ue),this.index=ue,this.seedValue=5489,this.produced=0,this.seed(e)}seed(e){this.seedValue=e>>>0,this.state[0]=this.seedValue;for(let e=1;e<ue;e++){let t=this.state[e-1]^this.state[e-1]>>>30;this.state[e]=Math.imul(1812433253,t)+e>>>0}this.index=ue,this.produced=0}generate(){for(let e=0;e<ue;e++){let t=this.state[e]&pe|this.state[(e+1)%ue]&me,n=this.state[(e+de)%ue]^t>>>1;t&1&&(n^=fe),this.state[e]=n>>>0}this.index=0}nextUint32(){this.index>=ue&&this.generate();let e=this.state[this.index++];return e^=e>>>11,e^=e<<7&2636928640,e^=e<<15&4022730752,e^=e>>>18,this.produced++,e>>>0}float(){return this.nextUint32()/4294967296}int(e){if(e<=0)return 0;let t=4294967296-4294967296%e,n=this.nextUint32();for(;n>=t;)n=this.nextUint32();return n%e}discard(e){for(let t=0;t<e;t++)this.nextUint32()}get discardCount(){return this.produced}getState(){return{seed:this.seedValue,state:[...this.state],index:this.index,discard:this.produced}}setState(e){this.seedValue=e.seed>>>0;for(let t=0;t<ue;t++)this.state[t]=(e.state[t]??0)>>>0;this.index=e.index,this.produced=e.discard}},ge=class{constructor(e=0){this.streams=new Map,this.baseSeed=e>>>0}stream(e){let t=this.streams.get(e);if(!t){let n=(this.baseSeed^se(e))>>>0;t={seed:n,engine:new he(n)},this.streams.set(e,t)}return t.engine}seedOf(e){return this.streams.get(e)?.seed}reseed(e){this.baseSeed=e>>>0,this.streams.clear()}names(){return[...this.streams.keys()]}getState(){let e={};for(let[t,n]of this.streams)e[t]=n.engine.getState();return e}setState(e){for(let[t,n]of Object.entries(e)){let e=new he(n.seed);e.setState(n),this.streams.set(t,{seed:n.seed,engine:e})}}}})),ve=l({DEFAULT_BINDINGS:()=>je,actionsForKey:()=>Pe,attach:()=>gt,attachPointer:()=>ht,attachSwipe:()=>mt,bind:()=>Me,bindAxis:()=>at,bindButton:()=>it,bindTouch:()=>lt,detach:()=>_t,dispatchAction:()=>Te,dispatchComposition:()=>Ee,dispatchText:()=>we,endFrame:()=>Ke,exportBindings:()=>Le,gamepadAxisCode:()=>rt,gamepadButtonCode:()=>nt,importBindings:()=>Re,isDown:()=>Ue,justPressed:()=>We,justReleased:()=>Ge,keysFor:()=>Ie,onAction:()=>P,onComposition:()=>F,onKey:()=>xe,onText:()=>Se,onWheel:()=>De,pollGamepads:()=>ot,pressTouch:()=>ut,releaseTouch:()=>dt,resetBindings:()=>ze,rumble:()=>st,textFromKey:()=>Ce,touchCode:()=>ct,unbind:()=>Fe,wheelActionFor:()=>Oe}),M=new Map,N=new Set,ye=new Set,be=new Set,P=new p(!0),xe=new p(!0),Se=new p(!0),F=new p(!0);function Ce(e){return e.isComposing||e.ctrlKey||e.metaKey||e.altKey?null:e.key.length===1?e.key:null}function we(e){e.length>0&&Se.dispatch(e)}function Te(e){P.dispatch(e)}function Ee(e){F.dispatch({phase:e.phase,text:e.text})}var De=new p(!0);function Oe(e){return e.ctrlKey||e.metaKey?`zoom`:e.shiftKey?`scrollHorizontal`:`scroll`}function ke(e){let t=Oe(e);t===`zoom`&&e.preventDefault();let n=t===`scrollHorizontal`&&e.deltaX!==0?e.deltaX:e.deltaY;De.dispatch({action:t,delta:n})}var Ae=!1,je={up:[`ArrowUp`,`KeyW`,`Numpad8`],down:[`ArrowDown`,`KeyS`,`Numpad2`],left:[`ArrowLeft`,`KeyA`,`Numpad4`],right:[`ArrowRight`,`KeyD`,`Numpad6`],upLeft:[`Numpad7`],upRight:[`Numpad9`],downLeft:[`Numpad1`],downRight:[`Numpad3`],wait:[`Numpad5`,`Period`],confirm:[`Enter`,`Space`,`NumpadEnter`],cancel:[`Escape`,`Backspace`],menu:[`KeyI`,`Tab`]};function Me(e,t){M.set(e,new Set(t))}function Ne(e,t){let n=M.get(e)??new Set;for(let e of t)n.add(e);M.set(e,n)}function Pe(e){return Be(e)}function Fe(e){M.delete(e)}function Ie(e){return[...M.get(e)??[]]}function Le(){let e={};for(let[t,n]of M)e[t]=[...n];return e}function Re(e){M.clear();for(let[t,n]of Object.entries(e))Me(t,n)}function ze(){Re(je)}ze();function Be(e){let t=[];for(let[n,r]of M)r.has(e)&&t.push(n);return t}function Ve(e){if(!N.has(e)){N.add(e);for(let t of Be(e))ye.add(t),P.dispatch(t)}}function He(e){if(N.has(e)){N.delete(e);for(let t of Be(e))be.add(t)}}function Ue(e){let t=M.get(e);if(!t)return!1;for(let e of t)if(N.has(e))return!0;return!1}function We(e){return ye.has(e)}function Ge(e){return be.has(e)}function Ke(){ye.clear(),be.clear()}function qe(e){let t=Ce(e);t!==null&&we(t)}function Je(e){e.repeat?(xe.dispatch(e),qe(e)):(Ve(e.code),xe.dispatch(e),qe(e))}function Ye(e){He(e.code)}function Xe(e){Ee({phase:`start`,text:e.data??``})}function Ze(e){Ee({phase:`update`,text:e.data??``})}function Qe(e){Ee({phase:`end`,text:e.data??``})}function $e(){N.clear(),ye.clear(),be.clear()}var et=.5,tt=new Set;function nt(e,t){return`Gamepad${e}Button${t}`}function rt(e,t,n){return`Gamepad${e}Axis${t}${n>0?`+`:`-`}`}function it(e,t,n){Ne(e,n.map(e=>nt(t,e)))}function at(e,t,n,r){Ne(e,[rt(t,n,r)])}function ot(e=typeof navigator?.getGamepads==`function`?navigator.getGamepads():[]){tt.clear();for(let t of e)if(t){for(let e=0;e<t.buttons.length;e++)t.buttons[e].pressed&&tt.add(nt(t.index,e));for(let e=0;e<t.axes.length;e++){let n=t.axes[e];n>=et?tt.add(rt(t.index,e,1)):n<=-.5&&tt.add(rt(t.index,e,-1))}}for(let e of tt)Ve(e);for(let e of N)!tt.has(e)&&e.startsWith(`Gamepad`)&&He(e)}function st(e,t,n=typeof navigator?.getGamepads==`function`?navigator.getGamepads():[]){(n[e]?.vibrationActuator)?.playEffect(`dual-rumble`,{duration:t.duration,weakMagnitude:t.weakMagnitude??1,strongMagnitude:t.strongMagnitude??1})}function ct(e){return`Touch:${e}`}function lt(e,t=e){Ne(e,[ct(t)])}function ut(e){Ve(ct(e))}function dt(e){He(ct(e))}var ft={up:`up`,down:`down`,left:`left`,right:`right`,upLeft:`upLeft`,upRight:`upRight`,downLeft:`downLeft`,downRight:`downRight`},pt=[`right`,`downRight`,`down`,`downLeft`,`left`,`upLeft`,`up`,`upRight`];function mt(e,t={}){let n={...ft,...t.actions},r=t.minDistance??24;for(let e of[...pt,`tap`]){let t=n[e];t&&lt(t,`swipe:${e}`)}let i=!1,a=-1,o=0,s=0;function c(e){ut(`swipe:${e}`),dt(`swipe:${e}`)}function l(e){i=!0,a=e.pointerId,o=e.clientX,s=e.clientY}function u(e){if(!i||e.pointerId!==a)return;i=!1;let t=e.clientX-o,l=e.clientY-s;if(Math.hypot(t,l)<r){n.tap&&c(`tap`);return}let u=pt[(Math.round(Math.atan2(l,t)/(Math.PI/4))%8+8)%8];n[u]&&c(u)}return e.addEventListener(`pointerdown`,l),e.addEventListener(`pointerup`,u),e.addEventListener(`pointercancel`,u),()=>{e.removeEventListener(`pointerdown`,l),e.removeEventListener(`pointerup`,u),e.removeEventListener(`pointercancel`,u)}}function ht(e,t={}){let n=t.dragThreshold??4,r=t.toWorld??((e,t)=>({x:e,y:t})),i=new p,a=new p,o=new p,s=new p,c=-1,l=!1,u=!1,d=0,f=0,m=null;function h(t){let n=e.getBoundingClientRect?.()??{left:0,top:0},i=t.clientX-n.left,a=t.clientY-n.top,o=r(i,a);return{x:i,y:a,worldX:o.x,worldY:o.y,dx:l?i-d:0,dy:l?a-f:0,pointerId:t.pointerId,pressed:l}}function g(t){if(l)return;l=!0,u=!1,c=t.pointerId;let n=e.getBoundingClientRect?.()??{left:0,top:0};d=t.clientX-n.left,f=t.clientY-n.top,m=h(t),i.dispatch(m)}function _(e){l&&e.pointerId!==c||(m=h(e),a.dispatch(m),l&&(!u&&Math.hypot(m.dx,m.dy)>=n&&(u=!0),u&&o.dispatch(m)))}function v(e){if(!l||e.pointerId!==c)return;let t=u,n=h(e);l=!1,u=!1,m={...n,pressed:!1,dx:0,dy:0},s.dispatch({...n,pressed:!1,dragged:t})}return e.addEventListener(`pointerdown`,g),e.addEventListener(`pointermove`,_),e.addEventListener(`pointerup`,v),e.addEventListener(`pointercancel`,v),{down:i,move:a,drag:o,up:s,get current(){return m},detach(){e.removeEventListener(`pointerdown`,g),e.removeEventListener(`pointermove`,_),e.removeEventListener(`pointerup`,v),e.removeEventListener(`pointercancel`,v)}}}function gt(e=window){Ae||(Ae=!0,e.addEventListener(`keydown`,Je),e.addEventListener(`keyup`,Ye),e.addEventListener(`compositionstart`,Xe),e.addEventListener(`compositionupdate`,Ze),e.addEventListener(`compositionend`,Qe),window.addEventListener(`blur`,$e),e.addEventListener(`wheel`,ke,{passive:!1}))}function _t(e=window){Ae&&(Ae=!1,e.removeEventListener(`keydown`,Je),e.removeEventListener(`keyup`,Ye),e.removeEventListener(`compositionstart`,Xe),e.removeEventListener(`compositionupdate`,Ze),e.removeEventListener(`compositionend`,Qe),e.removeEventListener(`wheel`,ke),window.removeEventListener(`blur`,$e),$e())}var vt=class{constructor(e,t={}){if(!e)throw Error(`a player needs a non-empty id`);this.id=e,this.padIndex=t.padIndex}scoped(e){return`${this.id}:${e}`}bind(e,t){Me(this.scoped(e),t)}bindButton(e,t){if(this.padIndex===void 0)throw Error(`player "${this.id}" has no padIndex to bind a gamepad button to`);it(this.scoped(e),this.padIndex,t)}bindAxis(e,t,n){if(this.padIndex===void 0)throw Error(`player "${this.id}" has no padIndex to bind a gamepad axis to`);at(this.scoped(e),this.padIndex,t,n)}bindTouch(e,t=e){lt(this.scoped(e),`${this.id}:${t}`)}pressTouch(e){ut(`${this.id}:${e}`)}releaseTouch(e){dt(`${this.id}:${e}`)}isDown(e){return Ue(this.scoped(e))}justPressed(e){return We(this.scoped(e))}justReleased(e){return Ge(this.scoped(e))}keysFor(e){return Ie(this.scoped(e))}},yt=25214903917n,bt=11n,xt=(1n<<48n)-1n,St=class{constructor(e=0,t={}){this.state=0n,this.onDraw=t.onDraw,this.setSeed(e)}setSeed(e){this.state=(BigInt.asIntN(64,BigInt(e))^yt)&xt}next(e){if(!Number.isInteger(e)||e<1||e>32)throw Error(`next needs 1 to 32 bits, got ${e}`);this.state=this.state*yt+bt&xt;let t=Number(this.state>>BigInt(48-e)),n=e===32?t|0:t;return this.onDraw?.({bits:e,value:n}),n}nextInt(e){if(e===void 0)return this.next(32);if(!Number.isInteger(e)||e<=0||e>2147483647)throw Error(`nextInt needs a bound from 1 to 2147483647, got ${e}`);if((e&-e)===e)return Math.floor(e*this.next(31)/2147483648);let t,n;do t=this.next(31),n=t%e;while(t-n+(e-1)>2147483647);return n}nextLong(){let e=BigInt(this.next(32)),t=BigInt(this.next(32));return BigInt.asIntN(64,(e<<32n)+t)}nextDouble(){return(this.next(26)*134217728+this.next(27))/2**53}nextFloat(){return this.next(24)/16777216}nextBoolean(){return this.next(1)!==0}};_e();function Ct(e,t){if(!t)throw Error(`scramble needs a non-empty key`);let n=new TextEncoder().encode(e),r=new TextEncoder().encode(t),i=new Uint8Array(n.length);for(let e=0;e<n.length;e++)i[e]=n[e]^r[e%r.length];return Tt(i)}function wt(e,t){if(!t)throw Error(`scramble needs a non-empty key`);let n=Et(e),r=new TextEncoder().encode(t),i=new Uint8Array(n.length);for(let e=0;e<n.length;e++)i[e]=n[e]^r[e%r.length];return new TextDecoder(`utf-8`,{fatal:!0}).decode(i)}function Tt(e){let t=``;for(let n of e)t+=String.fromCharCode(n);return btoa(t)}function Et(e){let t=atob(e),n=new Uint8Array(t.length);for(let e=0;e<t.length;e++)n[e]=t.charCodeAt(e);return n}var Dt=10485760;function Ot(e,t={}){let n=t.maxBytes??Dt,r=typeof e==`string`?new TextEncoder().encode(e).length:e.length;if(r>n)throw Error(`inbound data exceeds the ${n}-byte limit (${r} bytes)`)}function kt(e){for(let t=0;t<e.length;t++){let n=e.charCodeAt(t);if(n!==9&&n!==10&&n!==13&&(n<=31||n===127))throw Error(`inbound data contains a control character (0x${n.toString(16).padStart(2,`0`)}) at index ${t}`)}}function At(e,t={}){return Ot(e,t),kt(e),e}var jt=new Set([`__proto__`,`constructor`,`prototype`]);function Mt(e,t=`$`){if(Array.isArray(e))e.forEach((e,n)=>Mt(e,`${t}[${n}]`));else if(typeof e==`object`&&e)for(let n of Object.keys(e)){if(jt.has(n))throw Error(`${t} contains a forbidden key "${n}"`);Mt(e[n],`${t}.${n}`)}}function Nt(e,t,n=`$`){if(e===void 0){if(t.optional)return;throw Error(`${n} is required`)}switch(t.type){case`string`:if(typeof e!=`string`)throw Error(`${n} must be a string`);return;case`number`:if(typeof e!=`number`||!Number.isFinite(e))throw Error(`${n} must be a finite number`);if(t.min!==void 0&&e<t.min)throw Error(`${n} must be >= ${t.min}`);if(t.max!==void 0&&e>t.max)throw Error(`${n} must be <= ${t.max}`);return;case`boolean`:if(typeof e!=`boolean`)throw Error(`${n} must be a boolean`);return;case`array`:if(!Array.isArray(e))throw Error(`${n} must be an array`);if(t.maxLength!==void 0&&e.length>t.maxLength)throw Error(`${n} exceeds its maximum length of ${t.maxLength}`);e.forEach((e,r)=>Nt(e,t.items,`${n}[${r}]`));return;case`object`:if(typeof e!=`object`||!e||Array.isArray(e))throw Error(`${n} must be an object`);for(let t of Object.keys(e))if(jt.has(t))throw Error(`${n} contains a forbidden key "${t}"`);for(let[r,i]of Object.entries(t.fields))Nt(e[r],i,`${n}.${r}`);return}}function Pt(e,t={}){let n=t.label??`inbound data`;At(e,t);try{return JSON.parse(e,(e,t)=>{if(jt.has(e))throw new Ft(e);return t})}catch(e){throw e instanceof Ft?Error(`${n} contains a forbidden key "${e.key}"`):Error(`${n} is not valid JSON (${e instanceof Error?e.message:String(e)})`)}}var Ft=class{constructor(e){this.key=e}};function It(e,t){let n=Pt(e,{label:t}),r=n?.meta;if(typeof n!=`object`||!n||Array.isArray(n)||!(`state`in n))throw Error(`${t} is not save data (expected { meta, state })`);if(typeof r!=`object`||!r||!Number.isSafeInteger(r.version)||r.version<0)throw Error(`${t} has no valid meta.version`);if(typeof r.savedAt!=`number`||!Number.isFinite(r.savedAt))throw Error(`${t} has no valid meta.savedAt`);return n}var Lt=class{constructor(){this.data=new Map}read(e){return this.data.get(e)??null}write(e,t){this.data.set(e,t)}remove(e){this.data.delete(e)}keys(){return[...this.data.keys()]}};function Rt(){return typeof localStorage>`u`?new Lt:{read:e=>localStorage.getItem(e),write:(e,t)=>localStorage.setItem(e,t),remove:e=>localStorage.removeItem(e),keys:()=>Object.keys(localStorage)}}var zt=class{constructor(e){this.namespace=e.namespace,this.version=e.version,this.migrations=e.migrations??{},this.storage=e.storage??Rt()}key(e){return`mwg-save:${this.namespace}:${e}`}save(e,t,n){let r={meta:{version:this.version,savedAt:Date.now(),preview:n},state:t};this.storage.write(this.key(e),JSON.stringify(r))}load(e){let t=this.storage.read(this.key(e));if(!t)return null;try{let n=It(t,`save slot "${e}"`),r=n.state;for(let e=n.meta.version;e<this.version;e++)r=(this.migrations[e]??(e=>e))(r);return{meta:{...n.meta,version:this.version},state:r}}catch{return null}}importExternal(e,t,n,r){Ot(t);let i=n(t);for(let e=0;e<this.version;e++)i=(this.migrations[e]??(e=>e))(i);let a={meta:{version:this.version,savedAt:Date.now(),preview:r},state:i};this.storage.write(this.key(e),JSON.stringify(a))}delete(e){this.storage.remove(this.key(e))}exportSlot(e,t){let n=this.storage.read(this.key(e));return n===null?null:t?Ct(n,t):n}importSlot(e,t,n){Ot(t);let r=It(n?wt(t,n):t,`SaveSystem.importSlot: payload`),i=r.state;for(let e=r.meta.version;e<this.version;e++)i=(this.migrations[e]??(e=>e))(i);let a={meta:{...r.meta,version:this.version},state:i};this.storage.write(this.key(e),JSON.stringify(a))}list(){let e=`mwg-save:${this.namespace}:`,t=[];for(let n of this.storage.keys()){if(!n.startsWith(e))continue;let r=this.storage.read(n);if(r)try{t.push({slot:n.slice(e.length),meta:It(r,n).meta})}catch{}}return t}},Bt=class{constructor(e,t){this.storage=e,this.key=t}read(e){let t=this.storage.read(this.key);return t?Pt(t,{label:`stored value "${this.key}"`}):e}write(e){this.storage.write(this.key,JSON.stringify(e))}remove(){this.storage.remove(this.key)}};function Vt(e,t=`value`){return Ht(e,t,new Set)}function Ht(e,t,n){if(typeof e==`function`||typeof e==`symbol`)return t;if(typeof e!=`object`||!e||n.has(e)||(n.add(e),Ut(e)))return null;let r=[];if(e instanceof Map)for(let[n,i]of e)r.push([`${t}.get(${JSON.stringify(n)??String(n)})`,i]);else if(e instanceof Set)for(let n of e)r.push([`${t}.item`,n]);else if(Array.isArray(e))e.forEach((e,n)=>r.push([`${t}[${n}]`,e]));else for(let[n,i]of Object.entries(e))r.push([`${t}.${n}`,i]);for(let[e,t]of r){let r=Ht(t,e,n);if(r)return r}return t}function Ut(e){try{return structuredClone(e),!0}catch{return!1}}function Wt(e,t){try{return structuredClone(e)}catch(n){let r=Vt(e,t)??t;throw TypeError(`${r} cannot be structured-cloned (${n instanceof Error?n.message:String(n)}). Journals, snapshots and undo checkpoints copy their contents with structuredClone, so they must hold plain data: refer to a live object (an actor, anything carrying callbacks such as a ReactionTable) by its id and resolve it inside the rule.`,{cause:n})}}var Gt=class{constructor(e,t={}){this._state=Wt(e,`initial state`),this.version=t.version??1,this.migrations=t.migrations??{},this.extensions=t.extensions??new Kt}get state(){return structuredClone(this._state)}set(e){this._state=Wt(e,`state`)}update(e){let t=e(this.state);return this.set(t),this.state}snapshot(){let e=this.extensions.snapshot();return{version:this.version,state:this.state,extensions:e.extensions,extensionVersions:e.versions}}restore(e,t={}){if(!Number.isSafeInteger(e.version)||e.version<1)throw Error(`canonical state snapshot version must be a positive integer`);if(e.version>this.version)throw Error(`canonical state snapshot version ${e.version} is newer than ${this.version}`);let n=this.snapshot();try{let n=Wt(e.state,`snapshot.state`);for(let t=e.version;t<this.version;t++){let e=this.migrations[t+1];if(!e)throw Error(`missing canonical state migration to version ${t+1}`);n=e(n)}return this._state=Wt(n,`migrated state`),this.extensions.restore({extensions:e.extensions,versions:e.extensionVersions},t)}catch(e){throw this._state=structuredClone(n.state),this.extensions.restore({extensions:n.extensions,versions:n.extensionVersions},{missing:`keep`}),e}}transaction(e){let t=this.snapshot();try{return e(this)}catch(e){throw this.restore(t),e}}},Kt=class{constructor(){this.extensions=new Map}register(e){if(!/^[A-Za-z_][A-Za-z0-9_.-]*$/.test(e.id))throw Error(`invalid state extension id: ${e.id}`);if(this.extensions.has(e.id))throw Error(`state extension already registered: ${e.id}`);return this.extensions.set(e.id,{id:e.id,capture:()=>e.capture(),restore:t=>e.restore(t),version:e.version??1,migrations:e.migrations,reset:e.reset,remove:e.remove}),()=>this.extensions.delete(e.id)}snapshot(){let e={};for(let[t,n]of this.extensions)e[t]=Wt(n.capture(),`extension ${t}`);let t={};for(let[e,n]of this.extensions)t[e]=n.version??1;return{extensions:e,versions:t}}restore(e,t={}){let n=this.snapshot(),r=[];try{for(let[n,i]of this.extensions){if(!(n in e.extensions)){t.missing===`reset`?(i.reset&&i.reset(),r.push({extension:n,from:0,to:i.version??1,status:`reset`})):t.missing===`remove`&&(i.remove&&i.remove(),r.push({extension:n,from:0,to:i.version??1,status:`removed`}));continue}let a=e.versions?.[n]??1,o=i.version??1,s=Wt(e.extensions[n],`snapshot.extensions.${n}`);for(let e=a;e<o;e++){let t=i.migrations?.[e+1];if(!t)throw Error(`missing state migration for ${n} to version ${e+1}`);s=t(s)}i.restore(Wt(s,`migrated extension ${n}`)),r.push({extension:n,from:a,to:o,status:a===o?`unchanged`:`migrated`})}}catch(e){for(let[e,t]of this.extensions)e in n.extensions&&t.restore(structuredClone(n.extensions[e]));throw e}for(let e of r)t.onDiagnostic?.(e);return r}transaction(e){let t=this.snapshot();try{return e()}catch(e){throw this.restore(t),e}}},qt=new Set([`localhost`,`127.0.0.1`,`[::1]`]);function Jt(e,t,n=!1){if(n||!/^[a-z][a-z0-9+.-]*:/i.test(e))return;let r=new URL(e);if(r.protocol!==`https:`&&r.protocol!==`wss:`&&(r.protocol!==`http:`&&r.protocol!==`ws:`||!qt.has(r.hostname)))throw Error(`${t} url must use https: or wss: (got ${r.protocol}//${r.host}); pass allowInsecure: true to accept it`)}var Yt=class{constructor(e,t){if(!e.endpoint)throw Error(`${t} endpoint is required`);if(Jt(e.endpoint,`${t} endpoint`,e.allowInsecure),this.endpoint=e.endpoint,this.label=t,this.maxResponseBytes=e.maxResponseBytes??10485760,this.timeoutMs=e.timeoutMs??1e4,!(this.timeoutMs>0))throw Error(`${t} timeout must be positive`);if(this.fetchFn=e.fetch??globalThis.fetch,!this.fetchFn)throw Error(`fetch is unavailable; provide ${t}Options.fetch`)}async withTimeout(e){let t=new AbortController,n=setTimeout(()=>t.abort(),this.timeoutMs);try{return await e(t.signal)}finally{clearTimeout(n)}}async readText(e){let t=this.maxResponseBytes,n=Number(e.headers?.get?.(`content-length`));if(n>t)throw Error(`${this.label} response exceeds the ${t}-byte limit (${n} bytes)`);if(!e.body){let n=await e.text();return Ot(n,{maxBytes:t}),n}let r=e.body.getReader(),i=[],a=0;for(;;){let{done:e,value:n}=await r.read();if(e)break;if(a+=n.byteLength,a>t)throw await r.cancel(),Error(`${this.label} response exceeds the ${t}-byte limit`);i.push(n)}let o=new Uint8Array(a),s=0;for(let e of i)o.set(e,s),s+=e.byteLength;return new TextDecoder().decode(o)}async readJson(e,t){return Pt(await this.readText(e),{maxBytes:this.maxResponseBytes,label:`${this.label} ${t}`})}},Xt=class extends Yt{constructor(e){super(e,`save sync`)}async upload(e,t){if(!e)throw Error(`save sync slot name is required`);if(!t)throw Error(`save sync payload cannot be empty`);return this.withTimeout(async n=>{let r=await this.fetchFn(this.slotUrl(e),{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({payload:t}),signal:n});if(!r.ok)throw Error(`save upload failed with HTTP ${r.status}`);return{ok:!0,status:r.status}})}async download(e){if(!e)throw Error(`save sync slot name is required`);return this.withTimeout(async t=>{let n=await this.fetchFn(this.slotUrl(e),{signal:t});if(!n.ok)throw Error(`save download failed with HTTP ${n.status}`);let r=await this.readJson(n,`download`);if(typeof r?.payload!=`string`)throw Error(`save download response missing a string payload`);return r.payload})}async list(){return this.withTimeout(async e=>{let t=await this.fetchFn(this.endpoint,{signal:e});if(!t.ok)throw Error(`save slot list failed with HTTP ${t.status}`);let n=await this.readJson(t,`slot list`);if(!Array.isArray(n?.slots)||!n.slots.every(e=>typeof e==`string`))throw Error(`save slot list response missing a string array of slots`);return n.slots})}slotUrl(e){let t=this.endpoint.includes(`?`)?`&`:`?`;return`${this.endpoint}${t}slot=${encodeURIComponent(e)}`}},Zt=1,Qt=class{constructor(e){if(this.onWelcome=new p,this.onTick=new p,this.onReject=new p,this.onDesync=new p,this.onClose=new p,this.onProtocolError=new p,this.socket=null,this._id=null,!e.url)throw Error(`lockstep client url is required`);Jt(e.url,`lockstep client`,e.allowInsecure),this.url=e.url,this.createSocket=e.create??(e=>new WebSocket(e)),this.validateInput=e.validateInput,this.maxMessageBytes=e.maxMessageBytes??1048576}get id(){return this._id}get connected(){return this.socket!==null&&this.socket.readyState===Zt}connect(){if(this.socket)throw Error(`lockstep client is already connected`);let e=this.createSocket(this.url);e.onmessage=e=>this.handleMessage(e.data),e.onclose=()=>{this.socket=null,this._id=null,this.onClose.dispatch()},this.socket=e}submitInput(e,t){if(!this.connected)return;let n=this.validateInput?.(e);n===!1||typeof n==`string`?this.onReject.dispatch({reason:n===!1?`input rejected by client validation`:n}):this.socket?.send(JSON.stringify({type:`input`,payload:e,...t===void 0?{}:{checksum:t}}))}close(){this.socket?.close()}handleMessage(e){let t;try{if(typeof e!=`string`)throw Error(`lockstep message is not text`);t=en(Pt(e,{maxBytes:this.maxMessageBytes,label:`lockstep message`}))}catch(e){this.onProtocolError.dispatch({reason:e instanceof Error?e.message:String(e)});return}if(t.type===`welcome`)this._id=t.id,this.onWelcome.dispatch({id:t.id,...t.seed===void 0?{}:{seed:t.seed},...t.initialState===void 0?{}:{initialState:t.initialState}});else if(t.type===`tick`){let e={tick:t.tick,inputs:t.inputs,...t.checksums===void 0?{}:{checksums:t.checksums}};if(this.onTick.dispatch(e),t.checksums){let e=Object.values(t.checksums);e.some(t=>t!==e[0])&&this.onDesync.dispatch({tick:t.tick,checksums:t.checksums})}}else this.onReject.dispatch({reason:t.reason??`input rejected by server`})}},$t=e=>typeof e==`object`&&!!e&&!Array.isArray(e);function en(e){if(!$t(e)||typeof e.type!=`string`)throw Error(`lockstep message has no type`);switch(e.type){case`welcome`:if(typeof e.id!=`string`||e.id===``)throw Error(`welcome needs a string id`);if(e.seed!==void 0&&!Number.isSafeInteger(e.seed))throw Error(`welcome seed must be an integer`);return e;case`tick`:if(!Number.isSafeInteger(e.tick)||e.tick<0)throw Error(`tick needs a non-negative integer tick`);if(!$t(e.inputs))throw Error(`tick needs an inputs object`);if(e.checksums!==void 0&&(!$t(e.checksums)||Object.values(e.checksums).some(e=>typeof e!=`number`)))throw Error(`tick checksums must map ids to numbers`);return e;case`rejected`:if(e.reason!==void 0&&typeof e.reason!=`string`)throw Error(`rejected reason must be a string`);return e;default:throw Error(`unknown lockstep message type "${e.type}"`)}}var tn=class{constructor(e){this.store=new Bt(e.storage??Rt(),`mwg-runs:${e.namespace}`),this.limit=e.limit}readAll(){return this.store.read([])}record(e){let t=this.readAll(),n={id:`${Date.now()}-${t.length}`,endedAt:Date.now(),summary:e};return t.push(n),this.limit!==void 0&&t.length>this.limit&&t.splice(0,t.length-this.limit),this.store.write(t),n}all(){return this.readAll()}ranked(e,t=`desc`){let n=this.readAll(),r=t===`desc`?-1:1;return n.sort((t,n)=>r*(e(t.summary)-e(n.summary)))}clear(){this.store.remove()}},nn=class{constructor(e){this.store=new Bt(e.storage??Rt(),`mwg-stats:${e.namespace}`),this.initial=e.initial,this.combine=e.combine}get(){return this.store.read(this.initial)}record(e){let t=this.combine(this.get(),e);return this.store.write(t),t}reset(){this.store.remove()}},rn=class extends Yt{constructor(e){super(e,`telemetry`),this.consented=!1,this.maxStringLength=e.maxStringLength??256,this.maxProperties=e.maxProperties??32,this.allowed=e.allowedProperties?new Set(e.allowedProperties):null}get hasConsent(){return this.consented}setConsent(e){this.consented=e}async send(e){if(!e.name)throw Error(`telemetry event needs a name`);return this.consented?this.withTimeout(async t=>{let n=await this.fetchFn(this.endpoint,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify(this.bounded(e)),signal:t});if(!n.ok)throw Error(`telemetry request failed with HTTP ${n.status}`);return{ok:!0,status:n.status}}):null}bounded(e){let t=e=>e.length>this.maxStringLength?e.slice(0,this.maxStringLength):e;if(!e.properties)return{name:t(e.name)};let n={},r=0;for(let[i,a]of Object.entries(e.properties))if(!this.allowed||this.allowed.has(i)){if(r++>=this.maxProperties)break;n[t(i)]=typeof a==`string`?t(a):a}return{name:t(e.name),properties:n}}},an=class extends Yt{constructor(e){super(e,`news`)}async fetchItems(){return this.withTimeout(async e=>{let t=await this.fetchFn(this.endpoint,{signal:e});if(!t.ok)throw Error(`news request failed with HTTP ${t.status}`);let n=await this.readJson(t,`response`);if(!Array.isArray(n))throw Error(`news response was not an array`);return n.map((e,t)=>on(e,t))})}};function on(e,t){if(typeof e!=`object`||!e)throw Error(`news item ${t} was not an object`);let n=e;if(typeof n.id!=`string`||!n.id)throw Error(`news item ${t} is missing a string id`);if(typeof n.title!=`string`)throw Error(`news item ${t} is missing a string title`);if(typeof n.body!=`string`)throw Error(`news item ${t} is missing a string body`);return{id:n.id,title:n.title,body:n.body,publishedAt:typeof n.publishedAt==`number`?n.publishedAt:void 0}}var sn=class{constructor(e){this.store=new Bt(e.storage??Rt(),`mwg-news-seen:${e.namespace}`)}readSeen(){return new Set(this.store.read([]))}isSeen(e){return this.readSeen().has(e)}markSeen(e){let t=this.readSeen();t.add(e),this.store.write([...t])}unseen(e){let t=this.readSeen();return e.filter(e=>!t.has(e.id))}},cn=class{constructor(e,t={}){if(!e)throw Error(`a collection needs a name`);this.prefix=`mwg-db:${t.namespace??`default`}:${e}:`,this.storage=t.storage??Rt()}get size(){return this.keys().length}all(){return this.keys().map(e=>this.read(e))}get(e){let t=this.storage.read(this.prefix+e);return t===null?void 0:this.parse(t,this.prefix+e)}put(e){if(typeof e!=`object`||!e||Array.isArray(e)||typeof e.id!=`string`||e.id===``)throw Error(`a record must be an object with a string id`);this.storage.write(this.prefix+e.id,JSON.stringify(e))}remove(e){this.storage.remove(this.prefix+e)}where(e){return this.all().filter(e)}clear(){for(let e of this.keys())this.storage.remove(e)}keys(){return this.storage.keys().filter(e=>e.startsWith(this.prefix))}read(e){return this.parse(this.storage.read(e),e)}parse(e,t){return Pt(e,{label:`record "${t}"`})}},ln=class{constructor(e,t,n){if(this.recorded=[],this.fromFrame=n?.fromFrame??0,!Number.isInteger(this.fromFrame)||this.fromFrame<0)throw RangeError(`a resumed recorder needs a non-negative fromFrame`);this.frameStamp=this.fromFrame,this.actions=e,this.frames=t,this.onAction=e=>{this.recorded.push({frame:this.frameStamp,action:e})},this.onFrame=()=>{this.frameStamp++},e.add(this.onAction),t.add(this.onFrame)}get events(){return this.recorded}get frame(){return this.frameStamp}toJSON(){return this.recorded.map(e=>({frame:e.frame,action:e.action}))}stop(){this.actions.remove(this.onAction),this.frames.remove(this.onFrame)}},un=class{constructor(e,t,n,r){if(this.events=e,this.dispatch=t,this.frames=n,this.fromFrame=r?.fromFrame??0,!Number.isInteger(this.fromFrame)||this.fromFrame<0)throw RangeError(`a resumed player needs a non-negative fromFrame`);this.index=this.events.findIndex(e=>e.frame>=this.fromFrame),this.index<0&&(this.index=this.events.length),this.frame=this.fromFrame,this.onFrame=()=>this.pump(),n.add(this.onFrame)}get done(){return this.index>=this.events.length}stop(){this.frames.remove(this.onFrame)}pump(){for(;this.index<this.events.length&&this.events[this.index].frame<=this.frame;)this.dispatch(this.events[this.index].action),this.index++;this.frame++}};function dn(e){return JSON.stringify(e)}function fn(e){return pn(JSON.parse(e))}function pn(e){if(!Array.isArray(e))throw Error(`a replay must be an array of {frame, action}`);return e.map(e=>{if(typeof e!=`object`||!e||!Number.isInteger(e.frame)||e.frame<0||typeof e.action!=`string`)throw Error(`a replay entry must be {frame: a non-negative integer, action: a string}`);return{frame:e.frame,action:e.action}})}function mn(e,t){if(typeof t!=`string`||!t.length)throw TypeError(`a replay file needs a non-empty ${e}`);return t}function hn(e){let t=e;if(!Number.isInteger(t)||t<0||t>4294967295)throw RangeError(`a replay seed must be uint32`);return t}function gn(e,t){let n={format:`mwg-replay`,version:1,framework:mn(`framework`,t.framework),game:mn(`game`,t.game),events:pn(e)};if(t.seed!==void 0&&(n.seed=hn(t.seed)),t.recordedAt!==void 0){if(typeof t.recordedAt!=`number`||!Number.isFinite(t.recordedAt))throw TypeError(`recordedAt must be a finite number`);n.recordedAt=t.recordedAt}return JSON.stringify(n)}function _n(e,t){let n=Pt(e,{label:`replay file`});if(typeof n!=`object`||!n||Array.isArray(n))throw Error(`a replay file must be a JSON object`);if(n.format!==`mwg-replay`)throw Error(`not a replay file: expected format "mwg-replay", found ${JSON.stringify(n.format)}`);if(n.version!==1)throw Error(`replay file version ${JSON.stringify(n.version)} is not supported (this build reads 1)`);if(n.game!==t.game)throw Error(`a replay of ${JSON.stringify(n.game)} cannot be imported into ${JSON.stringify(t.game)}`);if(n.framework!==t.framework)throw Error(`a replay recorded against mwg ${JSON.stringify(n.framework)} cannot be imported into ${JSON.stringify(t.framework)}`);let r=pn(n.events);return{format:`mwg-replay`,version:1,framework:n.framework,game:n.game,...n.seed===void 0?{}:{seed:hn(n.seed)},...n.recordedAt===void 0?{}:{recordedAt:n.recordedAt},events:r}}var vn=class{constructor(e){if(typeof e.namespace!=`string`||!e.namespace.length)throw TypeError(`a last-run store needs a non-empty namespace`);let t=e.maxEvents??1e4;if(!Number.isInteger(t)||t<1||t>1e6)throw RangeError(`last-run maxEvents must be in 1..1000000`);this.maxEvents=t,this.store=new Bt(e.storage??Rt(),`mwg-last-run:${e.namespace}`)}keep(e,t){let n=pn(e);n.length>this.maxEvents&&(n=n.slice(n.length-this.maxEvents));let r=t===void 0?{version:1,events:n}:{version:1,seed:hn(t),events:n};Ot(JSON.stringify(r)),this.store.write(r)}load(){let e=this.store.read(null);if(e===null)return null;if(e?.version!==1)throw Error(`the kept last-run recording is not version 1; clear() it and record again`);return{version:1,...e.seed===void 0?{}:{seed:hn(e.seed)},events:pn(e.events)}}clear(){this.store.remove()}};function yn(e,t){return{version:1,frame:e.frame,events:e.toJSON(),state:t}}function bn(e,t,n){if(typeof e!=`object`||!e||e.version!==1)throw Error(`a run checkpoint must be { version: 1, frame, events, state }`);return new un(pn(e.events),t,n,{fromFrame:e.frame})}function xn(e,t){if(typeof document>`u`)throw Error(`downloadReplayFile needs a browser document`);let n=document.createElement(`a`);n.href=URL.createObjectURL(new Blob([e],{type:`application/json`})),n.download=t,document.body.appendChild(n),n.click(),n.remove(),setTimeout(()=>URL.revokeObjectURL(n.href),0)}async function Sn(e){return await e.text()}async function Cn(e=`.json`){if(typeof document>`u`)throw Error(`pickReplayFile needs a browser document`);return await new Promise(t=>{let n=document.createElement(`input`);n.type=`file`,n.accept=e,n.style.display=`none`;let r=!1,i=e=>{r||(r=!0,n.remove(),t(e))};n.addEventListener(`change`,()=>i(n.files?.[0]??null)),n.addEventListener(`cancel`,()=>i(null)),document.body.appendChild(n),n.click()})}var wn=class e{constructor(){this.entries=[],this.nextSequence=0}append(e,t=[]){let n={sequence:this.nextSequence,action:Wt(e,`action`),events:Wt([...t],`events`)};return this.nextSequence++,this.entries.push(n),structuredClone(n)}get size(){return this.entries.length}get all(){return structuredClone(this.entries)}mark(){return this.nextSequence}since(e){return structuredClone(this.entries.filter(t=>t.sequence>=e))}truncate(e){this.entries=this.entries.filter(t=>t.sequence<e),this.nextSequence=e}toJSON(){return this.all}replace(t){let n=e.fromJSON(t);this.entries=n.entries,this.nextSequence=n.nextSequence}static fromJSON(t){let n=new e,r=0;for(let e of t){if(!Number.isSafeInteger(e.sequence)||e.sequence!==r)throw Error(`action journal sequences must be contiguous from zero`);n.entries.push(structuredClone({sequence:e.sequence,action:e.action,events:[...e.events]})),r++}return n.nextSequence=r,n}};function Tn(e){if(e.criteria)return e.criteria;if(e.counter===void 0||e.target===void 0)throw Error(`achievement "${e.id}" needs either counter/target or criteria`);return[{counter:e.counter,target:e.target}]}var En=class e{constructor(){this.definitions=new Map,this.counts=new Map,this.fresh=[]}define(e){this.definitions.set(e.id,e)}count(e){return this.counts.get(e)??0}increment(e,t=1){let n=this.count(e);this.counts.set(e,n+t);let r=[];for(let t of this.definitions.values()){let i=Tn(t);i.some(t=>t.counter===e)&&(i.every(t=>t.counter===e?n>=t.target:this.count(t.counter)>=t.target)||i.every(e=>this.count(e.counter)>=e.target)&&(r.push(t.id),this.fresh.push(t.id)))}return r}unlocked(e){let t=this.definitions.get(e);if(!t)throw Error(`no such achievement: "${e}"`);return Tn(t).every(e=>this.count(e.counter)>=e.target)}progress(e){let t=this.definitions.get(e);if(!t)throw Error(`no such achievement: "${e}"`);let n=Tn(t);if(n.length!==1)throw Error(`achievement "${e}" has several criteria - use subProgress`);return{count:this.count(n[0].counter),target:n[0].target}}subProgress(e){let t=this.definitions.get(e);if(!t)throw Error(`no such achievement: "${e}"`);return Tn(t).map(e=>{let t=this.count(e.counter);return{counter:e.counter,count:t,target:e.target,met:t>=e.target}})}drainNew(){let e=this.fresh;return this.fresh=[],e}toJSON(){return{counts:[...this.counts]}}static fromJSON(t,n){let r=new e;for(let e of t)r.define(e);for(let[e,t]of n.counts)r.counts.set(e,t);return r}},Dn=class e{constructor(e=[]){this.active=new Set,this.spent=new Set,this.rules=[...e]}add(e){this.rules.push(e)}remove(e){this.rules=this.rules.filter(t=>t.id!==e),this.active.delete(e)}check(e){let t=[];for(let n of this.rules){if(n.once&&this.spent.has(n.id))continue;let r=n.when(e);r&&!this.active.has(n.id)&&(n.action(e),t.push(n.id),n.once&&this.spent.add(n.id)),r?this.active.add(n.id):this.active.delete(n.id)}return t}isActive(e){return this.active.has(e)}reset(){this.active.clear(),this.spent.clear()}toJSON(){return{active:[...this.active],spent:[...this.spent]}}static fromJSON(t,n){let r=new e(t);for(let e of n.active)r.active.add(e);for(let e of n.spent)r.spent.add(e);return r}},On=class{constructor(e={}){let t=e.storage??Rt(),n=`mwg-session:${e.namespace??`default`}`,r=Number(t.read(n)??`0`);this.launches=(Number.isFinite(r)?r:0)+1,t.write(n,String(this.launches))}};function kn(e,t,n){return e<t?t:e>n?n:e}function An(){return{musicVolume:1,sfxVolume:1,muted:!1,zoom:1,bindings:In(je),custom:{}}}function jn(e){return e.muted?0:e.musicVolume}function Mn(e){return e.muted?0:e.sfxVolume}var Nn=class{constructor(e={}){this.storage=e.storage??Rt(),this.key=`mwg-settings:${e.namespace??`default`}`,this.value=Vn(this.storage,this.key),Re(this.value.bindings)}get current(){return{...this.value,bindings:In(this.value.bindings),custom:{...this.value.custom}}}getCustom(e,t){return this.value.custom[e]??t}setCustom(e,t){this.value.custom[e]=t,Hn(this.storage,this.key,this.value)}setMusicVolume(e){this.update({musicVolume:e})}setSfxVolume(e){this.update({sfxVolume:e})}setMuted(e){this.update({muted:e})}setZoom(e){this.update({zoom:e})}setBindings(e){this.update({bindings:e})}update(e){e.musicVolume!==void 0&&(this.value.musicVolume=Pn(e.musicVolume)),e.sfxVolume!==void 0&&(this.value.sfxVolume=Pn(e.sfxVolume)),e.muted!==void 0&&(this.value.muted=e.muted),e.zoom!==void 0&&(this.value.zoom=Fn(e.zoom)),e.bindings!==void 0&&(this.value.bindings=Ln(e.bindings),Re(this.value.bindings)),e.custom!==void 0&&(this.value.custom=zn(e.custom)),Hn(this.storage,this.key,this.value)}applyBindings(){Re(this.value.bindings)}reset(){this.value=An(),Re(this.value.bindings),Hn(this.storage,this.key,this.value)}};function Pn(e){return Number.isFinite(e)?kn(e,0,1):1}function Fn(e){return Number.isFinite(e)?Math.max(.01,e):1}function In(e){let t={};for(let[n,r]of Object.entries(e))t[n]=[...r];return t}function Ln(e){if(typeof e!=`object`||!e||Array.isArray(e))return In(je);let t={};for(let[n,r]of Object.entries(e)){if(!Array.isArray(r))continue;let e=r.filter(e=>typeof e==`string`);e.length>0&&(t[n]=e)}return Object.keys(t).length>0?t:In(je)}function Rn(e){let t=typeof e;return t===`string`||t===`number`||t===`boolean`}function zn(e){if(typeof e!=`object`||!e||Array.isArray(e))return{};let t={};for(let[n,r]of Object.entries(e))Rn(r)&&(t[n]=r);return t}function Bn(e){let t=An();if(typeof e!=`object`||!e||Array.isArray(e))return t;let n=e;return{musicVolume:typeof n.musicVolume==`number`?Pn(n.musicVolume):t.musicVolume,sfxVolume:typeof n.sfxVolume==`number`?Pn(n.sfxVolume):t.sfxVolume,muted:typeof n.muted==`boolean`?n.muted:t.muted,zoom:typeof n.zoom==`number`?Fn(n.zoom):t.zoom,bindings:Ln(n.bindings),custom:zn(n.custom)}}function Vn(e,t){let n=e.read(t);if(!n)return An();try{return Bn(Pt(n,{label:`settings`}))}catch{return An()}}function Hn(e,t,n){e.write(t,JSON.stringify(n))}var Un=class extends Yt{constructor(e){super(e,`feedback`)}async submit(e){if(!e.message?.trim())throw Error(`feedback message cannot be empty`);return this.withTimeout(async t=>{let n=await this.fetchFn(this.endpoint,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify(e),signal:t});if(!n.ok)throw Error(`feedback request failed with HTTP ${n.status}`);return{ok:!0,status:n.status}})}},Wn=class{constructor(e){this.schedule=[],this.cursor=0,this.elapsed=0,this.startedWaves=new Set,this.completed=!1,this.onSpawn=e.onSpawn,this.onWaveStart=e.onWaveStart,this.onComplete=e.onComplete,e.waves.forEach((e,t)=>{let n=e.entries.reduce((e,t)=>e+t.count,0),r=e.duration??0,i=0;for(let a of e.entries)for(let o=0;o<a.count;o++,i++){let o=n>1&&r>0?i/(n-1)*r:0;this.schedule.push({time:e.delay+o,kind:a.kind,waveIndex:t})}}),this.schedule.sort((e,t)=>e.time-t.time),this.schedule.length===0&&(this.completed=!0,this.onComplete?.())}update(e){if(!this.completed){for(this.elapsed+=e;this.cursor<this.schedule.length&&this.schedule[this.cursor].time<=this.elapsed;){let e=this.schedule[this.cursor];this.startedWaves.has(e.waveIndex)||(this.startedWaves.add(e.waveIndex),this.onWaveStart?.(e.waveIndex)),this.onSpawn(e.kind),this.cursor++}this.cursor>=this.schedule.length&&(this.completed=!0,this.onComplete?.())}}get isComplete(){return this.completed}};function Gn(e,t){let n=e,r=t-(e-(e&1))/2;return{x:n,y:-n-r,z:r}}function Kn(e){return{x:e.x,y:e.z+(e.x-(e.x&1))/2}}var qn=[{x:1,y:-1,z:0},{x:1,y:0,z:-1},{x:0,y:1,z:-1},{x:-1,y:1,z:0},{x:-1,y:0,z:1},{x:0,y:-1,z:1}];function Jn(e,t){let n=Gn(e,t);return qn.map(e=>Kn({x:n.x+e.x,y:n.y+e.y,z:n.z+e.z}))}function Yn(e,t){let n=Gn(e.x,e.y),r=Gn(t.x,t.y);return Math.max(Math.abs(n.x-r.x),Math.abs(n.y-r.y),Math.abs(n.z-r.z))}function Xn(e){let t=Math.round(e.x),n=Math.round(e.y),r=Math.round(e.z),i=Math.abs(t-e.x),a=Math.abs(n-e.y),o=Math.abs(r-e.z);return i>a&&i>o?t=-n-r:a>o?n=-t-r:r=-t-n,{x:t,y:n,z:r}}function Zn(e,t){let n=Yn(e,t);if(n===0)return[{x:e.x,y:e.y}];let r=Gn(e.x,e.y),i=Gn(t.x,t.y),a=[];for(let e=0;e<=n;e++){let t=e/n;a.push(Kn(Xn({x:r.x+(i.x-r.x)*t,y:r.y+(i.y-r.y)*t,z:r.z+(i.z-r.z)*t})))}return a}function Qn(e,t){let n=Gn(e.x,e.y),r=[];for(let e=-t;e<=t;e++)for(let i=Math.max(-t,-e-t);i<=Math.min(t,-e+t);i++){let t=-e-i;r.push(Kn({x:n.x+e,y:n.y+i,z:n.z+t}))}return r}function $n(e,t,n,r,i={}){let a=+((i.offset??`odd`)===`odd`);return(i.orientation??`flat-top`)===`pointy-top`?{x:e*n+((t&1)===a?n/2:0)+n/2,y:t*r*.75+r/2}:{x:e*n*.75+n/2,y:t*r+((e&1)===a?r/2:0)+r/2}}function er(e,t,n,r,i={}){let a=(i.orientation??`flat-top`)===`pointy-top`,o=a?n:n*.75,s=a?r*.75:r,c=Math.round((e-n/2)/o),l=Math.round((t-r/2)/s),u={x:c,y:l},d=1/0;for(let a=-2;a<=2;a++)for(let o=-2;o<=2;o++){let s={x:c+a,y:l+o},f=$n(s.x,s.y,n,r,i),p=(f.x-e)**2+(f.y-t)**2;p<d&&(d=p,u=s)}return u}function tr(e,t,n,r){return n>=0&&r>=0&&n<e&&r<t}function nr(e,t,n){return n*e+t}function rr(e,t){return t%e}function ir(e,t){return Math.floor(t/e)}function ar(e,t){return`${e},${t}`}function or(e){let t=/^(-?\d+),(-?\d+)$/.exec(e);return t?{x:Number(t[1]),y:Number(t[2])}:null}var sr=[[0,-1],[1,0],[0,1],[-1,0]],cr=[[-1,-1],[0,-1],[1,-1],[-1,0],[1,0],[-1,1],[0,1],[1,1]],lr=[[0,-1],[1,-1],[1,0],[1,1],[0,1],[-1,1],[-1,0],[-1,-1]];function ur(e,t){return Math.hypot(e.x-t.x,e.y-t.y)}function dr(e,t){return Math.max(Math.abs(e.x-t.x),Math.abs(e.y-t.y))}function fr(e,t){return Math.abs(e.x-t.x)+Math.abs(e.y-t.y)}function pr(e,t,n){switch(e){case`euclidean`:return ur(t,n);case`manhattan`:return fr(t,n);case`chebyshev`:return dr(t,n);default:throw RangeError(`unknown distance metric: ${e}`)}}var mr=.12,hr=null,gr,_r=new p;function vr(){if(gr===void 0&&(gr=typeof matchMedia==`function`?matchMedia(`(prefers-reduced-motion: reduce)`):null,gr)){let e=()=>{_r.dispatch(br())};typeof gr.addEventListener==`function`?gr.addEventListener(`change`,e):typeof gr.addListener==`function`&&gr.addListener(e)}return gr}function yr(){return vr()?.matches??!1}function br(){return hr??yr()}function xr(e){hr=e,_r.dispatch(br())}function Sr(e){return vr(),_r.add(e),()=>_r.remove(e)}function Cr(e,t=`decorative`){return br()?t===`meaningful`?Math.min(e,mr):0:e}var wr=class e{constructor(e,t){this.width=e,this.height=t,this.volume=new Float32Array(e*t)}index(e,t){return e<0||t<0||e>=this.width||t>=this.height?-1:t*this.width+e}volumeAt(e,t){let n=this.index(e,t);return n<0?0:this.volume[n]}total(){let e=0;for(let t=0;t<this.volume.length;t++)e+=this.volume[t];return e}seed(e,t,n){let r=this.index(e,t);r>=0&&(this.volume[r]+=n)}clear(e,t){let n=this.index(e,t);n>=0&&(this.volume[n]=0)}spread(e,t=.25,n=.9){let r=new Float32Array(this.volume.length);for(let n=0;n<this.height;n++)for(let i=0;i<this.width;i++){let a=n*this.width+i,o=this.volume[a];if(o<=0)continue;let s=[];for(let[t,r]of sr){let a=i+t,o=n+r,c=this.index(a,o);c>=0&&e(a,o)&&s.push(c)}if(s.length===0)r[a]+=o;else{let e=o*t/s.length;r[a]+=o-e*s.length;for(let t of s)r[t]+=e}}let i=[];for(let e=0;e<r.length;e++){let t=r[e]*n,a=t<.001?0:t;a===0&&this.volume[e]>0&&i.push({x:e%this.width,y:Math.floor(e/this.width)}),this.volume[e]=a}return i}cellsAbove(e){let t=[];for(let n=0;n<this.height;n++)for(let r=0;r<this.width;r++){let i=this.volume[n*this.width+r];i>=e&&t.push({x:r,y:n,volume:i})}return t}toJSON(){return{width:this.width,height:this.height,volume:[...this.volume]}}static fromJSON(t){let n=new e(t.width,t.height);return n.volume.set(t.volume.slice(0,n.volume.length)),n}},Tr=class{constructor(e=1/60,t=5){if(this.accumulator=0,!(e>0))throw Error(`a fixed step must be positive`);this.step=e,this.maxSubsteps=Math.max(1,Math.floor(t))}advance(e,t){this.accumulator+=Math.max(0,e);let n=0;for(;this.accumulator>=this.step&&n<this.maxSubsteps;)t(this.step),this.accumulator-=this.step,n++;return this.accumulator>=this.step&&(this.accumulator=0),n}get alpha(){return this.accumulator/this.step}reset(){this.accumulator=0}},Er={linear:e=>e,easeInQuad:e=>e*e,easeOutQuad:e=>e*(2-e),easeInOutQuad:e=>e<.5?2*e*e:1-(-2*e+2)**2/2,easeInCubic:e=>e*e*e,easeOutCubic:e=>1-(1-e)**3,easeInOutCubic:e=>e<.5?4*e*e*e:1-(-2*e+2)**3/2},Dr=class{constructor(){this.tweens=[]}tween(e,t,n=Er.linear){let{ease:r=Er.linear,intent:i=`decorative`,alternate:a}=typeof n==`function`?{ease:n}:n,o=br()&&a!==void 0,s=o?a:t,c=Cr(e,o?`meaningful`:i);return c>0?new Promise(e=>{this.tweens.push({elapsed:0,duration:c,ease:r,apply:s,resolve:e,finishOnReduced:!o&&i===`decorative`})}):(s(1),Promise.resolve())}update(e){if(this.tweens.length!==0)for(let t of[...this.tweens]){if(t.finishOnReduced&&br()){this.tweens.splice(this.tweens.indexOf(t),1),t.apply(1),t.resolve();continue}t.elapsed+=e;let n=Math.min(1,t.elapsed/t.duration);t.apply(t.ease(n)),n>=1&&(this.tweens.splice(this.tweens.indexOf(t),1),t.resolve())}}get isBusy(){return this.tweens.length>0}clear(){this.tweens=[]}},Or=class{constructor(e={}){this.history=[],this.cursor=-1,this.limit=Math.max(1,e.limit??50)}push(e){this.history.length=this.cursor+1,this.history.push(e),this.history.length>this.limit&&this.history.shift(),this.cursor=this.history.length-1}get canUndo(){return this.cursor>0}get canRedo(){return this.cursor<this.history.length-1}undo(){return this.canUndo?(this.cursor--,this.history[this.cursor]):null}redo(){return this.canRedo?(this.cursor++,this.history[this.cursor]):null}get current(){return this.cursor>=0?this.history[this.cursor]:null}clear(){this.history=[],this.cursor=-1}};function kr(e,t){if(!Number.isFinite(t.maxCost)||t.maxCost<0)return new Map;let n=t.key(e),r=new Map([[n,{cell:e,cost:0}]]),i=[{cell:e,key:n,cost:0}];for(;i.length>0;){let e=jr(i),n=r.get(e.key);if(n&&e.cost===n.cost&&!t.stop?.(e.cell,e.cost))for(let n of t.neighbors(e.cell)){let a=t.cost(e.cell,n);if(!Number.isFinite(a)||a<0)continue;let o=e.cost+a;if(o>t.maxCost||t.canEnter&&!t.canEnter(n,e.cell,o))continue;let s=t.key(n);if(o>=(r.get(s)?.cost??1/0))continue;let c={cell:n,key:s,cost:o};r.set(s,c),Ar(i,c)}}return r}function Ar(e,t){e.push(t);let n=e.length-1;for(;n>0;){let t=Math.floor((n-1)/2);if(e[t].cost<=e[n].cost)break;[e[t],e[n]]=[e[n],e[t]],n=t}}function jr(e){let t=e[0],n=e.pop();if(!t||!n||e.length===0)return t;e[0]=n;let r=0;for(;;){let t=r*2+1,n=t+1,i=r;if(t<e.length&&e[t].cost<e[i].cost&&(i=t),n<e.length&&e[n].cost<e[i].cost&&(i=n),i===r)break;[e[r],e[i]]=[e[i],e[r]],r=i}return t}var Mr=class{constructor(){this.changed=new p,this.tasks=[],this.progress=new Map,this.status_=`idle`,this.current_=null,this.error_=null,this.cancelled=!1}add(e){if(!e.id)throw Error(`load task needs an id`);if(this.tasks.some(t=>t.id===e.id))throw Error(`duplicate load task "${e.id}"`);if(e.weight!==void 0&&!(e.weight>0))throw Error(`load task weight must be positive`);if(this.status_===`loading`)throw Error(`cannot add a task while loading`);return this.tasks.push(e),this.progress.set(e.id,0),this.emit(),this}get snapshot(){let e=this.tasks.reduce((e,t)=>e+(t.weight??1),0),t=this.tasks.reduce((e,t)=>e+(t.weight??1)*(this.progress.get(t.id)??0),0);return{status:this.status_,completed:t,total:e,current:this.current_,error:this.error_}}async start(){if(this.status_===`loading`)throw Error(`load queue is already running`);this.status_=`loading`,this.cancelled=!1,this.error_=null,this.emit();try{for(let e of this.tasks){if(this.cancelled)break;this.current_=e.id,this.emit();let t=this;await e.run({get cancelled(){return t.cancelled},report:t=>this.report(e.id,t)}),this.cancelled||this.report(e.id,1)}this.current_=null,this.status_=this.cancelled?`cancelled`:`ready`,this.emit()}catch(e){throw this.current_=null,this.error_=e,this.status_=`failed`,this.emit(),e}}cancel(){this.status_===`loading`&&(this.cancelled=!0)}retry(){if(this.status_===`loading`)throw Error(`cannot retry while loading`);this.status_=`idle`,this.current_=null,this.error_=null,this.cancelled=!1;for(let e of this.tasks)this.progress.set(e.id,0);this.emit()}report(e,t){if(!Number.isFinite(t))throw Error(`load progress must be finite`);this.progress.set(e,Math.max(0,Math.min(1,t))),this.emit()}emit(){this.changed.dispatch(this.snapshot)}},Nr=class{constructor(){this.entities=new Map,this.ids=new Map,this.sequence=0}add(e,t){let n=this.ids.get(e);if(n!==void 0){if(t!==void 0&&t!==n)throw Error(`entity already has id ${n}`);return n}let r=t;if(r!==void 0){if(r.length===0)throw Error(`entity id must not be empty`);if(this.entities.has(r))throw Error(`entity id already exists: ${r}`)}else do r=`e${this.sequence++}`;while(this.entities.has(r));return this.entities.set(r,e),this.ids.set(e,r),r}get(e){return this.entities.get(e)}idOf(e){return this.ids.get(e)}has(e){return this.entities.has(e)}remove(e){let t=this.entities.get(e);return t!==void 0&&(this.entities.delete(e),this.ids.delete(t),!0)}get size(){return this.entities.size}},Pr=class{constructor(e){this.queue=[],this.busy=!1,this.remaining=0,this.play=e.play}enqueue(e){this.queue.push(...e),this.busy||this.advance()}update(e){this.busy&&(this.remaining-=e,this.remaining<=0&&this.advance())}get isBusy(){return this.busy}clear(){this.queue=[],this.busy=!1,this.remaining=0}advance(){let e=this.queue.shift();if(e===void 0){this.busy=!1,this.remaining=0;return}this.busy=!0;let t=this.play(e)||0;t>0?this.remaining=t:this.advance()}},Fr=2166136261,Ir=16777619;function Lr(e,t=!1){if(e==null)return`null`;switch(typeof e){case`boolean`:return e?`true`:`false`;case`number`:return Number.isFinite(e)?String(e):`null`;case`string`:return JSON.stringify(e);case`bigint`:return JSON.stringify(e.toString());case`function`:case`symbol`:return t?`null`:`undefined`}return Array.isArray(e)?`[${e.map(e=>Lr(e,!0)).join(`,`)}]`:`{${Object.entries(e).filter(([,e])=>e!==void 0&&typeof e!=`function`&&typeof e!=`symbol`).sort(([e],[t])=>e<t?-1:+(e>t)).map(([e,t])=>`${JSON.stringify(e)}:${Lr(t)}`).join(`,`)}}`}function Rr(e){let t=Lr(e),n=Fr;for(let e=0;e<t.length;e++)n^=t.charCodeAt(e),n=Math.imul(n,Ir)>>>0;return n>>>0}var zr=class{constructor(){this.reference=new Map,this.desyncTick=null}observe(e,t){let n=this.reference.get(e);return n===void 0?(this.reference.set(e,t),!0):n===t||(this.desyncTick===null&&(this.desyncTick=e),!1)}get divergent(){return this.desyncTick!==null}get atTick(){return this.desyncTick}reset(){this.reference.clear(),this.desyncTick=null}};function Br(e,t={}){let n=Ur(e);if(n.length===0)return[];let[r,...i]=n,a=t.listDelimiter??`;`,o=t.mapDelimiter??`=`;return i.map((e,n)=>{let i={};return r.forEach((r,s)=>{let c=e[s]??``;c!==``&&(i[r]=Vr(c,t.columns?.[r]??`string`,r,n,a,o))}),i})}function Vr(e,t,n,r,i,a){return Hr(e,t,{column:n,rowIndex:r,listDelimiter:i,mapDelimiter:a,error:()=>t===`number`?`CSV row ${r+1}, column "${n}": "${e}" is not a number`:t===`boolean`?`CSV row ${r+1}, column "${n}": "${e}" is not "true" or "false"`:t===`map`?`CSV row ${r+1}, column "${n}": "${e.split(i).map(e=>e.trim()).find(e=>e&&!e.includes(a))??e}" has no "${a}" to split a key from its value`:`CSV row ${r+1}, column "${n}": "${e}" is invalid`})}function Hr(e,t,n={}){let r=n.column??`value`,i=n.listDelimiter??`;`,a=n.mapDelimiter??`=`,o=e=>{throw Error(n.error?.(e)??e)};switch(t){case`string`:return e;case`number`:{let t=Number(e);return Number.isFinite(t)||o(`${r} must be a number`),t}case`boolean`:{let t=e.toLowerCase();if(t===`true`)return!0;if(t===`false`)return!1;if(n.acceptYesNo&&t===`yes`)return!0;if(n.acceptYesNo&&t===`no`)return!1;o(`${r} must be true, false${n.acceptYesNo?`, yes, or no`:``}`)}case`list`:return e.split(i).map(e=>e.trim()).filter(e=>e.length>0);case`map`:{let t={};for(let n of e.split(i).map(e=>e.trim()).filter(e=>e.length>0)){let e=n.indexOf(a);e===-1&&o(`${r} map entry must contain ${a}`),t[n.slice(0,e).trim()]=n.slice(e+a.length).trim()}return t}}}function Ur(e){let t=[],n=[],r=``,i=!1,a=0;for(;a<e.length;){let o=e[a];if(i){if(o===`"`){if(e[a+1]===`"`){r+=`"`,a+=2;continue}i=!1,a++;continue}r+=o,a++}else o===`"`?(i=!0,a++):o===`,`?(n.push(r),r=``,a++):o===`\r`?a++:o===`
1
+ var mw_games=(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=Object.create,n=Object.defineProperty,r=Object.getOwnPropertyDescriptor,i=Object.getOwnPropertyNames,a=Object.getPrototypeOf,o=Object.prototype.hasOwnProperty,s=(e,t,n)=>()=>{if(n)throw n[0];try{return e&&(t=e(e=0)),t}catch(e){throw n=[e],e}},c=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports),l=(e,t)=>{let r={};for(var i in e)n(r,i,{get:e[i],enumerable:!0});return t||n(r,Symbol.toStringTag,{value:`Module`}),r},u=(e,t,a,s)=>{if(t&&typeof t==`object`||typeof t==`function`)for(var c=i(t),l=0,u=c.length,d;l<u;l++)d=c[l],!o.call(e,d)&&d!==a&&n(e,d,{get:(e=>t[e]).bind(null,d),enumerable:!(s=r(t,d))||s.enumerable});return e},d=(e,r,i)=>(i=e==null?{}:t(a(e)),u(r||!e||!e.__esModule||!o.call(e,`default`)?n(i,`default`,{value:e,enumerable:!0}):i,e)),f=`0.40.0`,p=class{constructor(e=!1){this.listeners=[],this.stackMode=e}add(e){this.listeners.includes(e)||(this.stackMode?this.listeners.unshift(e):this.listeners.push(e))}remove(e){let t=this.listeners.indexOf(e);t!==-1&&this.listeners.splice(t,1)}removeAll(){this.listeners.length=0}get size(){return this.listeners.length}dispatch(e){let t=this.listeners.slice();for(let n of t)if(this.listeners.includes(n)&&n(e)===!0)return!0;return!1}},m=class{constructor(){this.onDestroy=new p,this.destroyed=!1}update(e){}resize(e,t){}onSuspend(){}onResume(e){}destroy(){this.destroyed||(this.destroyed=!0,this.onDestroy.dispatch(),this.onDestroy.removeAll(),this.teardown())}teardown(){}get isDestroyed(){return this.destroyed}},h=class{constructor(){this.scenes=[]}get current(){return this.scenes.length===0?null:this.scenes[this.scenes.length-1]}get depth(){return this.scenes.length}replace(e){for(let e of this.scenes)e.destroy();this.scenes=[e],e.create()}push(e){this.current?.onSuspend(),this.scenes.push(e),e.create()}pop(e){if(this.scenes.length<=1)throw Error(`cannot pop the last scene off the stack - switchScene somewhere instead`);this.scenes.pop()?.destroy(),this.current?.onResume(e)}update(e){this.current?.update(e)}resize(e,t){for(let n of this.scenes)n.resize(e,t)}destroy(){for(let e of this.scenes)e.destroy();this.scenes=[]}},g=class{constructor(){this.items=new Map}register(e,t){if(this.items.has(e))throw Error(`"${e}" is already registered`);this.items.set(e,t)}get(e){let t=this.items.get(e);if(t===void 0)throw Error(`no registration named "${e}"`);return t}has(e){return this.items.has(e)}list(){return[...this.items.keys()]}};function _(){let e=1,t=new Map;return{get size(){return t.size},put(n){let r=e++;return t.set(r,n),r},get(e){let n=t.get(e);if(n===void 0)throw Error(`unknown handle ${e}`);return n},drop(e){t.delete(e)},clear(){t.clear()},with(n,r){let i=e++;t.set(i,n);try{return r(i)}finally{t.delete(i)}}}}var v=class{constructor(){this.order=[],this.registry=new g}add(e,t){this.registry.register(e.name,e),this.order.push(e),e.create?.(t)}has(e){return this.registry.has(e)}get(e){return this.registry.get(e)}update(e,t){for(let n of this.order)n.update?.(e,t)}resize(e,t,n){for(let r of this.order)r.resize?.(e,t,n)}onSuspend(e){for(let t of this.order)t.onSuspend?.(e)}onResume(e,t){for(let n of this.order)n.onResume?.(e,t)}destroy(e){for(let t=this.order.length-1;t>=0;t--)this.order[t].destroy?.(e);this.order=[],this.registry=new g}},y={debug:0,info:1,warn:2,error:3},b=class{constructor(e,t={}){if(!e)throw Error(`a logger needs a category`);this.category=e,this.level=t.level??`debug`,this.sink=t.sink??x}setLevel(e){this.level=e}debug(e,t){this.write(`debug`,e,t)}info(e,t){this.write(`info`,e,t)}warn(e,t){this.write(`warn`,e,t)}error(e,t){this.write(`error`,e,t)}write(e,t,n){y[e]<y[this.level]||this.sink({level:e,category:this.category,message:t,data:n,time:Date.now()})}};function x(e){let t=`[${e.category}] ${e.message}`;e.level===`debug`?console.debug(t,e.data??``):e.level===`info`?console.info(t,e.data??``):e.level===`warn`?console.warn(t,e.data??``):console.error(t,e.data??``)}var S=class{constructor(){this.hooks=[]}on(e,t,n){this.hooks.push({event:e,handler:t,source:n})}off(e){this.hooks=this.hooks.filter(t=>t.handler!==e)}offSource(e){this.hooks=this.hooks.filter(t=>t.source!==e)}emit(e,...t){for(let n of[...this.hooks])n.event===e&&n.handler(...t)}get size(){return this.hooks.length}clear(){this.hooks=[]}},C=l({Generator:()=>j,MersenneTwister:()=>he,RandomStreams:()=>ge,active:()=>ee,chance:()=>A,element:()=>re,float:()=>D,int:()=>O,normalRange:()=>k,pop:()=>T,push:()=>w,range:()=>ne,reset:()=>te,shuffle:()=>oe,weighted:()=>ie,weightedKey:()=>ae,withSeed:()=>E});function ee(){return le()}function w(e){let t=new j(e);return ce.push(t),t}function T(){if(ce.length===1)throw Error(`the base random generator cannot be popped`);ce.pop()}function E(e,t){w(e);try{return t()}finally{T()}}function te(){ce.length=0,ce.push(new j)}function D(e,t,n){let r=n??le();return e===void 0?r.float():t===void 0?r.float()*e:e+r.float()*(t-e)}function O(e,t,n){let r=n??le();return t===void 0?r.int(e):e+r.int(t-e)}function ne(e,t,n){return e+(n??le()).int(t-e+1)}function k(e,t,n){let r=n??le();return e+Math.floor((r.float()+r.float())*(t-e+1)/2)}function A(e,t){return(t??le()).float()<e}function re(e,t){return e.length>0?e[(t??le()).int(e.length)]:null}function ie(e,t){let n=0;for(let t of e)n+=t;if(n<=0)return null;let r=(t??le()).float()*n;for(let t=0;t<e.length;t++)if(r-=e[t],r<0)return t;return e.length-1}function ae(e,t){let n=[...e.keys()],r=ie(n.map(t=>e.get(t)??0),t);return r===null?null:n[r]}function oe(e,t){for(let n=e.length-1;n>0;n--){let r=(t??le()).int(n+1);[e[n],e[r]]=[e[r],e[n]]}return e}function se(e){let t=2166136261;for(let n=0;n<e.length;n++)t^=e.charCodeAt(n),t=Math.imul(t,16777619)>>>0;return t>>>0}var j,ce,le,ue,de,fe,pe,me,he,ge,_e=s((()=>{j=class{constructor(e){this.s0=0,this.s1=0,this.s2=0,this.s3=0,this.seed=e===void 0?Math.random()*4294967296>>>0:e>>>0;let t=this.seed,n=()=>{t=t+2654435769>>>0;let e=t;return e=Math.imul(e^e>>>16,569420461),e=Math.imul(e^e>>>15,1935289751),(e^e>>>15)>>>0};this.s0=n(),this.s1=n(),this.s2=n(),this.s3=n(),(this.s0|this.s1|this.s2|this.s3)===0&&(this.s0=1)}nextUint32(){let e=Math.imul(this.rotl(Math.imul(this.s1,5),7),9)>>>0>>>0,t=this.s1<<9>>>0;return this.s2^=this.s0,this.s3^=this.s1,this.s1^=this.s2,this.s0^=this.s3,this.s2^=t,this.s3=this.rotl(this.s3,11),e}float(){return this.nextUint32()/4294967296}int(e){if(e<=0)return 0;let t=4294967296-4294967296%e,n=this.nextUint32();for(;n>=t;)n=this.nextUint32();return n%e}getState(){return[this.s0,this.s1,this.s2,this.s3]}setState(e){[this.s0,this.s1,this.s2,this.s3]=e}rotl(e,t){return(e<<t|e>>>32-t)>>>0}},ce=[new j],le=()=>ce[ce.length-1],ue=624,de=397,fe=2567483615,pe=2147483648,me=2147483647,he=class{constructor(e=5489){this.state=new Uint32Array(ue),this.index=ue,this.seedValue=5489,this.produced=0,this.seed(e)}seed(e){this.seedValue=e>>>0,this.state[0]=this.seedValue;for(let e=1;e<ue;e++){let t=this.state[e-1]^this.state[e-1]>>>30;this.state[e]=Math.imul(1812433253,t)+e>>>0}this.index=ue,this.produced=0}generate(){for(let e=0;e<ue;e++){let t=this.state[e]&pe|this.state[(e+1)%ue]&me,n=this.state[(e+de)%ue]^t>>>1;t&1&&(n^=fe),this.state[e]=n>>>0}this.index=0}nextUint32(){this.index>=ue&&this.generate();let e=this.state[this.index++];return e^=e>>>11,e^=e<<7&2636928640,e^=e<<15&4022730752,e^=e>>>18,this.produced++,e>>>0}float(){return this.nextUint32()/4294967296}int(e){if(e<=0)return 0;let t=4294967296-4294967296%e,n=this.nextUint32();for(;n>=t;)n=this.nextUint32();return n%e}discard(e){for(let t=0;t<e;t++)this.nextUint32()}get discardCount(){return this.produced}getState(){return{seed:this.seedValue,state:[...this.state],index:this.index,discard:this.produced}}setState(e){this.seedValue=e.seed>>>0;for(let t=0;t<ue;t++)this.state[t]=(e.state[t]??0)>>>0;this.index=e.index,this.produced=e.discard}},ge=class{constructor(e=0){this.streams=new Map,this.baseSeed=e>>>0}stream(e){let t=this.streams.get(e);if(!t){let n=(this.baseSeed^se(e))>>>0;t={seed:n,engine:new he(n)},this.streams.set(e,t)}return t.engine}seedOf(e){return this.streams.get(e)?.seed}reseed(e){this.baseSeed=e>>>0,this.streams.clear()}names(){return[...this.streams.keys()]}getState(){let e={};for(let[t,n]of this.streams)e[t]=n.engine.getState();return e}setState(e){for(let[t,n]of Object.entries(e)){let e=new he(n.seed);e.setState(n),this.streams.set(t,{seed:n.seed,engine:e})}}}})),ve=l({DEFAULT_BINDINGS:()=>je,actionsForKey:()=>Pe,attach:()=>gt,attachPointer:()=>ht,attachSwipe:()=>mt,bind:()=>Me,bindAxis:()=>at,bindButton:()=>it,bindTouch:()=>lt,detach:()=>_t,dispatchAction:()=>Te,dispatchComposition:()=>Ee,dispatchText:()=>we,endFrame:()=>Ke,exportBindings:()=>Le,gamepadAxisCode:()=>rt,gamepadButtonCode:()=>nt,importBindings:()=>Re,isDown:()=>Ue,justPressed:()=>We,justReleased:()=>Ge,keysFor:()=>Ie,onAction:()=>P,onComposition:()=>F,onKey:()=>xe,onText:()=>Se,onWheel:()=>De,pollGamepads:()=>ot,pressTouch:()=>ut,releaseTouch:()=>dt,resetBindings:()=>ze,rumble:()=>st,textFromKey:()=>Ce,touchCode:()=>ct,unbind:()=>Fe,wheelActionFor:()=>Oe}),M=new Map,N=new Set,ye=new Set,be=new Set,P=new p(!0),xe=new p(!0),Se=new p(!0),F=new p(!0);function Ce(e){return e.isComposing||e.ctrlKey||e.metaKey||e.altKey?null:e.key.length===1?e.key:null}function we(e){e.length>0&&Se.dispatch(e)}function Te(e){P.dispatch(e)}function Ee(e){F.dispatch({phase:e.phase,text:e.text})}var De=new p(!0);function Oe(e){return e.ctrlKey||e.metaKey?`zoom`:e.shiftKey?`scrollHorizontal`:`scroll`}function ke(e){let t=Oe(e);t===`zoom`&&e.preventDefault();let n=t===`scrollHorizontal`&&e.deltaX!==0?e.deltaX:e.deltaY;De.dispatch({action:t,delta:n})}var Ae=!1,je={up:[`ArrowUp`,`KeyW`,`Numpad8`],down:[`ArrowDown`,`KeyS`,`Numpad2`],left:[`ArrowLeft`,`KeyA`,`Numpad4`],right:[`ArrowRight`,`KeyD`,`Numpad6`],upLeft:[`Numpad7`],upRight:[`Numpad9`],downLeft:[`Numpad1`],downRight:[`Numpad3`],wait:[`Numpad5`,`Period`],confirm:[`Enter`,`Space`,`NumpadEnter`],cancel:[`Escape`,`Backspace`],menu:[`KeyI`,`Tab`]};function Me(e,t){M.set(e,new Set(t))}function Ne(e,t){let n=M.get(e)??new Set;for(let e of t)n.add(e);M.set(e,n)}function Pe(e){return Be(e)}function Fe(e){M.delete(e)}function Ie(e){return[...M.get(e)??[]]}function Le(){let e={};for(let[t,n]of M)e[t]=[...n];return e}function Re(e){M.clear();for(let[t,n]of Object.entries(e))Me(t,n)}function ze(){Re(je)}ze();function Be(e){let t=[];for(let[n,r]of M)r.has(e)&&t.push(n);return t}function Ve(e){if(!N.has(e)){N.add(e);for(let t of Be(e))ye.add(t),P.dispatch(t)}}function He(e){if(N.has(e)){N.delete(e);for(let t of Be(e))be.add(t)}}function Ue(e){let t=M.get(e);if(!t)return!1;for(let e of t)if(N.has(e))return!0;return!1}function We(e){return ye.has(e)}function Ge(e){return be.has(e)}function Ke(){ye.clear(),be.clear()}function qe(e){let t=Ce(e);t!==null&&we(t)}function Je(e){e.repeat?(xe.dispatch(e),qe(e)):(Ve(e.code),xe.dispatch(e),qe(e))}function Ye(e){He(e.code)}function Xe(e){Ee({phase:`start`,text:e.data??``})}function Ze(e){Ee({phase:`update`,text:e.data??``})}function Qe(e){Ee({phase:`end`,text:e.data??``})}function $e(){N.clear(),ye.clear(),be.clear()}var et=.5,tt=new Set;function nt(e,t){return`Gamepad${e}Button${t}`}function rt(e,t,n){return`Gamepad${e}Axis${t}${n>0?`+`:`-`}`}function it(e,t,n){Ne(e,n.map(e=>nt(t,e)))}function at(e,t,n,r){Ne(e,[rt(t,n,r)])}function ot(e=typeof navigator?.getGamepads==`function`?navigator.getGamepads():[]){tt.clear();for(let t of e)if(t){for(let e=0;e<t.buttons.length;e++)t.buttons[e].pressed&&tt.add(nt(t.index,e));for(let e=0;e<t.axes.length;e++){let n=t.axes[e];n>=et?tt.add(rt(t.index,e,1)):n<=-.5&&tt.add(rt(t.index,e,-1))}}for(let e of tt)Ve(e);for(let e of N)!tt.has(e)&&e.startsWith(`Gamepad`)&&He(e)}function st(e,t,n=typeof navigator?.getGamepads==`function`?navigator.getGamepads():[]){(n[e]?.vibrationActuator)?.playEffect(`dual-rumble`,{duration:t.duration,weakMagnitude:t.weakMagnitude??1,strongMagnitude:t.strongMagnitude??1})}function ct(e){return`Touch:${e}`}function lt(e,t=e){Ne(e,[ct(t)])}function ut(e){Ve(ct(e))}function dt(e){He(ct(e))}var ft={up:`up`,down:`down`,left:`left`,right:`right`,upLeft:`upLeft`,upRight:`upRight`,downLeft:`downLeft`,downRight:`downRight`},pt=[`right`,`downRight`,`down`,`downLeft`,`left`,`upLeft`,`up`,`upRight`];function mt(e,t={}){let n={...ft,...t.actions},r=t.minDistance??24;for(let e of[...pt,`tap`]){let t=n[e];t&&lt(t,`swipe:${e}`)}let i=!1,a=-1,o=0,s=0;function c(e){ut(`swipe:${e}`),dt(`swipe:${e}`)}function l(e){i=!0,a=e.pointerId,o=e.clientX,s=e.clientY}function u(e){if(!i||e.pointerId!==a)return;i=!1;let t=e.clientX-o,l=e.clientY-s;if(Math.hypot(t,l)<r){n.tap&&c(`tap`);return}let u=pt[(Math.round(Math.atan2(l,t)/(Math.PI/4))%8+8)%8];n[u]&&c(u)}return e.addEventListener(`pointerdown`,l),e.addEventListener(`pointerup`,u),e.addEventListener(`pointercancel`,u),()=>{e.removeEventListener(`pointerdown`,l),e.removeEventListener(`pointerup`,u),e.removeEventListener(`pointercancel`,u)}}function ht(e,t={}){let n=t.dragThreshold??4,r=t.toWorld??((e,t)=>({x:e,y:t})),i=new p,a=new p,o=new p,s=new p,c=-1,l=!1,u=!1,d=0,f=0,m=null;function h(t){let n=e.getBoundingClientRect?.()??{left:0,top:0},i=t.clientX-n.left,a=t.clientY-n.top,o=r(i,a);return{x:i,y:a,worldX:o.x,worldY:o.y,dx:l?i-d:0,dy:l?a-f:0,pointerId:t.pointerId,pressed:l}}function g(t){if(l)return;l=!0,u=!1,c=t.pointerId;let n=e.getBoundingClientRect?.()??{left:0,top:0};d=t.clientX-n.left,f=t.clientY-n.top,m=h(t),i.dispatch(m)}function _(e){l&&e.pointerId!==c||(m=h(e),a.dispatch(m),l&&(!u&&Math.hypot(m.dx,m.dy)>=n&&(u=!0),u&&o.dispatch(m)))}function v(e){if(!l||e.pointerId!==c)return;let t=u,n=h(e);l=!1,u=!1,m={...n,pressed:!1,dx:0,dy:0},s.dispatch({...n,pressed:!1,dragged:t})}return e.addEventListener(`pointerdown`,g),e.addEventListener(`pointermove`,_),e.addEventListener(`pointerup`,v),e.addEventListener(`pointercancel`,v),{down:i,move:a,drag:o,up:s,get current(){return m},detach(){e.removeEventListener(`pointerdown`,g),e.removeEventListener(`pointermove`,_),e.removeEventListener(`pointerup`,v),e.removeEventListener(`pointercancel`,v)}}}function gt(e=window){Ae||(Ae=!0,e.addEventListener(`keydown`,Je),e.addEventListener(`keyup`,Ye),e.addEventListener(`compositionstart`,Xe),e.addEventListener(`compositionupdate`,Ze),e.addEventListener(`compositionend`,Qe),window.addEventListener(`blur`,$e),e.addEventListener(`wheel`,ke,{passive:!1}))}function _t(e=window){Ae&&(Ae=!1,e.removeEventListener(`keydown`,Je),e.removeEventListener(`keyup`,Ye),e.removeEventListener(`compositionstart`,Xe),e.removeEventListener(`compositionupdate`,Ze),e.removeEventListener(`compositionend`,Qe),e.removeEventListener(`wheel`,ke),window.removeEventListener(`blur`,$e),$e())}var vt=class{constructor(e,t={}){if(!e)throw Error(`a player needs a non-empty id`);this.id=e,this.padIndex=t.padIndex}scoped(e){return`${this.id}:${e}`}bind(e,t){Me(this.scoped(e),t)}bindButton(e,t){if(this.padIndex===void 0)throw Error(`player "${this.id}" has no padIndex to bind a gamepad button to`);it(this.scoped(e),this.padIndex,t)}bindAxis(e,t,n){if(this.padIndex===void 0)throw Error(`player "${this.id}" has no padIndex to bind a gamepad axis to`);at(this.scoped(e),this.padIndex,t,n)}bindTouch(e,t=e){lt(this.scoped(e),`${this.id}:${t}`)}pressTouch(e){ut(`${this.id}:${e}`)}releaseTouch(e){dt(`${this.id}:${e}`)}isDown(e){return Ue(this.scoped(e))}justPressed(e){return We(this.scoped(e))}justReleased(e){return Ge(this.scoped(e))}keysFor(e){return Ie(this.scoped(e))}},yt=25214903917n,bt=11n,xt=(1n<<48n)-1n,St=class{constructor(e=0,t={}){this.state=0n,this.onDraw=t.onDraw,this.setSeed(e)}setSeed(e){this.state=(BigInt.asIntN(64,BigInt(e))^yt)&xt}next(e){if(!Number.isInteger(e)||e<1||e>32)throw Error(`next needs 1 to 32 bits, got ${e}`);this.state=this.state*yt+bt&xt;let t=Number(this.state>>BigInt(48-e)),n=e===32?t|0:t;return this.onDraw?.({bits:e,value:n}),n}nextInt(e){if(e===void 0)return this.next(32);if(!Number.isInteger(e)||e<=0||e>2147483647)throw Error(`nextInt needs a bound from 1 to 2147483647, got ${e}`);if((e&-e)===e)return Math.floor(e*this.next(31)/2147483648);let t,n;do t=this.next(31),n=t%e;while(t-n+(e-1)>2147483647);return n}nextLong(){let e=BigInt(this.next(32)),t=BigInt(this.next(32));return BigInt.asIntN(64,(e<<32n)+t)}nextDouble(){return(this.next(26)*134217728+this.next(27))/2**53}nextFloat(){return this.next(24)/16777216}nextBoolean(){return this.next(1)!==0}};_e();function Ct(e,t){if(!t)throw Error(`scramble needs a non-empty key`);let n=new TextEncoder().encode(e),r=new TextEncoder().encode(t),i=new Uint8Array(n.length);for(let e=0;e<n.length;e++)i[e]=n[e]^r[e%r.length];return Tt(i)}function wt(e,t){if(!t)throw Error(`scramble needs a non-empty key`);let n=Et(e),r=new TextEncoder().encode(t),i=new Uint8Array(n.length);for(let e=0;e<n.length;e++)i[e]=n[e]^r[e%r.length];return new TextDecoder(`utf-8`,{fatal:!0}).decode(i)}function Tt(e){let t=``;for(let n of e)t+=String.fromCharCode(n);return btoa(t)}function Et(e){let t=atob(e),n=new Uint8Array(t.length);for(let e=0;e<t.length;e++)n[e]=t.charCodeAt(e);return n}var Dt=10485760;function Ot(e,t={}){let n=t.maxBytes??Dt,r=typeof e==`string`?new TextEncoder().encode(e).length:e.length;if(r>n)throw Error(`inbound data exceeds the ${n}-byte limit (${r} bytes)`)}function kt(e){for(let t=0;t<e.length;t++){let n=e.charCodeAt(t);if(n!==9&&n!==10&&n!==13&&(n<=31||n===127))throw Error(`inbound data contains a control character (0x${n.toString(16).padStart(2,`0`)}) at index ${t}`)}}function At(e,t={}){return Ot(e,t),kt(e),e}var jt=new Set([`__proto__`,`constructor`,`prototype`]);function Mt(e,t=`$`){if(Array.isArray(e))e.forEach((e,n)=>Mt(e,`${t}[${n}]`));else if(typeof e==`object`&&e)for(let n of Object.keys(e)){if(jt.has(n))throw Error(`${t} contains a forbidden key "${n}"`);Mt(e[n],`${t}.${n}`)}}function Nt(e,t,n=`$`){if(e===void 0){if(t.optional)return;throw Error(`${n} is required`)}switch(t.type){case`string`:if(typeof e!=`string`)throw Error(`${n} must be a string`);return;case`number`:if(typeof e!=`number`||!Number.isFinite(e))throw Error(`${n} must be a finite number`);if(t.min!==void 0&&e<t.min)throw Error(`${n} must be >= ${t.min}`);if(t.max!==void 0&&e>t.max)throw Error(`${n} must be <= ${t.max}`);return;case`boolean`:if(typeof e!=`boolean`)throw Error(`${n} must be a boolean`);return;case`array`:if(!Array.isArray(e))throw Error(`${n} must be an array`);if(t.maxLength!==void 0&&e.length>t.maxLength)throw Error(`${n} exceeds its maximum length of ${t.maxLength}`);e.forEach((e,r)=>Nt(e,t.items,`${n}[${r}]`));return;case`object`:if(typeof e!=`object`||!e||Array.isArray(e))throw Error(`${n} must be an object`);for(let t of Object.keys(e))if(jt.has(t))throw Error(`${n} contains a forbidden key "${t}"`);for(let[r,i]of Object.entries(t.fields))Nt(e[r],i,`${n}.${r}`);return}}function Pt(e,t={}){let n=t.label??`inbound data`;At(e,t);try{return JSON.parse(e,(e,t)=>{if(jt.has(e))throw new Ft(e);return t})}catch(e){throw e instanceof Ft?Error(`${n} contains a forbidden key "${e.key}"`):Error(`${n} is not valid JSON (${e instanceof Error?e.message:String(e)})`)}}var Ft=class{constructor(e){this.key=e}};function It(e,t){let n=Pt(e,{label:t}),r=n?.meta;if(typeof n!=`object`||!n||Array.isArray(n)||!(`state`in n))throw Error(`${t} is not save data (expected { meta, state })`);if(typeof r!=`object`||!r||!Number.isSafeInteger(r.version)||r.version<0)throw Error(`${t} has no valid meta.version`);if(typeof r.savedAt!=`number`||!Number.isFinite(r.savedAt))throw Error(`${t} has no valid meta.savedAt`);return n}var Lt=class{constructor(){this.data=new Map}read(e){return this.data.get(e)??null}write(e,t){this.data.set(e,t)}remove(e){this.data.delete(e)}keys(){return[...this.data.keys()]}};function Rt(){return typeof localStorage>`u`?new Lt:{read:e=>localStorage.getItem(e),write:(e,t)=>localStorage.setItem(e,t),remove:e=>localStorage.removeItem(e),keys:()=>Object.keys(localStorage)}}var zt=class{constructor(e){this.namespace=e.namespace,this.version=e.version,this.migrations=e.migrations??{},this.storage=e.storage??Rt()}key(e){return`mwg-save:${this.namespace}:${e}`}save(e,t,n){let r={meta:{version:this.version,savedAt:Date.now(),preview:n},state:t};this.storage.write(this.key(e),JSON.stringify(r))}load(e){let t=this.storage.read(this.key(e));if(!t)return null;try{let n=It(t,`save slot "${e}"`),r=n.state;for(let e=n.meta.version;e<this.version;e++)r=(this.migrations[e]??(e=>e))(r);return{meta:{...n.meta,version:this.version},state:r}}catch{return null}}importExternal(e,t,n,r){Ot(t);let i=n(t);for(let e=0;e<this.version;e++)i=(this.migrations[e]??(e=>e))(i);let a={meta:{version:this.version,savedAt:Date.now(),preview:r},state:i};this.storage.write(this.key(e),JSON.stringify(a))}delete(e){this.storage.remove(this.key(e))}exportSlot(e,t){let n=this.storage.read(this.key(e));return n===null?null:t?Ct(n,t):n}importSlot(e,t,n){Ot(t);let r=It(n?wt(t,n):t,`SaveSystem.importSlot: payload`),i=r.state;for(let e=r.meta.version;e<this.version;e++)i=(this.migrations[e]??(e=>e))(i);let a={meta:{...r.meta,version:this.version},state:i};this.storage.write(this.key(e),JSON.stringify(a))}list(){let e=`mwg-save:${this.namespace}:`,t=[];for(let n of this.storage.keys()){if(!n.startsWith(e))continue;let r=this.storage.read(n);if(r)try{t.push({slot:n.slice(e.length),meta:It(r,n).meta})}catch{}}return t}},Bt=class{constructor(e,t){this.storage=e,this.key=t}read(e){let t=this.storage.read(this.key);return t?Pt(t,{label:`stored value "${this.key}"`}):e}write(e){this.storage.write(this.key,JSON.stringify(e))}remove(){this.storage.remove(this.key)}};function Vt(e,t=`value`){return Ht(e,t,new Set)}function Ht(e,t,n){if(typeof e==`function`||typeof e==`symbol`)return t;if(typeof e!=`object`||!e||n.has(e)||(n.add(e),Ut(e)))return null;let r=[];if(e instanceof Map)for(let[n,i]of e)r.push([`${t}.get(${JSON.stringify(n)??String(n)})`,i]);else if(e instanceof Set)for(let n of e)r.push([`${t}.item`,n]);else if(Array.isArray(e))e.forEach((e,n)=>r.push([`${t}[${n}]`,e]));else for(let[n,i]of Object.entries(e))r.push([`${t}.${n}`,i]);for(let[e,t]of r){let r=Ht(t,e,n);if(r)return r}return t}function Ut(e){try{return structuredClone(e),!0}catch{return!1}}function Wt(e,t){try{return structuredClone(e)}catch(n){let r=Vt(e,t)??t;throw TypeError(`${r} cannot be structured-cloned (${n instanceof Error?n.message:String(n)}). Journals, snapshots and undo checkpoints copy their contents with structuredClone, so they must hold plain data: refer to a live object (an actor, anything carrying callbacks such as a ReactionTable) by its id and resolve it inside the rule.`,{cause:n})}}var Gt=class{constructor(e,t={}){this._state=Wt(e,`initial state`),this.version=t.version??1,this.migrations=t.migrations??{},this.extensions=t.extensions??new Kt}get state(){return structuredClone(this._state)}set(e){this._state=Wt(e,`state`)}update(e){let t=e(this.state);return this.set(t),this.state}snapshot(){let e=this.extensions.snapshot();return{version:this.version,state:this.state,extensions:e.extensions,extensionVersions:e.versions}}restore(e,t={}){if(!Number.isSafeInteger(e.version)||e.version<1)throw Error(`canonical state snapshot version must be a positive integer`);if(e.version>this.version)throw Error(`canonical state snapshot version ${e.version} is newer than ${this.version}`);let n=this.snapshot();try{let n=Wt(e.state,`snapshot.state`);for(let t=e.version;t<this.version;t++){let e=this.migrations[t+1];if(!e)throw Error(`missing canonical state migration to version ${t+1}`);n=e(n)}return this._state=Wt(n,`migrated state`),this.extensions.restore({extensions:e.extensions,versions:e.extensionVersions},t)}catch(e){throw this._state=structuredClone(n.state),this.extensions.restore({extensions:n.extensions,versions:n.extensionVersions},{missing:`keep`}),e}}transaction(e){let t=this.snapshot();try{return e(this)}catch(e){throw this.restore(t),e}}},Kt=class{constructor(){this.extensions=new Map}register(e){if(!/^[A-Za-z_][A-Za-z0-9_.-]*$/.test(e.id))throw Error(`invalid state extension id: ${e.id}`);if(this.extensions.has(e.id))throw Error(`state extension already registered: ${e.id}`);return this.extensions.set(e.id,{id:e.id,capture:()=>e.capture(),restore:t=>e.restore(t),version:e.version??1,migrations:e.migrations,reset:e.reset,remove:e.remove}),()=>this.extensions.delete(e.id)}snapshot(){let e={};for(let[t,n]of this.extensions)e[t]=Wt(n.capture(),`extension ${t}`);let t={};for(let[e,n]of this.extensions)t[e]=n.version??1;return{extensions:e,versions:t}}restore(e,t={}){let n=this.snapshot(),r=[];try{for(let[n,i]of this.extensions){if(!(n in e.extensions)){t.missing===`reset`?(i.reset&&i.reset(),r.push({extension:n,from:0,to:i.version??1,status:`reset`})):t.missing===`remove`&&(i.remove&&i.remove(),r.push({extension:n,from:0,to:i.version??1,status:`removed`}));continue}let a=e.versions?.[n]??1,o=i.version??1,s=Wt(e.extensions[n],`snapshot.extensions.${n}`);for(let e=a;e<o;e++){let t=i.migrations?.[e+1];if(!t)throw Error(`missing state migration for ${n} to version ${e+1}`);s=t(s)}i.restore(Wt(s,`migrated extension ${n}`)),r.push({extension:n,from:a,to:o,status:a===o?`unchanged`:`migrated`})}}catch(e){for(let[e,t]of this.extensions)e in n.extensions&&t.restore(structuredClone(n.extensions[e]));throw e}for(let e of r)t.onDiagnostic?.(e);return r}transaction(e){let t=this.snapshot();try{return e()}catch(e){throw this.restore(t),e}}},qt=new Set([`localhost`,`127.0.0.1`,`[::1]`]);function Jt(e,t,n=!1){if(n||!/^[a-z][a-z0-9+.-]*:/i.test(e))return;let r=new URL(e);if(r.protocol!==`https:`&&r.protocol!==`wss:`&&(r.protocol!==`http:`&&r.protocol!==`ws:`||!qt.has(r.hostname)))throw Error(`${t} url must use https: or wss: (got ${r.protocol}//${r.host}); pass allowInsecure: true to accept it`)}var Yt=class{constructor(e,t){if(!e.endpoint)throw Error(`${t} endpoint is required`);if(Jt(e.endpoint,`${t} endpoint`,e.allowInsecure),this.endpoint=e.endpoint,this.label=t,this.maxResponseBytes=e.maxResponseBytes??10485760,this.timeoutMs=e.timeoutMs??1e4,!(this.timeoutMs>0))throw Error(`${t} timeout must be positive`);if(this.fetchFn=e.fetch??globalThis.fetch,!this.fetchFn)throw Error(`fetch is unavailable; provide ${t}Options.fetch`)}async withTimeout(e){let t=new AbortController,n=setTimeout(()=>t.abort(),this.timeoutMs);try{return await e(t.signal)}finally{clearTimeout(n)}}async readText(e){let t=this.maxResponseBytes,n=Number(e.headers?.get?.(`content-length`));if(n>t)throw Error(`${this.label} response exceeds the ${t}-byte limit (${n} bytes)`);if(!e.body){let n=await e.text();return Ot(n,{maxBytes:t}),n}let r=e.body.getReader(),i=[],a=0;for(;;){let{done:e,value:n}=await r.read();if(e)break;if(a+=n.byteLength,a>t)throw await r.cancel(),Error(`${this.label} response exceeds the ${t}-byte limit`);i.push(n)}let o=new Uint8Array(a),s=0;for(let e of i)o.set(e,s),s+=e.byteLength;return new TextDecoder().decode(o)}async readJson(e,t){return Pt(await this.readText(e),{maxBytes:this.maxResponseBytes,label:`${this.label} ${t}`})}},Xt=class extends Yt{constructor(e){super(e,`save sync`)}async upload(e,t){if(!e)throw Error(`save sync slot name is required`);if(!t)throw Error(`save sync payload cannot be empty`);return this.withTimeout(async n=>{let r=await this.fetchFn(this.slotUrl(e),{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({payload:t}),signal:n});if(!r.ok)throw Error(`save upload failed with HTTP ${r.status}`);return{ok:!0,status:r.status}})}async download(e){if(!e)throw Error(`save sync slot name is required`);return this.withTimeout(async t=>{let n=await this.fetchFn(this.slotUrl(e),{signal:t});if(!n.ok)throw Error(`save download failed with HTTP ${n.status}`);let r=await this.readJson(n,`download`);if(typeof r?.payload!=`string`)throw Error(`save download response missing a string payload`);return r.payload})}async list(){return this.withTimeout(async e=>{let t=await this.fetchFn(this.endpoint,{signal:e});if(!t.ok)throw Error(`save slot list failed with HTTP ${t.status}`);let n=await this.readJson(t,`slot list`);if(!Array.isArray(n?.slots)||!n.slots.every(e=>typeof e==`string`))throw Error(`save slot list response missing a string array of slots`);return n.slots})}slotUrl(e){let t=this.endpoint.includes(`?`)?`&`:`?`;return`${this.endpoint}${t}slot=${encodeURIComponent(e)}`}},Zt=1,Qt=class{constructor(e){if(this.onWelcome=new p,this.onTick=new p,this.onReject=new p,this.onDesync=new p,this.onClose=new p,this.onProtocolError=new p,this.socket=null,this._id=null,!e.url)throw Error(`lockstep client url is required`);Jt(e.url,`lockstep client`,e.allowInsecure),this.url=e.url,this.createSocket=e.create??(e=>new WebSocket(e)),this.validateInput=e.validateInput,this.maxMessageBytes=e.maxMessageBytes??1048576}get id(){return this._id}get connected(){return this.socket!==null&&this.socket.readyState===Zt}connect(){if(this.socket)throw Error(`lockstep client is already connected`);let e=this.createSocket(this.url);e.onmessage=e=>this.handleMessage(e.data),e.onclose=()=>{this.socket=null,this._id=null,this.onClose.dispatch()},this.socket=e}submitInput(e,t){if(!this.connected)return;let n=this.validateInput?.(e);n===!1||typeof n==`string`?this.onReject.dispatch({reason:n===!1?`input rejected by client validation`:n}):this.socket?.send(JSON.stringify({type:`input`,payload:e,...t===void 0?{}:{checksum:t}}))}close(){this.socket?.close()}handleMessage(e){let t;try{if(typeof e!=`string`)throw Error(`lockstep message is not text`);t=en(Pt(e,{maxBytes:this.maxMessageBytes,label:`lockstep message`}))}catch(e){this.onProtocolError.dispatch({reason:e instanceof Error?e.message:String(e)});return}if(t.type===`welcome`)this._id=t.id,this.onWelcome.dispatch({id:t.id,...t.seed===void 0?{}:{seed:t.seed},...t.initialState===void 0?{}:{initialState:t.initialState}});else if(t.type===`tick`){let e={tick:t.tick,inputs:t.inputs,...t.checksums===void 0?{}:{checksums:t.checksums}};if(this.onTick.dispatch(e),t.checksums){let e=Object.values(t.checksums);e.some(t=>t!==e[0])&&this.onDesync.dispatch({tick:t.tick,checksums:t.checksums})}}else this.onReject.dispatch({reason:t.reason??`input rejected by server`})}},$t=e=>typeof e==`object`&&!!e&&!Array.isArray(e);function en(e){if(!$t(e)||typeof e.type!=`string`)throw Error(`lockstep message has no type`);switch(e.type){case`welcome`:if(typeof e.id!=`string`||e.id===``)throw Error(`welcome needs a string id`);if(e.seed!==void 0&&!Number.isSafeInteger(e.seed))throw Error(`welcome seed must be an integer`);return e;case`tick`:if(!Number.isSafeInteger(e.tick)||e.tick<0)throw Error(`tick needs a non-negative integer tick`);if(!$t(e.inputs))throw Error(`tick needs an inputs object`);if(e.checksums!==void 0&&(!$t(e.checksums)||Object.values(e.checksums).some(e=>typeof e!=`number`)))throw Error(`tick checksums must map ids to numbers`);return e;case`rejected`:if(e.reason!==void 0&&typeof e.reason!=`string`)throw Error(`rejected reason must be a string`);return e;default:throw Error(`unknown lockstep message type "${e.type}"`)}}var tn=class{constructor(e){this.store=new Bt(e.storage??Rt(),`mwg-runs:${e.namespace}`),this.limit=e.limit}readAll(){return this.store.read([])}record(e){let t=this.readAll(),n={id:`${Date.now()}-${t.length}`,endedAt:Date.now(),summary:e};return t.push(n),this.limit!==void 0&&t.length>this.limit&&t.splice(0,t.length-this.limit),this.store.write(t),n}all(){return this.readAll()}ranked(e,t=`desc`){let n=this.readAll(),r=t===`desc`?-1:1;return n.sort((t,n)=>r*(e(t.summary)-e(n.summary)))}clear(){this.store.remove()}},nn=class{constructor(e){this.store=new Bt(e.storage??Rt(),`mwg-stats:${e.namespace}`),this.initial=e.initial,this.combine=e.combine}get(){return this.store.read(this.initial)}record(e){let t=this.combine(this.get(),e);return this.store.write(t),t}reset(){this.store.remove()}},rn=class extends Yt{constructor(e){super(e,`telemetry`),this.consented=!1,this.maxStringLength=e.maxStringLength??256,this.maxProperties=e.maxProperties??32,this.allowed=e.allowedProperties?new Set(e.allowedProperties):null}get hasConsent(){return this.consented}setConsent(e){this.consented=e}async send(e){if(!e.name)throw Error(`telemetry event needs a name`);return this.consented?this.withTimeout(async t=>{let n=await this.fetchFn(this.endpoint,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify(this.bounded(e)),signal:t});if(!n.ok)throw Error(`telemetry request failed with HTTP ${n.status}`);return{ok:!0,status:n.status}}):null}bounded(e){let t=e=>e.length>this.maxStringLength?e.slice(0,this.maxStringLength):e;if(!e.properties)return{name:t(e.name)};let n={},r=0;for(let[i,a]of Object.entries(e.properties))if(!this.allowed||this.allowed.has(i)){if(r++>=this.maxProperties)break;n[t(i)]=typeof a==`string`?t(a):a}return{name:t(e.name),properties:n}}},an=class extends Yt{constructor(e){super(e,`news`)}async fetchItems(){return this.withTimeout(async e=>{let t=await this.fetchFn(this.endpoint,{signal:e});if(!t.ok)throw Error(`news request failed with HTTP ${t.status}`);let n=await this.readJson(t,`response`);if(!Array.isArray(n))throw Error(`news response was not an array`);return n.map((e,t)=>on(e,t))})}};function on(e,t){if(typeof e!=`object`||!e)throw Error(`news item ${t} was not an object`);let n=e;if(typeof n.id!=`string`||!n.id)throw Error(`news item ${t} is missing a string id`);if(typeof n.title!=`string`)throw Error(`news item ${t} is missing a string title`);if(typeof n.body!=`string`)throw Error(`news item ${t} is missing a string body`);return{id:n.id,title:n.title,body:n.body,publishedAt:typeof n.publishedAt==`number`?n.publishedAt:void 0}}var sn=class{constructor(e){this.store=new Bt(e.storage??Rt(),`mwg-news-seen:${e.namespace}`)}readSeen(){return new Set(this.store.read([]))}isSeen(e){return this.readSeen().has(e)}markSeen(e){let t=this.readSeen();t.add(e),this.store.write([...t])}unseen(e){let t=this.readSeen();return e.filter(e=>!t.has(e.id))}},cn=class{constructor(e,t={}){if(!e)throw Error(`a collection needs a name`);this.prefix=`mwg-db:${t.namespace??`default`}:${e}:`,this.storage=t.storage??Rt()}get size(){return this.keys().length}all(){return this.keys().map(e=>this.read(e))}get(e){let t=this.storage.read(this.prefix+e);return t===null?void 0:this.parse(t,this.prefix+e)}put(e){if(typeof e!=`object`||!e||Array.isArray(e)||typeof e.id!=`string`||e.id===``)throw Error(`a record must be an object with a string id`);this.storage.write(this.prefix+e.id,JSON.stringify(e))}remove(e){this.storage.remove(this.prefix+e)}where(e){return this.all().filter(e)}clear(){for(let e of this.keys())this.storage.remove(e)}keys(){return this.storage.keys().filter(e=>e.startsWith(this.prefix))}read(e){return this.parse(this.storage.read(e),e)}parse(e,t){return Pt(e,{label:`record "${t}"`})}},ln=class{constructor(e,t,n){if(this.recorded=[],this.fromFrame=n?.fromFrame??0,!Number.isInteger(this.fromFrame)||this.fromFrame<0)throw RangeError(`a resumed recorder needs a non-negative fromFrame`);this.frameStamp=this.fromFrame,this.actions=e,this.frames=t,this.onAction=e=>{this.recorded.push({frame:this.frameStamp,action:e})},this.onFrame=()=>{this.frameStamp++},e.add(this.onAction),t.add(this.onFrame)}get events(){return this.recorded}get frame(){return this.frameStamp}toJSON(){return this.recorded.map(e=>({frame:e.frame,action:e.action}))}stop(){this.actions.remove(this.onAction),this.frames.remove(this.onFrame)}},un=class{constructor(e,t,n,r){if(this.events=e,this.dispatch=t,this.frames=n,this.fromFrame=r?.fromFrame??0,!Number.isInteger(this.fromFrame)||this.fromFrame<0)throw RangeError(`a resumed player needs a non-negative fromFrame`);this.index=this.events.findIndex(e=>e.frame>=this.fromFrame),this.index<0&&(this.index=this.events.length),this.frame=this.fromFrame,this.onFrame=()=>this.pump(),n.add(this.onFrame)}get done(){return this.index>=this.events.length}stop(){this.frames.remove(this.onFrame)}pump(){for(;this.index<this.events.length&&this.events[this.index].frame<=this.frame;)this.dispatch(this.events[this.index].action),this.index++;this.frame++}};function dn(e){return JSON.stringify(e)}function fn(e){return pn(JSON.parse(e))}function pn(e){if(!Array.isArray(e))throw Error(`a replay must be an array of {frame, action}`);return e.map(e=>{if(typeof e!=`object`||!e||!Number.isInteger(e.frame)||e.frame<0||typeof e.action!=`string`)throw Error(`a replay entry must be {frame: a non-negative integer, action: a string}`);return{frame:e.frame,action:e.action}})}function mn(e,t){if(typeof t!=`string`||!t.length)throw TypeError(`a replay file needs a non-empty ${e}`);return t}function hn(e){let t=e;if(!Number.isInteger(t)||t<0||t>4294967295)throw RangeError(`a replay seed must be uint32`);return t}function gn(e,t){let n={format:`mwg-replay`,version:1,framework:mn(`framework`,t.framework),game:mn(`game`,t.game),events:pn(e)};if(t.seed!==void 0&&(n.seed=hn(t.seed)),t.recordedAt!==void 0){if(typeof t.recordedAt!=`number`||!Number.isFinite(t.recordedAt))throw TypeError(`recordedAt must be a finite number`);n.recordedAt=t.recordedAt}return JSON.stringify(n)}function _n(e,t){let n=Pt(e,{label:`replay file`});if(typeof n!=`object`||!n||Array.isArray(n))throw Error(`a replay file must be a JSON object`);if(n.format!==`mwg-replay`)throw Error(`not a replay file: expected format "mwg-replay", found ${JSON.stringify(n.format)}`);if(n.version!==1)throw Error(`replay file version ${JSON.stringify(n.version)} is not supported (this build reads 1)`);if(n.game!==t.game)throw Error(`a replay of ${JSON.stringify(n.game)} cannot be imported into ${JSON.stringify(t.game)}`);if(n.framework!==t.framework)throw Error(`a replay recorded against mwg ${JSON.stringify(n.framework)} cannot be imported into ${JSON.stringify(t.framework)}`);let r=pn(n.events);return{format:`mwg-replay`,version:1,framework:n.framework,game:n.game,...n.seed===void 0?{}:{seed:hn(n.seed)},...n.recordedAt===void 0?{}:{recordedAt:n.recordedAt},events:r}}var vn=class{constructor(e){if(typeof e.namespace!=`string`||!e.namespace.length)throw TypeError(`a last-run store needs a non-empty namespace`);let t=e.maxEvents??1e4;if(!Number.isInteger(t)||t<1||t>1e6)throw RangeError(`last-run maxEvents must be in 1..1000000`);this.maxEvents=t,this.store=new Bt(e.storage??Rt(),`mwg-last-run:${e.namespace}`)}keep(e,t){let n=pn(e);n.length>this.maxEvents&&(n=n.slice(n.length-this.maxEvents));let r=t===void 0?{version:1,events:n}:{version:1,seed:hn(t),events:n};Ot(JSON.stringify(r)),this.store.write(r)}load(){let e=this.store.read(null);if(e===null)return null;if(e?.version!==1)throw Error(`the kept last-run recording is not version 1; clear() it and record again`);return{version:1,...e.seed===void 0?{}:{seed:hn(e.seed)},events:pn(e.events)}}clear(){this.store.remove()}};function yn(e,t){return{version:1,frame:e.frame,events:e.toJSON(),state:t}}function bn(e,t,n){if(typeof e!=`object`||!e||e.version!==1)throw Error(`a run checkpoint must be { version: 1, frame, events, state }`);return new un(pn(e.events),t,n,{fromFrame:e.frame})}function xn(e,t){if(typeof document>`u`)throw Error(`downloadReplayFile needs a browser document`);let n=document.createElement(`a`);n.href=URL.createObjectURL(new Blob([e],{type:`application/json`})),n.download=t,document.body.appendChild(n),n.click(),n.remove(),setTimeout(()=>URL.revokeObjectURL(n.href),0)}async function Sn(e){return await e.text()}async function Cn(e=`.json`){if(typeof document>`u`)throw Error(`pickReplayFile needs a browser document`);return await new Promise(t=>{let n=document.createElement(`input`);n.type=`file`,n.accept=e,n.style.display=`none`;let r=!1,i=e=>{r||(r=!0,n.remove(),t(e))};n.addEventListener(`change`,()=>i(n.files?.[0]??null)),n.addEventListener(`cancel`,()=>i(null)),document.body.appendChild(n),n.click()})}var wn=class e{constructor(){this.entries=[],this.nextSequence=0}append(e,t=[]){let n={sequence:this.nextSequence,action:Wt(e,`action`),events:Wt([...t],`events`)};return this.nextSequence++,this.entries.push(n),structuredClone(n)}get size(){return this.entries.length}get all(){return structuredClone(this.entries)}mark(){return this.nextSequence}since(e){return structuredClone(this.entries.filter(t=>t.sequence>=e))}truncate(e){this.entries=this.entries.filter(t=>t.sequence<e),this.nextSequence=e}toJSON(){return this.all}replace(t){let n=e.fromJSON(t);this.entries=n.entries,this.nextSequence=n.nextSequence}static fromJSON(t){let n=new e,r=0;for(let e of t){if(!Number.isSafeInteger(e.sequence)||e.sequence!==r)throw Error(`action journal sequences must be contiguous from zero`);n.entries.push(structuredClone({sequence:e.sequence,action:e.action,events:[...e.events]})),r++}return n.nextSequence=r,n}};function Tn(e){if(e.criteria)return e.criteria;if(e.counter===void 0||e.target===void 0)throw Error(`achievement "${e.id}" needs either counter/target or criteria`);return[{counter:e.counter,target:e.target}]}var En=class e{constructor(){this.definitions=new Map,this.counts=new Map,this.fresh=[]}define(e){this.definitions.set(e.id,e)}count(e){return this.counts.get(e)??0}increment(e,t=1){let n=this.count(e);this.counts.set(e,n+t);let r=[];for(let t of this.definitions.values()){let i=Tn(t);i.some(t=>t.counter===e)&&(i.every(t=>t.counter===e?n>=t.target:this.count(t.counter)>=t.target)||i.every(e=>this.count(e.counter)>=e.target)&&(r.push(t.id),this.fresh.push(t.id)))}return r}unlocked(e){let t=this.definitions.get(e);if(!t)throw Error(`no such achievement: "${e}"`);return Tn(t).every(e=>this.count(e.counter)>=e.target)}progress(e){let t=this.definitions.get(e);if(!t)throw Error(`no such achievement: "${e}"`);let n=Tn(t);if(n.length!==1)throw Error(`achievement "${e}" has several criteria - use subProgress`);return{count:this.count(n[0].counter),target:n[0].target}}subProgress(e){let t=this.definitions.get(e);if(!t)throw Error(`no such achievement: "${e}"`);return Tn(t).map(e=>{let t=this.count(e.counter);return{counter:e.counter,count:t,target:e.target,met:t>=e.target}})}drainNew(){let e=this.fresh;return this.fresh=[],e}toJSON(){return{counts:[...this.counts]}}static fromJSON(t,n){let r=new e;for(let e of t)r.define(e);for(let[e,t]of n.counts)r.counts.set(e,t);return r}},Dn=class e{constructor(e=[]){this.active=new Set,this.spent=new Set,this.rules=[...e]}add(e){this.rules.push(e)}remove(e){this.rules=this.rules.filter(t=>t.id!==e),this.active.delete(e)}check(e){let t=[];for(let n of this.rules){if(n.once&&this.spent.has(n.id))continue;let r=n.when(e);r&&!this.active.has(n.id)&&(n.action(e),t.push(n.id),n.once&&this.spent.add(n.id)),r?this.active.add(n.id):this.active.delete(n.id)}return t}isActive(e){return this.active.has(e)}reset(){this.active.clear(),this.spent.clear()}toJSON(){return{active:[...this.active],spent:[...this.spent]}}static fromJSON(t,n){let r=new e(t);for(let e of n.active)r.active.add(e);for(let e of n.spent)r.spent.add(e);return r}},On=class{constructor(e={}){let t=e.storage??Rt(),n=`mwg-session:${e.namespace??`default`}`,r=Number(t.read(n)??`0`);this.launches=(Number.isFinite(r)?r:0)+1,t.write(n,String(this.launches))}};function kn(e,t,n){return e<t?t:e>n?n:e}function An(){return{musicVolume:1,sfxVolume:1,muted:!1,zoom:1,bindings:In(je),custom:{}}}function jn(e){return e.muted?0:e.musicVolume}function Mn(e){return e.muted?0:e.sfxVolume}var Nn=class{constructor(e={}){this.storage=e.storage??Rt(),this.key=`mwg-settings:${e.namespace??`default`}`,this.value=Vn(this.storage,this.key),Re(this.value.bindings)}get current(){return{...this.value,bindings:In(this.value.bindings),custom:{...this.value.custom}}}getCustom(e,t){return this.value.custom[e]??t}setCustom(e,t){this.value.custom[e]=t,Hn(this.storage,this.key,this.value)}setMusicVolume(e){this.update({musicVolume:e})}setSfxVolume(e){this.update({sfxVolume:e})}setMuted(e){this.update({muted:e})}setZoom(e){this.update({zoom:e})}setBindings(e){this.update({bindings:e})}update(e){e.musicVolume!==void 0&&(this.value.musicVolume=Pn(e.musicVolume)),e.sfxVolume!==void 0&&(this.value.sfxVolume=Pn(e.sfxVolume)),e.muted!==void 0&&(this.value.muted=e.muted),e.zoom!==void 0&&(this.value.zoom=Fn(e.zoom)),e.bindings!==void 0&&(this.value.bindings=Ln(e.bindings),Re(this.value.bindings)),e.custom!==void 0&&(this.value.custom=zn(e.custom)),Hn(this.storage,this.key,this.value)}applyBindings(){Re(this.value.bindings)}reset(){this.value=An(),Re(this.value.bindings),Hn(this.storage,this.key,this.value)}};function Pn(e){return Number.isFinite(e)?kn(e,0,1):1}function Fn(e){return Number.isFinite(e)?Math.max(.01,e):1}function In(e){let t={};for(let[n,r]of Object.entries(e))t[n]=[...r];return t}function Ln(e){if(typeof e!=`object`||!e||Array.isArray(e))return In(je);let t={};for(let[n,r]of Object.entries(e)){if(!Array.isArray(r))continue;let e=r.filter(e=>typeof e==`string`);e.length>0&&(t[n]=e)}return Object.keys(t).length>0?t:In(je)}function Rn(e){let t=typeof e;return t===`string`||t===`number`||t===`boolean`}function zn(e){if(typeof e!=`object`||!e||Array.isArray(e))return{};let t={};for(let[n,r]of Object.entries(e))Rn(r)&&(t[n]=r);return t}function Bn(e){let t=An();if(typeof e!=`object`||!e||Array.isArray(e))return t;let n=e;return{musicVolume:typeof n.musicVolume==`number`?Pn(n.musicVolume):t.musicVolume,sfxVolume:typeof n.sfxVolume==`number`?Pn(n.sfxVolume):t.sfxVolume,muted:typeof n.muted==`boolean`?n.muted:t.muted,zoom:typeof n.zoom==`number`?Fn(n.zoom):t.zoom,bindings:Ln(n.bindings),custom:zn(n.custom)}}function Vn(e,t){let n=e.read(t);if(!n)return An();try{return Bn(Pt(n,{label:`settings`}))}catch{return An()}}function Hn(e,t,n){e.write(t,JSON.stringify(n))}var Un=class extends Yt{constructor(e){super(e,`feedback`)}async submit(e){if(!e.message?.trim())throw Error(`feedback message cannot be empty`);return this.withTimeout(async t=>{let n=await this.fetchFn(this.endpoint,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify(e),signal:t});if(!n.ok)throw Error(`feedback request failed with HTTP ${n.status}`);return{ok:!0,status:n.status}})}},Wn=class{constructor(e){this.schedule=[],this.cursor=0,this.elapsed=0,this.startedWaves=new Set,this.completed=!1,this.onSpawn=e.onSpawn,this.onWaveStart=e.onWaveStart,this.onComplete=e.onComplete,e.waves.forEach((e,t)=>{let n=e.entries.reduce((e,t)=>e+t.count,0),r=e.duration??0,i=0;for(let a of e.entries)for(let o=0;o<a.count;o++,i++){let o=n>1&&r>0?i/(n-1)*r:0;this.schedule.push({time:e.delay+o,kind:a.kind,waveIndex:t})}}),this.schedule.sort((e,t)=>e.time-t.time),this.schedule.length===0&&(this.completed=!0,this.onComplete?.())}update(e){if(!this.completed){for(this.elapsed+=e;this.cursor<this.schedule.length&&this.schedule[this.cursor].time<=this.elapsed;){let e=this.schedule[this.cursor];this.startedWaves.has(e.waveIndex)||(this.startedWaves.add(e.waveIndex),this.onWaveStart?.(e.waveIndex)),this.onSpawn(e.kind),this.cursor++}this.cursor>=this.schedule.length&&(this.completed=!0,this.onComplete?.())}}get isComplete(){return this.completed}};function Gn(e,t){let n=e,r=t-(e-(e&1))/2;return{x:n,y:-n-r,z:r}}function Kn(e){return{x:e.x,y:e.z+(e.x-(e.x&1))/2}}var qn=[{x:1,y:-1,z:0},{x:1,y:0,z:-1},{x:0,y:1,z:-1},{x:-1,y:1,z:0},{x:-1,y:0,z:1},{x:0,y:-1,z:1}];function Jn(e,t){let n=Gn(e,t);return qn.map(e=>Kn({x:n.x+e.x,y:n.y+e.y,z:n.z+e.z}))}function Yn(e,t){let n=Gn(e.x,e.y),r=Gn(t.x,t.y);return Math.max(Math.abs(n.x-r.x),Math.abs(n.y-r.y),Math.abs(n.z-r.z))}function Xn(e){let t=Math.round(e.x),n=Math.round(e.y),r=Math.round(e.z),i=Math.abs(t-e.x),a=Math.abs(n-e.y),o=Math.abs(r-e.z);return i>a&&i>o?t=-n-r:a>o?n=-t-r:r=-t-n,{x:t,y:n,z:r}}function Zn(e,t){let n=Yn(e,t);if(n===0)return[{x:e.x,y:e.y}];let r=Gn(e.x,e.y),i=Gn(t.x,t.y),a=[];for(let e=0;e<=n;e++){let t=e/n;a.push(Kn(Xn({x:r.x+(i.x-r.x)*t,y:r.y+(i.y-r.y)*t,z:r.z+(i.z-r.z)*t})))}return a}function Qn(e,t){let n=Gn(e.x,e.y),r=[];for(let e=-t;e<=t;e++)for(let i=Math.max(-t,-e-t);i<=Math.min(t,-e+t);i++){let t=-e-i;r.push(Kn({x:n.x+e,y:n.y+i,z:n.z+t}))}return r}function $n(e,t,n,r,i={}){let a=+((i.offset??`odd`)===`odd`);return(i.orientation??`flat-top`)===`pointy-top`?{x:e*n+((t&1)===a?n/2:0)+n/2,y:t*r*.75+r/2}:{x:e*n*.75+n/2,y:t*r+((e&1)===a?r/2:0)+r/2}}function er(e,t,n,r,i={}){let a=(i.orientation??`flat-top`)===`pointy-top`,o=a?n:n*.75,s=a?r*.75:r,c=Math.round((e-n/2)/o),l=Math.round((t-r/2)/s),u={x:c,y:l},d=1/0;for(let a=-2;a<=2;a++)for(let o=-2;o<=2;o++){let s={x:c+a,y:l+o},f=$n(s.x,s.y,n,r,i),p=(f.x-e)**2+(f.y-t)**2;p<d&&(d=p,u=s)}return u}function tr(e,t,n,r){return n>=0&&r>=0&&n<e&&r<t}function nr(e,t,n){return n*e+t}function rr(e,t){return t%e}function ir(e,t){return Math.floor(t/e)}function ar(e,t){return`${e},${t}`}function or(e){let t=/^(-?\d+),(-?\d+)$/.exec(e);return t?{x:Number(t[1]),y:Number(t[2])}:null}var sr=[[0,-1],[1,0],[0,1],[-1,0]],cr=[[-1,-1],[0,-1],[1,-1],[-1,0],[1,0],[-1,1],[0,1],[1,1]],lr=[[0,-1],[1,-1],[1,0],[1,1],[0,1],[-1,1],[-1,0],[-1,-1]];function ur(e,t){return Math.hypot(e.x-t.x,e.y-t.y)}function dr(e,t){return Math.max(Math.abs(e.x-t.x),Math.abs(e.y-t.y))}function fr(e,t){return Math.abs(e.x-t.x)+Math.abs(e.y-t.y)}function pr(e,t,n){switch(e){case`euclidean`:return ur(t,n);case`manhattan`:return fr(t,n);case`chebyshev`:return dr(t,n);default:throw RangeError(`unknown distance metric: ${e}`)}}var mr=.12,hr=null,gr,_r=new p;function vr(){if(gr===void 0&&(gr=typeof matchMedia==`function`?matchMedia(`(prefers-reduced-motion: reduce)`):null,gr)){let e=()=>{_r.dispatch(br())};typeof gr.addEventListener==`function`?gr.addEventListener(`change`,e):typeof gr.addListener==`function`&&gr.addListener(e)}return gr}function yr(){return vr()?.matches??!1}function br(){return hr??yr()}function xr(e){hr=e,_r.dispatch(br())}function Sr(e){return vr(),_r.add(e),()=>_r.remove(e)}function Cr(e,t=`decorative`){return br()?t===`meaningful`?Math.min(e,mr):0:e}var wr=class e{constructor(e,t){this.width=e,this.height=t,this.volume=new Float32Array(e*t)}index(e,t){return e<0||t<0||e>=this.width||t>=this.height?-1:t*this.width+e}volumeAt(e,t){let n=this.index(e,t);return n<0?0:this.volume[n]}total(){let e=0;for(let t=0;t<this.volume.length;t++)e+=this.volume[t];return e}seed(e,t,n){let r=this.index(e,t);r>=0&&(this.volume[r]+=n)}clear(e,t){let n=this.index(e,t);n>=0&&(this.volume[n]=0)}spread(e,t=.25,n=.9){let r=new Float32Array(this.volume.length);for(let n=0;n<this.height;n++)for(let i=0;i<this.width;i++){let a=n*this.width+i,o=this.volume[a];if(o<=0)continue;let s=[];for(let[t,r]of sr){let a=i+t,o=n+r,c=this.index(a,o);c>=0&&e(a,o)&&s.push(c)}if(s.length===0)r[a]+=o;else{let e=o*t/s.length;r[a]+=o-e*s.length;for(let t of s)r[t]+=e}}let i=[];for(let e=0;e<r.length;e++){let t=r[e]*n,a=t<.001?0:t;a===0&&this.volume[e]>0&&i.push({x:e%this.width,y:Math.floor(e/this.width)}),this.volume[e]=a}return i}cellsAbove(e){let t=[];for(let n=0;n<this.height;n++)for(let r=0;r<this.width;r++){let i=this.volume[n*this.width+r];i>=e&&t.push({x:r,y:n,volume:i})}return t}toJSON(){return{width:this.width,height:this.height,volume:[...this.volume]}}static fromJSON(t){let n=new e(t.width,t.height);return n.volume.set(t.volume.slice(0,n.volume.length)),n}},Tr=class{constructor(e=1/60,t=5){if(this.accumulator=0,!(e>0))throw Error(`a fixed step must be positive`);this.step=e,this.maxSubsteps=Math.max(1,Math.floor(t))}advance(e,t){this.accumulator+=Math.max(0,e);let n=0;for(;this.accumulator>=this.step&&n<this.maxSubsteps;)t(this.step),this.accumulator-=this.step,n++;return this.accumulator>=this.step&&(this.accumulator=0),n}get alpha(){return this.accumulator/this.step}reset(){this.accumulator=0}},Er={linear:e=>e,easeInQuad:e=>e*e,easeOutQuad:e=>e*(2-e),easeInOutQuad:e=>e<.5?2*e*e:1-(-2*e+2)**2/2,easeInCubic:e=>e*e*e,easeOutCubic:e=>1-(1-e)**3,easeInOutCubic:e=>e<.5?4*e*e*e:1-(-2*e+2)**3/2},Dr=class{constructor(){this.tweens=[]}tween(e,t,n=Er.linear){let{ease:r=Er.linear,intent:i=`decorative`,alternate:a}=typeof n==`function`?{ease:n}:n,o=br()&&a!==void 0,s=o?a:t,c=Cr(e,o?`meaningful`:i);return c>0?new Promise(e=>{this.tweens.push({elapsed:0,duration:c,ease:r,apply:s,resolve:e,finishOnReduced:!o&&i===`decorative`})}):(s(1),Promise.resolve())}update(e){if(this.tweens.length!==0)for(let t of[...this.tweens]){if(t.finishOnReduced&&br()){this.tweens.splice(this.tweens.indexOf(t),1),t.apply(1),t.resolve();continue}t.elapsed+=e;let n=Math.min(1,t.elapsed/t.duration);t.apply(t.ease(n)),n>=1&&(this.tweens.splice(this.tweens.indexOf(t),1),t.resolve())}}get isBusy(){return this.tweens.length>0}clear(){this.tweens=[]}},Or=class{constructor(e={}){this.history=[],this.cursor=-1,this.limit=Math.max(1,e.limit??50)}push(e){this.history.length=this.cursor+1,this.history.push(e),this.history.length>this.limit&&this.history.shift(),this.cursor=this.history.length-1}get canUndo(){return this.cursor>0}get canRedo(){return this.cursor<this.history.length-1}undo(){return this.canUndo?(this.cursor--,this.history[this.cursor]):null}redo(){return this.canRedo?(this.cursor++,this.history[this.cursor]):null}get current(){return this.cursor>=0?this.history[this.cursor]:null}clear(){this.history=[],this.cursor=-1}};function kr(e,t){if(!Number.isFinite(t.maxCost)||t.maxCost<0)return new Map;let n=t.key(e),r=new Map([[n,{cell:e,cost:0}]]),i=[{cell:e,key:n,cost:0}];for(;i.length>0;){let e=jr(i),n=r.get(e.key);if(n&&e.cost===n.cost&&!t.stop?.(e.cell,e.cost))for(let n of t.neighbors(e.cell)){let a=t.cost(e.cell,n);if(!Number.isFinite(a)||a<0)continue;let o=e.cost+a;if(o>t.maxCost||t.canEnter&&!t.canEnter(n,e.cell,o))continue;let s=t.key(n);if(o>=(r.get(s)?.cost??1/0))continue;let c={cell:n,key:s,cost:o};r.set(s,c),Ar(i,c)}}return r}function Ar(e,t){e.push(t);let n=e.length-1;for(;n>0;){let t=Math.floor((n-1)/2);if(e[t].cost<=e[n].cost)break;[e[t],e[n]]=[e[n],e[t]],n=t}}function jr(e){let t=e[0],n=e.pop();if(!t||!n||e.length===0)return t;e[0]=n;let r=0;for(;;){let t=r*2+1,n=t+1,i=r;if(t<e.length&&e[t].cost<e[i].cost&&(i=t),n<e.length&&e[n].cost<e[i].cost&&(i=n),i===r)break;[e[r],e[i]]=[e[i],e[r]],r=i}return t}var Mr=class{constructor(){this.changed=new p,this.tasks=[],this.progress=new Map,this.status_=`idle`,this.current_=null,this.error_=null,this.cancelled=!1}add(e){if(!e.id)throw Error(`load task needs an id`);if(this.tasks.some(t=>t.id===e.id))throw Error(`duplicate load task "${e.id}"`);if(e.weight!==void 0&&!(e.weight>0))throw Error(`load task weight must be positive`);if(this.status_===`loading`)throw Error(`cannot add a task while loading`);return this.tasks.push(e),this.progress.set(e.id,0),this.emit(),this}get snapshot(){let e=this.tasks.reduce((e,t)=>e+(t.weight??1),0),t=this.tasks.reduce((e,t)=>e+(t.weight??1)*(this.progress.get(t.id)??0),0);return{status:this.status_,completed:t,total:e,current:this.current_,error:this.error_}}async start(){if(this.status_===`loading`)throw Error(`load queue is already running`);this.status_=`loading`,this.cancelled=!1,this.error_=null,this.emit();try{for(let e of this.tasks){if(this.cancelled)break;this.current_=e.id,this.emit();let t=this;await e.run({get cancelled(){return t.cancelled},report:t=>this.report(e.id,t)}),this.cancelled||this.report(e.id,1)}this.current_=null,this.status_=this.cancelled?`cancelled`:`ready`,this.emit()}catch(e){throw this.current_=null,this.error_=e,this.status_=`failed`,this.emit(),e}}cancel(){this.status_===`loading`&&(this.cancelled=!0)}retry(){if(this.status_===`loading`)throw Error(`cannot retry while loading`);this.status_=`idle`,this.current_=null,this.error_=null,this.cancelled=!1;for(let e of this.tasks)this.progress.set(e.id,0);this.emit()}report(e,t){if(!Number.isFinite(t))throw Error(`load progress must be finite`);this.progress.set(e,Math.max(0,Math.min(1,t))),this.emit()}emit(){this.changed.dispatch(this.snapshot)}},Nr=class{constructor(){this.entities=new Map,this.ids=new Map,this.sequence=0}add(e,t){let n=this.ids.get(e);if(n!==void 0){if(t!==void 0&&t!==n)throw Error(`entity already has id ${n}`);return n}let r=t;if(r!==void 0){if(r.length===0)throw Error(`entity id must not be empty`);if(this.entities.has(r))throw Error(`entity id already exists: ${r}`)}else do r=`e${this.sequence++}`;while(this.entities.has(r));return this.entities.set(r,e),this.ids.set(e,r),r}get(e){return this.entities.get(e)}idOf(e){return this.ids.get(e)}has(e){return this.entities.has(e)}remove(e){let t=this.entities.get(e);return t!==void 0&&(this.entities.delete(e),this.ids.delete(t),!0)}get size(){return this.entities.size}},Pr=class{constructor(e){this.queue=[],this.busy=!1,this.remaining=0,this.play=e.play}enqueue(e){this.queue.push(...e),this.busy||this.advance()}update(e){this.busy&&(this.remaining-=e,this.remaining<=0&&this.advance())}get isBusy(){return this.busy}clear(){this.queue=[],this.busy=!1,this.remaining=0}advance(){let e=this.queue.shift();if(e===void 0){this.busy=!1,this.remaining=0;return}this.busy=!0;let t=this.play(e)||0;t>0?this.remaining=t:this.advance()}},Fr=2166136261,Ir=16777619;function Lr(e,t=!1){if(e==null)return`null`;switch(typeof e){case`boolean`:return e?`true`:`false`;case`number`:return Number.isFinite(e)?String(e):`null`;case`string`:return JSON.stringify(e);case`bigint`:return JSON.stringify(e.toString());case`function`:case`symbol`:return t?`null`:`undefined`}return Array.isArray(e)?`[${e.map(e=>Lr(e,!0)).join(`,`)}]`:`{${Object.entries(e).filter(([,e])=>e!==void 0&&typeof e!=`function`&&typeof e!=`symbol`).sort(([e],[t])=>e<t?-1:+(e>t)).map(([e,t])=>`${JSON.stringify(e)}:${Lr(t)}`).join(`,`)}}`}function Rr(e){let t=Lr(e),n=Fr;for(let e=0;e<t.length;e++)n^=t.charCodeAt(e),n=Math.imul(n,Ir)>>>0;return n>>>0}var zr=class{constructor(){this.reference=new Map,this.desyncTick=null}observe(e,t){let n=this.reference.get(e);return n===void 0?(this.reference.set(e,t),!0):n===t||(this.desyncTick===null&&(this.desyncTick=e),!1)}get divergent(){return this.desyncTick!==null}get atTick(){return this.desyncTick}reset(){this.reference.clear(),this.desyncTick=null}};function Br(e,t={}){let n=Ur(e);if(n.length===0)return[];let[r,...i]=n,a=t.listDelimiter??`;`,o=t.mapDelimiter??`=`;return i.map((e,n)=>{let i={};return r.forEach((r,s)=>{let c=e[s]??``;c!==``&&(i[r]=Vr(c,t.columns?.[r]??`string`,r,n,a,o))}),i})}function Vr(e,t,n,r,i,a){return Hr(e,t,{column:n,rowIndex:r,listDelimiter:i,mapDelimiter:a,error:()=>t===`number`?`CSV row ${r+1}, column "${n}": "${e}" is not a number`:t===`boolean`?`CSV row ${r+1}, column "${n}": "${e}" is not "true" or "false"`:t===`map`?`CSV row ${r+1}, column "${n}": "${e.split(i).map(e=>e.trim()).find(e=>e&&!e.includes(a))??e}" has no "${a}" to split a key from its value`:`CSV row ${r+1}, column "${n}": "${e}" is invalid`})}function Hr(e,t,n={}){let r=n.column??`value`,i=n.listDelimiter??`;`,a=n.mapDelimiter??`=`,o=e=>{throw Error(n.error?.(e)??e)};switch(t){case`string`:return e;case`number`:{let t=Number(e);return Number.isFinite(t)||o(`${r} must be a number`),t}case`boolean`:{let t=e.toLowerCase();if(t===`true`)return!0;if(t===`false`)return!1;if(n.acceptYesNo&&t===`yes`)return!0;if(n.acceptYesNo&&t===`no`)return!1;o(`${r} must be true, false${n.acceptYesNo?`, yes, or no`:``}`)}case`list`:return e.split(i).map(e=>e.trim()).filter(e=>e.length>0);case`map`:{let t={};for(let n of e.split(i).map(e=>e.trim()).filter(e=>e.length>0)){let e=n.indexOf(a);e===-1&&o(`${r} map entry must contain ${a}`),t[n.slice(0,e).trim()]=n.slice(e+a.length).trim()}return t}}}function Ur(e){let t=[],n=[],r=``,i=!1,a=0;for(;a<e.length;){let o=e[a];if(i){if(o===`"`){if(e[a+1]===`"`){r+=`"`,a+=2;continue}i=!1,a++;continue}r+=o,a++}else o===`"`?(i=!0,a++):o===`,`?(n.push(r),r=``,a++):o===`\r`?a++:o===`
2
2
  `?(n.push(r),t.push(n),n=[],r=``,a++):(r+=o,a++)}return(r.length>0||n.length>0)&&(n.push(r),t.push(n)),t.filter(e=>e.length!==1||e[0]!==``)}function Wr(e){let t=[],n=[],r,i=e=>{if(!n.includes(e)){if(n.length>=2)throw Error(`dialogue text supports at most two speakers for the "-" shorthand; found a third speaker "${e}" after "${n[0]}" and "${n[1]}" were already established`);n.push(e)}};for(let a of e.split(`
3
3
  `)){let e=a.trim();if(e===``)continue;let o=/^@(\S+)(?:\s+(.*))?$/.exec(e);if(o){let[,e,n]=o;i(e),n&&(t.push({text:n.trim(),speaker:e}),r=e)}else if(e.startsWith(`-`)){if(n.length<2)throw Error(`a "-" line needs two established speakers first (write two "@id text" lines before it)`);let i=e.slice(1).trim(),a=n.find(e=>e!==r)??n[0];t.push({text:i,speaker:a}),r=a}else t.push({text:e}),r=void 0}return t}function Gr(e){let t=Kr(e);if(t.length===0)throw Error(`this twee source has no passages - passages start with ":: Name"`);let n={},r,i;for(let e of t)if(e.name===`StoryTitle`)r=e.body.trim()||void 0;else if(e.name===`StoryData`)i=Jr(e.body);else{if(Object.prototype.hasOwnProperty.call(n,e.name))throw Error(`this twee source defines "${e.name}" twice`);n[e.name]=Yr(e.name,e.body)}let a=Object.keys(n);if(a.length===0)throw Error(`this twee source has no story passages, only StoryTitle/StoryData`);if(i!==void 0&&!Object.prototype.hasOwnProperty.call(n,i))throw Error(`StoryData starts at "${i}", which is not a passage in this file`);for(let[e,t]of Object.entries(n))for(let r of t)if(`ask`in r){for(let t of r.choices)if(t.goto!==void 0&&!Object.prototype.hasOwnProperty.call(n,t.goto))throw Error(`passage "${e}" links to "${t.goto}", which is not a passage in this file`)}return{story:n,start:i??a[0],title:r}}function Kr(e){let t=[],n=null,r=[],i=()=>{n!==null&&t.push({name:n,body:r.join(`
4
4
  `)})};for(let t of e.split(`
package/dist/version.d.ts CHANGED
@@ -9,5 +9,5 @@
9
9
  * this from `package.json` automatically, chosen because this project's own build already
10
10
  * has no step before `tsc`/`vite` that this would need to run ahead of.
11
11
  */
12
- export declare const version = "0.39.1";
12
+ export declare const version = "0.40.0";
13
13
  //# sourceMappingURL=version.d.ts.map
package/dist/version.js CHANGED
@@ -9,5 +9,5 @@
9
9
  * this from `package.json` automatically, chosen because this project's own build already
10
10
  * has no step before `tsc`/`vite` that this would need to run ahead of.
11
11
  */
12
- export const version = '0.39.1';
12
+ export const version = '0.40.0';
13
13
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datamoc/mw_games",
3
- "version": "0.39.1",
3
+ "version": "0.40.0",
4
4
  "description": "A local-file game framework: renderer-free game logic, drawn through PixiJS in 2D or Babylon.js in 3D, with mobile and desktop packaging.",
5
5
  "license": "MPL-2.0",
6
6
  "repository": {
@@ -82,6 +82,8 @@
82
82
  "tools/sbom.mjs",
83
83
  "tools/sbom.d.mts",
84
84
  "sbom.cdx.json",
85
+ "data/microgm-opt.sf2",
86
+ "data/microgm-opt.PROVENANCE.md",
85
87
  "api-surface.json",
86
88
  "tools/single-file.mjs",
87
89
  "tools/single-file.d.mts",
package/sbom.cdx.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "bomFormat": "CycloneDX",
3
3
  "specVersion": "1.6",
4
- "serialNumber": "urn:uuid:87d5da55-61f5-526f-9055-cdcdd6680517",
4
+ "serialNumber": "urn:uuid:7aef1fa2-ee4d-5ed7-bb1c-aa6ad3a92c78",
5
5
  "version": 1,
6
6
  "metadata": {
7
7
  "tools": {
@@ -10,16 +10,16 @@
10
10
  "type": "application",
11
11
  "group": "datamoc",
12
12
  "name": "tools/sbom.mjs",
13
- "version": "0.39.1"
13
+ "version": "0.40.0"
14
14
  }
15
15
  ]
16
16
  },
17
17
  "component": {
18
18
  "type": "library",
19
- "bom-ref": "pkg:npm/%40datamoc/mw_games@0.39.1",
19
+ "bom-ref": "pkg:npm/%40datamoc/mw_games@0.40.0",
20
20
  "name": "@datamoc/mw_games",
21
- "version": "0.39.1",
22
- "purl": "pkg:npm/%40datamoc/mw_games@0.39.1",
21
+ "version": "0.40.0",
22
+ "purl": "pkg:npm/%40datamoc/mw_games@0.40.0",
23
23
  "description": "A local-file game framework: renderer-free game logic, drawn through PixiJS in 2D or Babylon.js in 3D, with mobile and desktop packaging.",
24
24
  "licenses": [
25
25
  {
@@ -4601,7 +4601,7 @@
4601
4601
  ]
4602
4602
  },
4603
4603
  {
4604
- "ref": "pkg:npm/%40datamoc/mw_games@0.39.1",
4604
+ "ref": "pkg:npm/%40datamoc/mw_games@0.40.0",
4605
4605
  "dependsOn": [
4606
4606
  "pkg:npm/%40babylonjs/core@9.30.0",
4607
4607
  "pkg:npm/%40babylonjs/loaders@9.30.0",
@@ -12,6 +12,10 @@ export interface CompileResourcesOptions {
12
12
  toWebp?: boolean;
13
13
  webpLossless?: boolean;
14
14
  webpQuality?: number;
15
+ /** Subset an embedded `.sf2` to the programs the embedded `.mid`/`.midi` files use. Default true; `false` embeds every SoundFont whole. */
16
+ soundFont?: boolean;
17
+ /** Explicit `{ bank, program }` list to subset a SoundFont to, overriding the MIDI-derived list. */
18
+ soundFontUsage?: readonly SoundFontUsage[];
15
19
  }
16
20
 
17
21
  export interface CompileResourcesGroup {
@@ -28,9 +32,27 @@ export interface CompileResourcesResult {
28
32
  skipped: number;
29
33
  /** the `CREDITS.txt` text when the asset folder holds a `credits.json`, else null */
30
34
  credits: string | null;
35
+ /** one entry per embedded `.sf2`: the programs kept (null when embedded whole) and the byte saving */
36
+ soundFonts: SoundFontEmbed[];
31
37
  }
32
38
 
33
39
  /** the plain-text credits for a manifest (an array, or `{ credits: [...] }`, of `{ name, license, author?, url? }`) */
34
40
  export function creditsText(manifest: unknown): string;
35
41
 
42
+ export interface SoundFontUsage {
43
+ bank: number;
44
+ program: number;
45
+ }
46
+
47
+ export interface SoundFontEmbed {
48
+ key: string;
49
+ /** programs kept, or null when the font was embedded whole for lack of a usage list */
50
+ programs: number | null;
51
+ rawBytes: number;
52
+ embeddedBytes: number;
53
+ }
54
+
55
+ /** parses a `--soundfont-usage=bank:program,...` CLI value; undefined when the flag is absent */
56
+ export function soundFontUsageFromFlag(value: string | undefined): SoundFontUsage[] | undefined;
57
+
36
58
  export function compileResources(options: CompileResourcesOptions): Promise<CompileResourcesResult>;
@@ -38,8 +38,56 @@ export const DEFAULT_MIME = {
38
38
  '.woff2': 'font/woff2',
39
39
  '.json': 'application/json',
40
40
  '.txt': 'text/plain',
41
+ '.mid': 'audio/midi',
42
+ '.midi': 'audio/midi',
43
+ '.sf2': 'application/octet-stream',
41
44
  };
42
45
 
46
+ const isSoundFontKey = (key) => key.toLowerCase().endsWith('.sf2');
47
+ const isMidiKey = (key) => /\.(mid|midi)$/i.test(key);
48
+
49
+ /**
50
+ * The SoundFont/MIDI implementation lives in the library, not in this tool: in this
51
+ * repository it is the TypeScript source, in an installed package it is the built
52
+ * `dist/` the source compiles to. Only called when a build actually embeds a
53
+ * SoundFont it can subset, so a game without one never pays for the import.
54
+ */
55
+ async function loadSoundFontTools() {
56
+ try {
57
+ const [soundFont, midi] = await Promise.all([
58
+ import('../src/audio/SoundFont.ts'),
59
+ import('../src/audio/Midi.ts'),
60
+ ]);
61
+ return { ...soundFont, ...midi };
62
+ } catch {
63
+ return import('../dist/audio/index.js');
64
+ }
65
+ }
66
+
67
+ function checkedSoundFontUsage(soundFontUsage) {
68
+ if (soundFontUsage === undefined) return undefined;
69
+ if (!Array.isArray(soundFontUsage)) throw new Error('soundFontUsage must be an array of { bank, program }');
70
+ return soundFontUsage.map((entry, index) => {
71
+ const { bank, program } = entry ?? {};
72
+ if (!Number.isInteger(bank) || bank < 0 || !Number.isInteger(program) || program < 0 || program > 127) {
73
+ throw new Error(`soundFontUsage[${index}] needs an integer bank >= 0 and a program between 0 and 127`);
74
+ }
75
+ return { bank, program };
76
+ });
77
+ }
78
+
79
+ /** parses a `--soundfont-usage=bank:program,...` CLI value; undefined when the flag is absent */
80
+ export function soundFontUsageFromFlag(value) {
81
+ if (value === undefined) return undefined;
82
+ return value
83
+ .split(',')
84
+ .filter(Boolean)
85
+ .map((pair) => {
86
+ const [bank, program] = pair.split(':').map(Number);
87
+ return { bank, program };
88
+ });
89
+ }
90
+
43
91
  export async function* walk(dir) {
44
92
  let entries;
45
93
  try {
@@ -72,6 +120,10 @@ export async function* walk(dir) {
72
120
  * pixel-art tileset's exact colours survive; set false only with `webpQuality` to opt into
73
121
  * lossy re-encoding for photographic source art)
74
122
  * @param options.webpQuality 0-100, used only when `webpLossless` is false (default 90)
123
+ * @param options.soundFont subset an embedded `.sf2` to the programs the embedded
124
+ * `.mid`/`.midi` files use (default true); `false` embeds every SoundFont whole
125
+ * @param options.soundFontUsage explicit `{ bank, program }` list to subset to, for a
126
+ * game that voices notes no shipped MIDI file requests; overrides the MIDI-derived list
75
127
  * @returns the group names, in the order the page should load them
76
128
  *
77
129
  * An `assets/credits.json` (an array, or `{ "credits": [...] }`, of `{ name, license, author?, url? }`)
@@ -88,6 +140,8 @@ export async function compileResources({
88
140
  toWebp: convertToWebp = false,
89
141
  webpLossless = true,
90
142
  webpQuality = 90,
143
+ soundFont = true,
144
+ soundFontUsage,
91
145
  } = {}) {
92
146
  if (!from || !to) throw new Error('compileResources needs both `from` and `to`');
93
147
 
@@ -110,6 +164,38 @@ export async function compileResources({
110
164
  }
111
165
  }
112
166
 
167
+ // A shipped SoundFont carries only the programs the game's shipped MIDI files
168
+ // request: the full bank - even a small one - is mostly
169
+ // instruments no track plays. Usage is resolved before any output is replaced,
170
+ // so an unreadable MIDI or SoundFont fails the build with the old output intact.
171
+ // With no shipped MIDI and no explicit `soundFontUsage` the usage is unknowable
172
+ // (a game may voice notes it generates), so the font embeds whole.
173
+ const isSelected = (file) => !selected || selected.has(keyOf(file));
174
+ const explicitUsage = checkedSoundFontUsage(soundFontUsage);
175
+ const embedsSoundFont =
176
+ soundFont !== false && files.some((file) => isSelected(file) && isSoundFontKey(keyOf(file)));
177
+ let soundFontTools = null;
178
+ let usage = explicitUsage;
179
+ if (embedsSoundFont && usage === undefined) {
180
+ const midiFiles = files.filter((file) => isSelected(file) && isMidiKey(keyOf(file)));
181
+ if (midiFiles.length > 0) {
182
+ soundFontTools = await loadSoundFontTools();
183
+ const parsed = [];
184
+ for (const file of midiFiles) {
185
+ try {
186
+ parsed.push(soundFontTools.parseMidi(await readFile(file)));
187
+ } catch (error) {
188
+ throw new Error(
189
+ `cannot read MIDI asset ${keyOf(file)} to subset a SoundFont: ${error.message ?? error}`,
190
+ );
191
+ }
192
+ }
193
+ usage = soundFontTools.collectSoundFontUsage(parsed);
194
+ }
195
+ }
196
+ if (embedsSoundFont && usage !== undefined && !soundFontTools) soundFontTools = await loadSoundFontTools();
197
+ const soundFonts = [];
198
+
113
199
  await rm(to, { recursive: true, force: true });
114
200
  await mkdir(to, { recursive: true });
115
201
 
@@ -153,6 +239,19 @@ export async function compileResources({
153
239
  }
154
240
  }
155
241
 
242
+ if (isSoundFontKey(key)) {
243
+ if (usage !== undefined && soundFontTools) {
244
+ try {
245
+ bytes = Buffer.from(soundFontTools.subsetSoundFont(data, usage));
246
+ } catch (error) {
247
+ throw new Error(`cannot subset SoundFont asset ${key}: ${error.message ?? error}`);
248
+ }
249
+ soundFonts.push({ key, programs: usage.length, rawBytes: data.length, embeddedBytes: bytes.length });
250
+ } else {
251
+ soundFonts.push({ key, programs: null, rawBytes: data.length, embeddedBytes: data.length });
252
+ }
253
+ }
254
+
156
255
  const name = group(key);
157
256
  if (!groups.has(name)) groups.set(name, {});
158
257
  groups.get(name)[key] = `data:${mimeType};base64,${bytes.toString('base64')}`;
@@ -173,7 +272,7 @@ export async function compileResources({
173
272
 
174
273
  if (credits) await writeFile(join(to, 'CREDITS.txt'), credits, 'utf8');
175
274
 
176
- return { groups: written, rawBytes, embeddedBytes, webpConverted, skipped, credits };
275
+ return { groups: written, rawBytes, embeddedBytes, webpConverted, skipped, credits, soundFonts };
177
276
  }
178
277
 
179
278
  /** the plain-text credits for a manifest, one block per entry; throws on an entry that cannot be attributed */
@@ -202,7 +301,7 @@ async function runCli() {
202
301
  const [from, to] = args.filter((a) => !a.startsWith('--'));
203
302
  if (!from || !to) {
204
303
  console.error(
205
- 'usage: node compile-resources.mjs <asset folder> <output folder> [--to-webp] [--webp-lossy[=quality]]',
304
+ 'usage: node compile-resources.mjs <asset folder> <output folder> [--to-webp] [--webp-lossy[=quality]] [--no-soundfont-subset] [--soundfont-usage=bank:program,...]',
206
305
  );
207
306
  process.exit(1);
208
307
  }
@@ -211,7 +310,11 @@ async function runCli() {
211
310
  const webpLossless = !lossyFlag;
212
311
  const webpQuality = lossyFlag && lossyFlag.includes('=') ? Number(lossyFlag.split('=')[1]) : 90;
213
312
 
214
- const { groups, rawBytes, embeddedBytes, webpConverted, skipped } = await compileResources({
313
+ const soundFontUsage = soundFontUsageFromFlag(
314
+ args.find((a) => a.startsWith('--soundfont-usage='))?.slice('--soundfont-usage='.length),
315
+ );
316
+
317
+ const { groups, rawBytes, embeddedBytes, webpConverted, skipped, soundFonts } = await compileResources({
215
318
  from,
216
319
  to,
217
320
  //a flat folder has no subdirectories to group by, so everything lands in one file
@@ -219,6 +322,8 @@ async function runCli() {
219
322
  toWebp,
220
323
  webpLossless,
221
324
  webpQuality,
325
+ soundFont: !args.includes('--no-soundfont-subset'),
326
+ soundFontUsage,
222
327
  });
223
328
 
224
329
  const kb = (n) => (n / 1024).toFixed(1).padStart(8) + ' KB';
@@ -226,6 +331,13 @@ async function runCli() {
226
331
  console.log(` ${join(relative(process.cwd(), to), name + '.js').padEnd(40)}${kb(size)} (${count} files)`);
227
332
  }
228
333
  console.log(` ${groups.length} script(s), ${kb(rawBytes)} of assets` + (skipped ? `, ${skipped} skipped` : ''));
334
+ for (const font of soundFonts) {
335
+ console.log(
336
+ font.programs === null
337
+ ? ` ${font.key}: SoundFont embedded whole (${kb(font.rawBytes)}), no shipped MIDI to subset against`
338
+ : ` ${font.key}: SoundFont subset to ${font.programs} program(s), ${kb(font.rawBytes)} -> ${kb(font.embeddedBytes)}`,
339
+ );
340
+ }
229
341
  if (toWebp) {
230
342
  console.log(
231
343
  ` ${webpConverted} image(s) converted to WebP (${webpLossless ? 'lossless' : `lossy, quality ${webpQuality}`}), ` +
@@ -1,4 +1,4 @@
1
- import type { CompileResourcesGroup } from './compile-resources.mjs';
1
+ import type { CompileResourcesGroup, SoundFontEmbed, SoundFontUsage } from './compile-resources.mjs';
2
2
  import type { CompressRow } from './compress-dist.mjs';
3
3
  import type { SingleFileOptions, SingleFileResult } from './single-file.mjs';
4
4
 
@@ -20,6 +20,10 @@ export interface EmitPageOptions {
20
20
  toWebp?: boolean;
21
21
  webpLossless?: boolean;
22
22
  webpQuality?: number;
23
+ /** Subset an embedded `.sf2` to the programs the embedded `.mid`/`.midi` files use. Default true; `false` embeds every SoundFont whole. */
24
+ soundFont?: boolean;
25
+ /** Explicit `{ bank, program }` list to subset a SoundFont to, overriding the MIDI-derived list. */
26
+ soundFontUsage?: readonly SoundFontUsage[];
23
27
  /** the page's Content-Security-Policy: on by default, `false` to leave it out */
24
28
  csp?: boolean | CspOptions;
25
29
  }
@@ -38,6 +42,8 @@ export interface EmitPageResult {
38
42
  rawBytes: number;
39
43
  embeddedBytes: number;
40
44
  webpConverted: number;
45
+ /** one entry per embedded `.sf2`: the programs kept (null when embedded whole) and the byte saving */
46
+ soundFonts: SoundFontEmbed[];
41
47
  compressed: CompressRow[] | null;
42
48
  single: SingleFileResult | null;
43
49
  }
@@ -3,7 +3,7 @@ import { readFile, writeFile, rm, stat } from 'node:fs/promises';
3
3
  import { realpathSync } from 'node:fs';
4
4
  import { join, resolve as resolvePath } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
- import { compileResources } from './compile-resources.mjs';
6
+ import { compileResources, soundFontUsageFromFlag } from './compile-resources.mjs';
7
7
  import { compressDist } from './compress-dist.mjs';
8
8
  import { toClassicScript } from './classic-html.mjs';
9
9
  import { buildSingleFile } from './single-file.mjs';
@@ -30,6 +30,7 @@ import { withContentSecurityPolicy } from './csp.mjs';
30
30
  * [--no-compress] [--xz]
31
31
  * [--single-file] [--single-file-compress[=level]]
32
32
  * [--single-file-brotli[=quality]] [--to-webp] [--webp-lossy[=quality]]
33
+ * [--no-soundfont-subset] [--soundfont-usage=bank:program,...]
33
34
  *
34
35
  * `--dist` defaults to `<project folder>/dist` and `--assets` to `<project folder>/assets`; with
35
36
  * no assets folder at all, the page is rewritten and nothing is compiled.
@@ -73,6 +74,8 @@ export async function emitPage({
73
74
  toWebp = false,
74
75
  webpLossless = true,
75
76
  webpQuality = 90,
77
+ soundFont = true,
78
+ soundFontUsage,
76
79
  csp = true,
77
80
  }) {
78
81
  if (!dist) throw new Error('emitPage needs the dist folder of a vite build');
@@ -86,8 +89,10 @@ export async function emitPage({
86
89
  toWebp,
87
90
  webpLossless,
88
91
  webpQuality,
92
+ soundFont,
93
+ soundFontUsage,
89
94
  })
90
- : { groups: [], rawBytes: 0, embeddedBytes: 0, webpConverted: 0 };
95
+ : { groups: [], rawBytes: 0, embeddedBytes: 0, webpConverted: 0, soundFonts: [] };
91
96
 
92
97
  const html = await readFile(join(dist, 'index.html'), 'utf8');
93
98
  const classic = toClassicScript(html);
@@ -142,6 +147,10 @@ async function main(argv) {
142
147
  level: levelFlag?.includes('=') ? Number(levelFlag.split('=')[1]) : undefined,
143
148
  };
144
149
 
150
+ const soundFontUsage = soundFontUsageFromFlag(
151
+ argv.find((a) => a.startsWith('--soundfont-usage='))?.slice('--soundfont-usage='.length),
152
+ );
153
+
145
154
  const result = await emitPage({
146
155
  dist,
147
156
  assets: hasAssets ? assetsDir : undefined,
@@ -153,6 +162,8 @@ async function main(argv) {
153
162
  toWebp: argv.includes('--to-webp'),
154
163
  webpLossless: !webpLossyFlag,
155
164
  webpQuality: webpLossyFlag?.includes('=') ? Number(webpLossyFlag.split('=')[1]) : 90,
165
+ soundFont: !argv.includes('--no-soundfont-subset'),
166
+ soundFontUsage,
156
167
  csp: argv.includes('--no-csp')
157
168
  ? false
158
169
  : { connect: argv.filter((a) => a.startsWith('--connect=')).map((a) => a.slice('--connect='.length)) },
@@ -168,6 +179,13 @@ async function main(argv) {
168
179
  `${kb(result.rawBytes)} -> ${kb(result.embeddedBytes)} embedded`,
169
180
  );
170
181
  }
182
+ for (const font of result.soundFonts ?? []) {
183
+ console.log(
184
+ font.programs === null
185
+ ? ` ${font.key}: SoundFont embedded whole (${kb(font.rawBytes)}), no shipped MIDI to subset against`
186
+ : ` ${font.key}: SoundFont subset to ${font.programs} program(s), ${kb(font.rawBytes)} -> ${kb(font.embeddedBytes)}`,
187
+ );
188
+ }
171
189
  console.log(' open index.html directly - no server needed');
172
190
  if (result.compressed) {
173
191
  for (const row of result.compressed) {
package/tools/vite.mjs CHANGED
@@ -14,6 +14,8 @@ import { buildArtifactSbom, serialize, shippedFiles } from './sbom.mjs';
14
14
  * build. `assetFiles: ['characters/hero.svg', 'music/theme.ogg']` embeds only those paths;
15
15
  * omit it for all assets, or pass [] for none. An unknown entry fails by name. Filtering
16
16
  * affects the build only, so the dev server still serves the asset folder normally.
17
+ * An embedded `.sf2` is subset at build time to the programs the embedded `.mid` files
18
+ * use (`soundFont: false` embeds it whole, `soundFontUsage` names the programs instead).
17
19
  * At the end of the build it runs `emitPage` over the output folder with the same
18
20
  * options. A setting the game's own config gives explicitly still wins, since vite merges a
19
21
  * plugin's `config` result under the user's.