@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 +20 -0
- package/data/microgm-opt.PROVENANCE.md +20 -0
- package/data/microgm-opt.sf2 +0 -0
- package/dist/mw_games.global.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -1
- package/sbom.cdx.json +6 -6
- package/tools/compile-resources.d.mts +22 -0
- package/tools/compile-resources.mjs +115 -3
- package/tools/emit-page.d.mts +7 -1
- package/tools/emit-page.mjs +20 -2
- package/tools/vite.mjs +2 -0
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
|
package/dist/mw_games.global.js
CHANGED
|
@@ -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&<(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&<(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.
|
|
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.
|
|
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.
|
|
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:
|
|
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.
|
|
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.
|
|
19
|
+
"bom-ref": "pkg:npm/%40datamoc/mw_games@0.40.0",
|
|
20
20
|
"name": "@datamoc/mw_games",
|
|
21
|
-
"version": "0.
|
|
22
|
-
"purl": "pkg:npm/%40datamoc/mw_games@0.
|
|
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.
|
|
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
|
|
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}`}), ` +
|
package/tools/emit-page.d.mts
CHANGED
|
@@ -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
|
}
|
package/tools/emit-page.mjs
CHANGED
|
@@ -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.
|