@cyberart-io/engine 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +193 -11
- package/dist/headless.d.ts +637 -0
- package/dist/headless.js +8 -0
- package/dist/index.d.ts +753 -8
- package/dist/index.js +1 -1
- package/docs/asset-resolver.md +155 -0
- package/docs/deterministic-mode.md +132 -0
- package/docs/events.md +272 -0
- package/docs/headless-harness.md +186 -0
- package/docs/presentation-adapter.md +150 -0
- package/docs/presentation-cue.md +82 -0
- package/package.json +15 -3
package/dist/index.js
CHANGED
|
@@ -5,4 +5,4 @@
|
|
|
5
5
|
* Not an OSI open-source license.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
var Ft=(e,t)=>()=>(e&&(t=e(e=0)),t);var jt={};function Kt(e,t){let n=e.indexOf(yt);if(n===-1)return null;let r=n+yt.length,i=`const __cyberSkip=new Set(${JSON.stringify(t)});const __cyberOrig=registerProcessor;registerProcessor=(n,p)=>__cyberSkip.has(n)?undefined:__cyberOrig(n,p);`;return e.slice(0,r)+i+e.slice(r)}var Y,U,Ut,_,yt,wt=Ft(()=>{"use strict";Y=/AudioWorkletProcessor with name:\s*["'`][^"'`]+["'`]\s+is already registered/i,U="__cyberartWorkletPatchApplied";typeof window<"u"&&!window[U]&&(window.addEventListener("error",e=>{let t=e?.message??"";Y.test(t)&&(e.preventDefault(),e.stopImmediatePropagation())},!0),window.addEventListener("unhandledrejection",e=>{let t=e?.reason,n=typeof t=="string"?t:t?.message??"";Y.test(n)&&e.preventDefault()}),window[U]=!0);Ut=!1,_=(...e)=>{Ut&&console.log("[workletPatch]",...e)},yt="((AudioWorkletProcessor,registerProcessor)=>{";if(typeof AudioWorklet<"u"&&!AudioWorklet.prototype[U]){let e=new WeakMap,t=/registerProcessor\s*\(\s*['"`]([^'"`]+)['"`]/g,n=AudioWorklet.prototype.addModule,r=0;AudioWorklet.prototype.addModule=async function(i,s){let o=++r,u=e.get(this);u||(u=new Set,e.set(this,u)),_(`#${o} addModule`,{url:i.slice(0,64),isBlob:i.startsWith("blob:"),knownBefore:[...u]});let l=i,c=null;if(i.startsWith("blob:")){let h=null;try{h=await(await fetch(i)).text()}catch(d){_(`#${o} blob fetch failed`,d)}if(h!==null){let d=new Set;for(let m of h.matchAll(t))d.add(m[1]);if(d.size>0){let m=[...d].filter(C=>u.has(C)),S=[...d].filter(C=>!u.has(C));if(m.length>0&&S.length===0){_(`#${o} short-circuit \u2014 all processors already registered`,m);return}if(m.length>0&&S.length>0){let C=Kt(h,m);if(C!==null){let f=new Blob([C],{type:"application/javascript"});c=URL.createObjectURL(f),l=c,_(`#${o} rewrote blob; skipping`,m,"allowing",S)}else _(`#${o} blob rewrite failed; passing original through`)}S.forEach(C=>u.add(C))}}}return n.call(this,l,s).then(()=>{c&&URL.revokeObjectURL(c)},h=>{c&&URL.revokeObjectURL(c);let d=(h&&h.message)??"";if(!(Y.test(d)||/already registered/i.test(d)))throw h})},AudioWorklet.prototype[U]=!0}});var dt=()=>{let e=["ms","moz","webkit","o"];for(let t=0;t<e.length&&!window.requestAnimationFrame;++t)window.requestAnimationFrame=window[e[t]+"RequestAnimationFrame"],window.cancelAnimationFrame=window[e[t]+"CancelAnimationFrame"]||window[e[t]+"CancelRequestAnimationFrame"]},b=window.devicePixelRatio||1,k=/Headless/i.test(navigator.userAgent),Ae=/Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent)||/Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.platform);function ut(){return Math.min(window.innerWidth,window.innerHeight)}var O="data-cyberart-canvas",T=class{constructor(t,n,r=1,a={}){this.width=t,this.height=n;let i=a.container,s=a.adoptFromDocument??!i,o=i?i.querySelector(`canvas[${O}]`)??i.querySelector("canvas"):null;if(o)this.canvas=o,this.adopted=!0;else if(s){let u=document.getElementById("canvas")??document.querySelector("canvas");u?(this.canvas=u,this.adopted=!0):(this.canvas=document.createElement("canvas"),this.canvas.setAttribute("id","canvas"),this.adopted=!1)}else this.canvas=document.createElement("canvas"),this.adopted=!1;this.canvas.hasAttribute(O)||this.canvas.setAttribute(O,""),this.canvas.style.display="block",this.canvas.style.margin="auto",this.ctx=this.canvas.getContext("2d",{colorSpace:"display-p3",alpha:!1}),this.ctx.globalAlpha=r,this.ctx.imageSmoothingEnabled=!1,this.setSize(t,n)}setSize(t,n){this.width=t,this.height=n,this.canvas.width=t,this.canvas.height=n,this.canvas.style.width=`${t/b}px`,this.canvas.style.height=`${n/b}px`,this.imageData=this.ctx.createImageData(t,n),this.ctx.putImageData(this.imageData,0,0)}_blank(t=1){let n=t?Math.max(t,.03):1;this.ctx.fillStyle=`rgba(0,0,0,${n})`,this.ctx.fillRect(0,0,this.width,this.height)}clearAll(){this.ctx.save(),this.ctx.globalAlpha=1,this._blank(),this.ctx.restore()}};function ct(e,t){let n=e/t,r,a;return n>1?(r=e,a=~~(e/n)):(a=t,r=~~(t*n)),{width:r,height:a,iWidth:1/r,iHeight:1/a,area:r*a,largeDim:Math.max(r,a),smallDim:Math.min(r,a),aspectRatio:n}}function ht(e,t=window.innerWidth,n=window.innerHeight){let r=ut(),a,i;return n<t?e>1?t>=r*e?(a=r*e,i=r):(a=t,i=a/e):(a=r*e,i=r):e>1?(a=r,i=r/e):n<r/e?(i=n,a=i*e):(a=r,i=r/e),[a,i]}var H=class{constructor(t=!1,n=!0){this.debugMode=t,this.actionMap={},this.checkKeypress=this.checkKeypress.bind(this),this.registerAction=this.registerAction.bind(this),n&&window.addEventListener("keydown",this.checkKeypress,!1)}checkKeypress({key:t}){this.debugMode&&console.log(`Pressed ${t}`),t in this.actionMap&&this.actionMap[t]()}registerAction(t,n,r=!1){if(t.length!==1)throw new Error(`Only single-character actions may be registered. Cannot register: ${t}`);if(t in this.actionMap&&!r)throw new Error(`Action already exists for code: ${t}`);this.actionMap[t]=n}destroy(){window.removeEventListener("keydown",this.checkKeypress,!1),this.actionMap={}}},lt=H;var B=class{constructor(t){this.x=-1;this.y=-1;this.isDown=!1;this.clicks=[];this.canvas=t,this.onPointerDown=this.onPointerDown.bind(this),this.onPointerMove=this.onPointerMove.bind(this),this.onPointerUp=this.onPointerUp.bind(this),t.addEventListener("pointerdown",this.onPointerDown),t.addEventListener("pointermove",this.onPointerMove),t.addEventListener("pointerup",this.onPointerUp)}toCanvasCoords(t){let n=this.canvas.getBoundingClientRect();return{x:(t.clientX-n.left)*(this.canvas.width/n.width),y:(t.clientY-n.top)*(this.canvas.height/n.height)}}onPointerDown(t){let{x:n,y:r}=this.toCanvasCoords(t);this.x=n,this.y=r,this.isDown=!0,this.clicks.push({x:n,y:r})}onPointerMove(t){let{x:n,y:r}=this.toCanvasCoords(t);this.x=n,this.y=r}onPointerUp(t){let{x:n,y:r}=this.toCanvasCoords(t);this.x=n,this.y=r,this.isDown=!1}hasClick(){return this.clicks.length>0}consumeClick(){return this.clicks.shift()??null}destroy(){this.canvas.removeEventListener("pointerdown",this.onPointerDown),this.canvas.removeEventListener("pointermove",this.onPointerMove),this.canvas.removeEventListener("pointerup",this.onPointerUp)}},pt=B;var mt=(e,t,n,r)=>({now:e,startTime:t,elapsedSinceStart:e-t,deltaSinceLastUpdate:n===null?0:e-n,deltaSinceLastRender:r===null?0:e-r}),N=class{constructor(t,n,r,a,i,s={}){this.startTime=0;this.lastUpdateAt=null;this.lastRenderAt=null;this.clockOffsetMs=0;this.pauseStartedAt=null;this.renderAnimation=this.renderAnimation.bind(this),this.dimensionContext=ct(t,n),this.R=r,this.gameManager=s.gameManager,this.hostChannel=s.hostChannel,this.canvas=new T(this.dimensionContext.width,this.dimensionContext.height,1,s.canvasMount),this.drawingContext=this.canvas.ctx,this.keyboardManager=new lt(!1,s.captureKeyboard??!0),this.pointerManager=new pt(this.canvas.canvas),this.animationCart=i;let o=s.customState;this.featureState=i.getDefaultFeatureState?.(this.R,this.dimensionContext,a,this.keyboardManager,o,this.pointerManager,this.gameManager,this.hostChannel),this.cartState=i.getDefaultState(this.R,this.dimensionContext,a,this.keyboardManager,o,this.pointerManager,this.gameManager,this.featureState,this.hostChannel),this.startTime=performance.now(),this.canvas.clearAll()}captureFramebuffer(){let t=this.canvas.imageData,n=new ImageData(t.width,t.height);return n.data.set(t.data),n}restoreFramebuffer(t){let n=this.canvas.imageData;return n.width!==t.width||n.height!==t.height||n.data.length!==t.data.length?!1:(n.data.set(t.data),this.drawingContext.putImageData(n,0,0),!0)}setPaused(t){let n=performance.now();if(t){this.pauseStartedAt===null&&(this.pauseStartedAt=n);return}this.pauseStartedAt!==null&&(this.clockOffsetMs+=n-this.pauseStartedAt,this.pauseStartedAt=null)}resolveNow(t){let n=this.pauseStartedAt!==null?t-this.pauseStartedAt:0;return t-this.clockOffsetMs-n}getElapsedSinceStart(t=performance.now()){return Math.max(0,this.resolveNow(t)-this.startTime)}restoreClock(t,n){this.pauseStartedAt=null,this.lastUpdateAt=null,this.lastRenderAt=null;let r=Math.max(0,t);if(typeof n=="number"&&Number.isFinite(n)){this.startTime=n,this.clockOffsetMs=performance.now()-n-r;return}this.clockOffsetMs=0,this.startTime=performance.now()-r}async updateAnimationState(t,n,r=performance.now()){let a=this.resolveNow(r),i=mt(a,this.startTime,this.lastUpdateAt,this.lastRenderAt);this.cartState=this.animationCart.update(this.R,t,n,this.dimensionContext,this.cartState,this.keyboardManager,this.pointerManager,this.gameManager,i,this.featureState,this.hostChannel),this.lastUpdateAt=a}async renderAnimation(t,n,r=performance.now()){let a=this.resolveNow(r),i=mt(a,this.startTime,this.lastUpdateAt,this.lastRenderAt);this.animationCart.render(this.R,t,n,this.dimensionContext,this.cartState,this.drawingContext,this.canvas.imageData,this.pointerManager,this.gameManager,i,this.featureState,this.hostChannel),this.lastRenderAt=a}getCanvas(){return this.canvas.canvas}getDrawDetails(){return[this.canvas.canvas,0,0,this.dimensionContext.width,this.dimensionContext.height]}teardown(){try{this.animationCart.teardown?.(this.cartState,this.featureState)}catch(t){console.error("Error during cart teardown",t)}this.keyboardManager.destroy(),this.pointerManager.destroy()}};var W=class extends T{constructor(t,n,r=1,a=!0,i={}){if(super(t,n,r,i),this.adopted)return;let s=i.container;if(s){this.canvas.parentNode!==s&&s.appendChild(this.canvas);return}if(a){let o=document.getElementById("canvas");o&&o!==this.canvas&&o.parentNode===document.body&&document.body.removeChild(o),document.body.appendChild(this.canvas)}}};var I=class{constructor(t){this.useA=!1;let n=function(r){let a=parseInt(r.substring(0,8),16),i=parseInt(r.substring(8,8),16),s=parseInt(r.substring(16,8),16),o=parseInt(r.substring(24,8),16);return function(){a|=0,i|=0,s|=0,o|=0;let u=(a+i|0)+o|0;return o=o+1|0,a=i^i>>>9,i=s+(s<<3)|0,s=s<<21|s>>>11,s=s+u|0,(u>>>0)/4294967296}};this.prngA=n(t.hash.substring(2,32)),this.prngB=n(t.hash.substring(34,32));for(let r=0;r<1e6;r+=2)this.prngA(),this.prngB()}r_zero_one(){return this.useA=!this.useA,this.useA?this.prngA():this.prngB()}dec(t,n){return t===void 0?this.r_zero_one():n===void 0?t*this.r_zero_one():t+(n-t)*this.r_zero_one()}int(t,n){return n===void 0&&(n=t,t=0),Math.floor(this.dec(t,n))}bool(t=.5){return this.r_zero_one()<t}sign(){return this.bool()?1:-1}choose(t){return t[this.int(t.length)]}};var gt="bafybeicjwcq5lxxtfnyj4p2ugcr7ctyb5wl62cbcechj6tsabdq5whcs7u",ft="https://ipfs.io/ipfs/";function Ot(e){return e.endsWith("/")?e:`${e}/`}var L=null;function Ht(e){return[...Array(e)].map(()=>Math.floor(Math.random()*16).toString(16)).join("")}function Bt(e){return e&&e.length>0?e:[{cid:gt}]}function Nt(e){return e.startsWith("0x")?e:`0x${e}`}function Wt(e){if(!e&&typeof tokenData<"u"&&tokenData?.hash)return{hash:tokenData.hash,tokenId:String(tokenData.tokenId),externalAssetDependencies:Bt(tokenData.externalAssetDependencies),preferredIPFSGateway:Ot(tokenData.preferredIPFSGateway||ft),preferredArweaveGateway:tokenData.preferredArweaveGateway};let t=typeof window<"u"?new URLSearchParams(window.location.search):new URLSearchParams;return{hash:e?Nt(e):t.get("hash")||`0x${Ht(64)}`,tokenId:"18009999",externalAssetDependencies:[{cid:gt}],preferredIPFSGateway:ft}}function q(e){let t=e?.useCache!==!1;if(t&&L&&!e?.hash)return L;let n=Wt(e?.hash);return t?L=n:L||(L=n),n}function bt(e){return new Array(32).fill(null).map((t,n)=>parseInt(e.hash.slice(2+n*2,4+n*2),16))}async function J(){let e=await import("tone");if(typeof e.start=="function")return e;let t=e.default;return t&&typeof t.start=="function"?t:e}var St={id:"tone",async load(){await Promise.resolve().then(()=>(wt(),jt))},async unlock(){await St.load(),await(await J()).start()},async resumeIfSuspended(){let e=await J();e.context.state==="suspended"&&await e.context.resume()},async suspendIfRunning(){let t=(await J()).context.rawContext;"suspend"in t&&t.state==="running"&&await t.suspend()}},Ct={tone:St};function X(e){if(!e)return[];let t=Array.isArray(e)?e:[e],n=[];for(let r of t){if(!(r in Ct)){console.warn(`Unknown audio library "${r}"`);continue}let a=r;n.includes(a)||n.push(a)}return n}function Q(e){return e.map(t=>Ct[t])}var K="$cyberartTa",g=class extends Error{constructor(t){super(t),this.name="IncompatibleCartStateError"}},Z={Float32Array,Float64Array,Int8Array,Int16Array,Int32Array,Uint8Array,Uint8ClampedArray,Uint16Array,Uint32Array};function Vt(e){for(let t of Object.keys(Z))if(e instanceof Z[t])return t}function At(e){return ArrayBuffer.isView(e)&&!(e instanceof DataView)}function y(e){if(e===null||typeof e!="object"||Array.isArray(e))return!1;let t=Object.getPrototypeOf(e);return t===Object.prototype||t===null}function tt(e){return y(e)?typeof e[K]=="string"&&Array.isArray(e.d):!1}function $t(e){return e==null?!1:typeof e=="function"?!0:!(typeof e!="object"||Array.isArray(e)||At(e)||y(e))}var Gt=new Set(["audioContextStarted","midiSynths","keepRatioPrimed","audioInitStarted","musicSeekApplied"]);function vt(e){return e===null?"null":tt(e)?String(e[K]):Array.isArray(e)?"array":y(e)?"object":typeof e}function zt(e,t){return e===t||e==="null"||t==="null"}function A(e){return e.trim().toLowerCase().replace(/^0x/,"")}function Et(e,t,n,r){if(!(!n&&!r)&&A(e)!==A(t))throw new g(`Generative cart state is for seed "${t}", but the live token is "${e}"`)}function P(e){return typeof e=="number"&&Number.isFinite(e)&&e>0}function xt(e,t){let n=e.dimensions;if(n&&P(n.width)&&P(n.height))return n;if(t&&P(t.width)&&P(t.height))return{width:t.width,height:t.height};if(y(e.state)){let r=e.state.containerWidth,a=e.state.containerHeight;if(P(r)&&P(a))return{width:r,height:a}}return null}function D(e,t=new WeakSet){if(e===null||typeof e=="string"||typeof e=="boolean")return e;if(typeof e=="number")return Number.isFinite(e)?e:null;if(!(typeof e=="function"||typeof e>"u"||typeof e=="bigint"||typeof e=="symbol")){if(At(e)){let n=Vt(e);return n?{[K]:n,d:Array.from(e)}:void 0}if(typeof e=="object"&&!t.has(e)){t.add(e);try{if(Array.isArray(e)){let a=[],i=0;for(let s of e){let o=D(s,t);o!==void 0&&(a.push(o),i++)}return e.length>0&&i===0?void 0:a}if(!y(e)||$t(e))return;let n=Object.keys(e),r={};for(let a of n){if(Gt.has(a))continue;let i=D(e[a],t);i!==void 0&&(r[a]=i)}return n.length>0&&Object.keys(r).length===0?void 0:r}finally{t.delete(e)}}}}function F(e){if(tt(e)){let t=Z[e[K]];return t?new t(e.d.map(n=>Number(n))):e}if(Array.isArray(e))return e.map(t=>F(t));if(y(e)){let t={};for(let n of Object.keys(e))t[n]=F(e[n]);return t}return e}function et(e,t,n,r,a=!0){if(a&&n&&r&&n!==r)throw new g(`Cart state is for "${r}", but the live cart is "${n}"`);if(!y(t)){if(a)throw new g("Cart state snapshot is empty");return}let i=Object.keys(t);if(i.length===0){if(a)throw new g("Cart state snapshot is empty");return}let s=D(e);if(!y(s)||Object.keys(s).length===0){if(a)throw new g("Live cart has no serializable state");return}let o=i.filter(u=>u in s);if(o.length===0){if(a)throw new g("Cart state snapshot does not overlap the live cart shape");return}for(let u of o){let l=vt(s[u]),c=vt(t[u]);if(!zt(l,c))throw new g(`Cart state field "${u}" has type ${c}, live cart expects ${l}`);if(l==="object"){let h=t[u],d=e[u];y(h)&&!tt(h)&&et(d,h,void 0,void 0,!1)}}}function kt(e,t){if(!y(e)||!y(t))return;let n=D(e);if(y(n))for(let r of Object.keys(t))r in n&&(e[r]=F(t[r]))}function Tt(e){let t=typeof e=="string"?JSON.parse(e):e;if(!t||typeof t!="object")throw new g("Cart state bundle is not an object");if(t.version!==1)throw new g(`Unsupported cart state version ${String(t.version)}`);if(typeof t.seed!="string"||typeof t.framesElapsed!="number")throw new g("Cart state bundle is missing seed or framesElapsed");return t}function Yt(e){if(!e||typeof e!="object")return 0;let t=e;return typeof t.startTime=="number"&&typeof t.lastSimulationUpdateAt=="number"?Math.max(0,t.lastSimulationUpdateAt-t.startTime):0}function Jt(e,t){if(typeof window.resizeTo!="function")return!1;try{if(window.top!==window)return!1}catch{return!1}let n=Math.max(1,Math.round(e)),r=Math.max(1,Math.round(t)),a=Math.max(0,window.outerWidth-window.innerWidth),i=Math.max(0,window.outerHeight-window.innerHeight);try{window.resizeTo(n+a,r+i)}catch{return!1}return Math.abs(window.innerWidth-n)<=8&&Math.abs(window.innerHeight-r)<=8}var Xt=[[0,0],[16,9],[9,16],[4,3],[3,4],[2,1],[1,2],[3,1],[1,3],[1,1]],rt=Xt[0],Qt=rt[0]/rt[1],nt=30,Zt=120,te=3840/2160,ee=2160/3840,j=class{constructor(t=nt,n=!1,r=window.innerWidth,a=window.innerHeight,i=!0,s,o={}){this.framesElapsed=0;this.frameRate=nt;this._paused=!1;this.savedToken=!1;this.prepared=!1;this.loopRunning=!1;this.fallbackAudio=[];this.audioLibrariesInternal=[];this.consecutiveErrorCount=0;this.lastErrorLogAt=0;this.reinitStayPaused=!1;this.reinitPending=!1;this.lastExportedFramebuffer=null;this.pinnedOutput=null;this.pinViewportAtImport=null;this.importing=!1;this.drawLoop=this.drawLoop.bind(this),this.loadCart=this.loadCart.bind(this),this.prepareCart=this.prepareCart.bind(this),this.beginPlayback=this.beginPlayback.bind(this),this.reinit=this.reinit.bind(this),this.tuneFramerate=this.tuneFramerate.bind(this),this.container=o.container,this.seed=o.seed,this.captureKeyboard=o.captureKeyboard??!0,this.customState=o.customState,this.hostChannel=o.hostChannel,this.fallbackAudio=X(o.audio),this.audioLibrariesInternal=this.fallbackAudio,this.isBodyCanvas=this.container?!1:i,this.autoMode=n,this.aspectRatio=Qt,this.selectedAsp=rt,this.isFullscreen=!!document.fullscreenElement,this.updateFrameRate(t),this.tempUnpause=!1,this.synchronous=k,this.containerWidth=r,this.containerHeight=a,this.gameManager=s,this.init(!0)}setGameManager(t){this.gameManager=t}reinit(t){if(!this.importing){if(this.shouldKeepPinnedOutput()){this.fitPinnedCanvas();return}this.reinitPending||(this.reinitStayPaused=this._paused,this.reinitPending=!0),this.paused=!0,clearTimeout(this.reinitTimeout),this.reinitTimeout=setTimeout(()=>{if(this.importing){this.paused=this.reinitStayPaused,this.reinitPending=!1;return}this.updateInProgress?this.reinit(t):(this.requestedAnimationFrame&&cancelAnimationFrame(this.requestedAnimationFrame),this.clearPinnedOutput(),this.init(),this.reloadCart(),this.paused=this.reinitStayPaused,this.reinitPending=!1)},150)}}initSeed(){this.tokenData=this.seed?q({hash:this.seed,useCache:!1}):q(),this.rawParams=bt(this.tokenData),this.mintNo=parseInt(this.tokenData.tokenId)%1e6,console.log("token",this.mintNo,this.tokenData.hash)}init(t=!1){t&&this.initSeed(),this.pureAspectUpdate(this.selectedAsp);let n=this.aspectRatio,[r,a]=ht(n,this.containerWidth,this.containerHeight);r*=b,a*=b,this.width=~~r,this.height=~~a,this.atCenterX=this.width/2,this.atCenterY=this.height/2,this.mainCanvas?this.mainCanvas.setSize(this.width,this.height):this.mainCanvas=new W(this.width,this.height,1,this.isBodyCanvas,this.canvasMountOptions())}loadCart(t){this.prepareCart(t)&&this.beginPlayback()}prepareCart(t){if(this.prepared)return!0;try{let n=~~(this.width/1),r=~~(this.height/this.width*n),a=new I(this.tokenData),i=new N(n,r,a,this.rawParams,t,{gameManager:this.gameManager,canvasMount:this.canvasMountOptions(),customState:this.customState,captureKeyboard:this.captureKeyboard,hostChannel:this.hostChannel});return this.animation=i,this.animationCart=t,this.paused=!1,this.framesElapsed=0,this.R=a,this.applyCartFrameRate(t,i),this.resolveAudioLibraries(t),this.prepared=!0,!0}catch(n){return console.error(n),!1}}applyCartFrameRate(t,n){let r=t.metadata,a=r?.frameRate??nt,i=n.cartState?.visualMode??n.featureState?.visualMode,s=i&&r?.frameRateByVisualMode?r.frameRateByVisualMode[i]:void 0;this.updateFrameRate(typeof s=="number"&&s>0?s:a)}get audioLibraries(){return this.audioLibrariesInternal}get needsAudio(){return this.audioLibrariesInternal.length>0}resolveAudioLibraries(t){this.audioLibrariesInternal=X(t.metadata?.audio)}async unlockAudio(){if(this.audioLibrariesInternal.length===0)return;let t=Q(this.audioLibrariesInternal);if(k){for(let n of t)await n.load();return}for(let n of t)await n.unlock()}async resumeAudioLibraries(){if(!(this.paused||k))for(let t of Q(this.audioLibrariesInternal))try{await t.resumeIfSuspended()}catch(n){console.error(`Failed to resume audio library "${t.id}" after reload`,n)}}beginPlayback(){!this.prepared||this.loopRunning||(this.prepared=!1,this.loopRunning=!0,this.consecutiveErrorCount=0,this.requestedAnimationFrame=requestAnimationFrame(this.drawLoop))}get isPrepared(){return this.prepared}get isLoopRunning(){return this.loopRunning}get paused(){return this._paused}set paused(t){this._paused!==t&&(this._paused=t,this.animation?.setPaused(t))}async waitUntilUpdateIdle(){for(;this.updateInProgress;)await new Promise(t=>{typeof requestAnimationFrame=="function"?requestAnimationFrame(()=>t()):setTimeout(t,0)})}requirePreparedAnimation(){if(!this.animation)throw new Error("No prepared cart");return this.animation}async exportState(){let t=this.requirePreparedAnimation(),n=this._paused;this.paused=!0,await this.waitUntilUpdateIdle();try{this.lastExportedFramebuffer=t.captureFramebuffer();let r=t.getCanvas();return{version:1,cartId:this.animationCart?.metadata?.id,generative:this.animationCart?.metadata?.generative,seed:this.tokenData.hash,framesElapsed:this.framesElapsed,elapsedSinceStart:t.getElapsedSinceStart(),dimensions:{width:r.width,height:r.height,cssWidth:this.containerWidth,cssHeight:this.containerHeight,dpr:b},state:D(t.cartState)}}finally{this.paused=n}}async exportStateJSON(){return JSON.stringify(await this.exportState())}peekExportedFramebuffer(){return this.lastExportedFramebuffer}peekSeed(){return this.tokenData?.hash}isGenerative(){return this.animationCart?.metadata?.generative===!0}async importState(t,n){let r=this.requirePreparedAnimation(),a=Tt(t),i=this._paused,s=this.loopRunning;this.paused=!0,await this.waitUntilUpdateIdle(),et(r.cartState,a.state,this.animationCart?.metadata?.id,a.cartId),Et(this.tokenData.hash,a.seed,this.animationCart?.metadata?.generative,a.generative),clearTimeout(this.reinitTimeout),this.reinitPending=!1,this.importing=!0;let o=xt(a,n?.framebuffer??this.lastExportedFramebuffer);if(o){this.pinOutputSize(o);let c=o.cssWidth??o.width/b,h=o.cssHeight??o.height/b;Jt(c,h),this.syncContainerFromDom(),this.pinViewportAtImport={w:this.containerWidth,h:this.containerHeight}}let u=this.customState,l=!1;try{if(this.customState=F(a.state),this.unloadCart(),!this.prepareCart(this.animationCart))throw new Error("Failed to prepare cart while loading state");this.animation.cartState!==this.customState&&kt(this.animation.cartState,a.state),this.framesElapsed=a.framesElapsed;let c=this.animation,h=c.cartState,d=typeof a.elapsedSinceStart=="number"?a.elapsedSinceStart:Yt(h);c.restoreClock(d,typeof h?.startTime=="number"?h.startTime:void 0),i&&c.setPaused(!0);let m=n?.framebuffer??this.lastExportedFramebuffer;m&&c.restoreFramebuffer(m)?h.keepRatioPrimed=!0:await c.renderAnimation(this.framesElapsed,this.rawParams),this.fitPinnedCanvas(),l=!0}catch(c){throw this.clearPinnedOutput(),this.customState=u,this.paused=i,this.animationCart&&(this.syncContainerFromDom(),this.init(),this.reloadCart()),c}finally{this.importing=!1,l&&(this.customState=u,this.paused=i,s&&this.beginPlayback())}}unloadCart(){this.loopRunning=!1,this.requestedAnimationFrame&&cancelAnimationFrame(this.requestedAnimationFrame),this.drawLoopTimeout&&clearTimeout(this.drawLoopTimeout),this.animation?.teardown(),this.animation=void 0,this.prepared=!1,this.audioLibrariesInternal=this.fallbackAudio,this.mainCanvas?.clearAll()}reloadCart(t=!1){let n=this.animationCart;if(!n)return;this.pinnedOutput&&(this.clearPinnedOutput(),this.syncContainerFromDom(),this.init());let r=this.reinitPending?this.reinitStayPaused:this._paused;this.unloadCart(),t&&this.initSeed(),this.loadCart(n),this.paused=r,!this.paused&&!k&&this.resumeAudioLibraries()}getFrameRate(){return this.frameRate}updateFrameRate(t){if(this.frameRate=t,t>=1)this.targetDrawTime=1e3/t;else{let r=1/(2-t);this.targetDrawTime=1e3/r}this.drawTime=this.targetDrawTime}canvasMountOptions(){return{container:this.container,adoptFromDocument:!this.container}}syncContainerFromDom(){if(this.container){this.containerWidth=this.container.clientWidth||this.containerWidth,this.containerHeight=this.container.clientHeight||this.containerHeight;return}this.containerWidth=window.innerWidth,this.containerHeight=window.innerHeight}pinOutputSize(t){this.pinnedOutput=t,this.width=~~t.width,this.height=~~t.height,this.atCenterX=this.width/2,this.atCenterY=this.height/2,this.aspectRatio=this.width/this.height,this.mainCanvas&&this.mainCanvas.setSize(this.width,this.height)}shouldKeepPinnedOutput(){return this.pinnedOutput?(this.syncContainerFromDom(),this.viewportMatches(this.pinnedCssSize())?!0:!!(this.pinViewportAtImport&&this.viewportMatches(this.pinViewportAtImport))):!1}pinnedCssSize(){let t=this.pinnedOutput;return{w:t.cssWidth??t.width/b,h:t.cssHeight??t.height/b}}viewportMatches(t){return Math.abs(this.containerWidth-t.w)<8&&Math.abs(this.containerHeight-t.h)<8}fitPinnedCanvas(){let t=this.pinnedOutput,n=this.canvas;if(!t||!n)return;this.container&&!this.container.style.position&&(this.container.style.position="relative");let r=this.containerWidth||window.innerWidth,a=this.containerHeight||window.innerHeight,i=t.width/b,s=t.height/b;if(i<1||s<1||r<1||a<1)return;let o=Math.min(r/i,a/s),u=i*o,l=s*o;n.style.position="absolute",n.style.left="50%",n.style.top="50%",n.style.transform="translate(-50%, -50%)",n.style.margin="0",n.style.width=`${u}px`,n.style.height=`${l}px`,n.style.imageRendering=Math.abs(o-1)<.01?"auto":"pixelated"}clearPinnedOutput(){let t=this.canvas;this.pinnedOutput=null,this.pinViewportAtImport=null,t&&(t.style.position="",t.style.left="",t.style.top="",t.style.transform="",t.style.imageRendering="",t.style.width="",t.style.height="",t.style.margin="auto")}get canvas(){return this.animation?.getCanvas()??this.mainCanvas?.canvas}destroy(){this.clearPinnedOutput(),this.unloadCart(),clearTimeout(this.reinitTimeout),this.mainCanvas&&!this.mainCanvas.adopted&&this.mainCanvas.canvas.remove()}drawIt(){}tuneFramerate(t){let n=this.lastCall||t;this.lastCall=t,this.drawTime=Math.max(0,this.drawTime+this.targetDrawTime-this.lastCall+n)}async drawLoop(t){if(!this.loopRunning)return;let n=!1,r,a="update";try{if(!this.paused||this.tempUnpause){if(k||this.tuneFramerate(t),!this.animation)return;if(a="update",this.updateInProgress=!0,await this.animation.updateAnimationState(this.framesElapsed,this.rawParams,t),this.updateInProgress=!1,a="render",await this.animation.renderAnimation(this.framesElapsed,this.rawParams,t),this.autoMode&&this.animation.cartState.saveToken&&!this.savedToken&&!new URLSearchParams(window.location.search).get("hash")){let o=localStorage.getItem("goodones"),u=JSON.stringify(o?[...JSON.parse(o),this.tokenData.hash]:[this.tokenData.hash]);localStorage.setItem("goodones",u),this.savedToken=!0,localStorage.setItem(this.tokenData.hash,this.mainCanvas.canvas.toDataURL("image/png")),window.location.reload(),this.loopRunning=!1;return}this.framesElapsed++,this.tempUnpause&&(this.tempUnpause=!1)}a="draw",this.drawIt()}catch(i){n=!0,r=i,this.handleFrameError(i,a)}finally{this.updateInProgress=!1}n?(this.consecutiveErrorCount++,this.consecutiveErrorCount>=Zt&&(this.loopRunning=!1,this.onError?.(r,{phase:a,consecutive:this.consecutiveErrorCount,stopped:!0}))):this.consecutiveErrorCount=0,this.loopRunning&&(this.drawLoopTimeout=setTimeout(()=>{this.requestedAnimationFrame=requestAnimationFrame(this.drawLoop)},this.drawTime))}handleFrameError(t,n){let r=performance.now();r-this.lastErrorLogAt>=1e3&&(this.lastErrorLogAt=r,console.error(`Animation ${n} error:`,t)),this.consecutiveErrorCount===0&&this.onError?.(t,{phase:n,consecutive:1,stopped:!1})}pureAspectUpdate(t){if(t[0]===0&&t[1]===0){let r=this.containerWidth||window.innerWidth,a=this.containerHeight||window.innerHeight,i=r/a;this.aspectRatio=Math.min(te,Math.max(ee,i))}else this.aspectRatio=t[0]/t[1]}};var M=class{constructor(){this.inbound=[];this.listeners=[]}dispatch(t){this.inbound.push(t),this.inbound.length>32&&this.inbound.splice(0,this.inbound.length-32)}consume(){if(this.inbound.length===0)return[];let t=this.inbound;return this.inbound=[],t}emit(t){for(let n of this.listeners)n(t)}onEvent(t){return this.listeners.push(t),()=>{this.listeners=this.listeners.filter(n=>n!==t)}}clear(){this.inbound=[],this.listeners=[]}};function ne(e){dt();let{container:t,seed:n,captureKeyboard:r=!1,audio:a}=e,i=new M,s=!1,o=null,u,l=!1,c=t.clientWidth||window.innerWidth,h=t.clientHeight||window.innerHeight,d=new j(30,!1,c,h,!1,void 0,{container:t,seed:n,captureKeyboard:r,hostChannel:i,audio:a}),m=()=>{let f=t.clientWidth||window.innerWidth,v=t.clientHeight||window.innerHeight;return f===d.containerWidth&&v===d.containerHeight?!1:(d.containerWidth=f,d.containerHeight=v,!0)},S=null;return typeof ResizeObserver<"u"&&(S=new ResizeObserver(()=>{if(s||!o)return;m()&&l&&d.reinit()}),S.observe(t)),d.onError=(f,v)=>{u?.(f,v)},{get tokenData(){return d.tokenData},get onError(){return u},set onError(f){u=f},mount(f,v={}){if(s)throw new Error("createRuntime: cannot mount on a destroyed runtime");o?.destroy(),l=!1,d.customState=v.initialState,d.setGameManager(v.gameManager);let ot=v.onEvent?i.onEvent(v.onEvent):()=>{};if(!d.prepareCart(f))throw ot(),new Error("createRuntime: failed to prepare cart");let p=!1,z={async start(){if(!p){if(m()&&d.isPrepared&&(d.init(),d.unloadCart(),!d.prepareCart(f)))throw new Error("createRuntime: failed to prepare cart");d.needsAudio&&(await d.unlockAudio(),p)||(l=!0,d.beginPlayback())}},pause(){p||(d.paused=!0)},resume(){p||(d.paused=!1)},dispatch(w){p||i.dispatch(w)},snapshot(){if(p)return{seed:"",pngDataUrl:""};let w=d.canvas;return{seed:d.tokenData.hash,metadata:f.metadata,pngDataUrl:w?w.toDataURL("image/png"):""}},destroy(){p||(p=!0,l=!1,ot(),i.clear(),d.unloadCart(),o===z&&(o=null))},reload(){if(!p){if(!l){if(d.isPrepared&&(d.unloadCart(),!d.prepareCart(f)))throw new Error("createRuntime: failed to prepare cart");return}d.reloadCart()}},reinit(w){if(!p){if(!l){m();return}d.reinit(w)}},getCartState(){return d.animation?.cartState},exportState(){return p?Promise.reject(new Error("Cart handle has been destroyed")):d.exportState()},exportStateJSON(){return p?Promise.reject(new Error("Cart handle has been destroyed")):d.exportStateJSON()},async importState(w,_t){if(p)throw new Error("Cart handle has been destroyed");await d.importState(w,_t)},peekExportedFramebuffer(){return p?null:d.peekExportedFramebuffer()},peekSeed(){if(!p)return d.peekSeed()},isGenerative(){return p?!1:d.isGenerative()},get canvas(){return d.canvas},get paused(){return d.paused},set paused(w){p||(d.paused=w)},get tokenData(){return d.tokenData},get isPrepared(){return d.isPrepared},get isLoopRunning(){return d.isLoopRunning},get needsAudio(){return d.needsAudio},get audioLibraries(){return d.audioLibraries}};return o=z,z},async unlockAudio(){await d.unlockAudio()},destroy(){s||(s=!0,o?.destroy(),o=null,S?.disconnect(),S=null,i.clear(),d.destroy())}}}var V="cyberart.state.save",$="cyberart.state.load";function re(e,t,n={}){let r=n.persist,a=r?{persist:r}:void 0,i=()=>{t.emit({type:V,payload:a})},s=()=>{t.emit({type:$,payload:a})};e.registerAction("w",i,!0),e.registerAction("W",i,!0),e.registerAction("e",s,!0),e.registerAction("E",s,!0)}function Pt(e){if(e.type!==V&&e.type!==$)return;let t=e.payload?.persist;return t==="localStorage"?t:void 0}var ae="cyberart-io",ie=1,E="framebuffers";function at(e,t){return`${e||"unknown"}:${t?A(t):"none"}`}function Mt(e,t){return`${e||"unknown"}:${t||"none"}`}function it(){return new Promise((e,t)=>{let n=indexedDB.open(ae,ie);n.onupgradeneeded=()=>{let r=n.result;r.objectStoreNames.contains(E)||r.createObjectStore(E)},n.onsuccess=()=>e(n.result),n.onerror=()=>t(n.error??new Error("indexedDB open failed"))})}function se(e){let t=e.data;return t.buffer.slice(t.byteOffset,t.byteOffset+t.byteLength)}function Dt(e,t){return new Promise((n,r)=>{let i=e.transaction(E,"readonly").objectStore(E).get(t);i.onsuccess=()=>n(i.result),i.onerror=()=>r(i.error??new Error("indexedDB read failed"))})}async function Rt(e,t,n){if(typeof indexedDB>"u")return;let r=await it(),a={width:n.width,height:n.height,data:se(n)};try{await new Promise((i,s)=>{let o=r.transaction(E,"readwrite");o.oncomplete=()=>i(),o.onerror=()=>s(o.error??new Error("indexedDB write failed")),o.objectStore(E).put(a,at(e,t))})}finally{r.close()}}async function It(e,t){if(typeof indexedDB>"u")return null;let n=await it();try{let r=at(e,t),a=Mt(e,t),i=await Dt(n,r)??(r===a?void 0:await Dt(n,a));if(!i||!i.data||i.width<1||i.height<1)return null;let s=new ImageData(i.width,i.height);return s.data.set(new Uint8ClampedArray(i.data)),s}finally{n.close()}}async function Lt(e,t){if(typeof indexedDB>"u")return;let n=await it();try{await new Promise((r,a)=>{let i=n.transaction(E,"readwrite");i.oncomplete=()=>r(),i.onerror=()=>a(i.error??new Error("indexedDB delete failed"));let s=i.objectStore(E);s.delete(at(e,t)),s.delete(Mt(e,t))})}finally{n.close()}}var st="cyberart.state.",oe="Saved state",de="Loaded state",ue="Failed to save state",ce="Failed to load state",he="No saved state";function x(e,t){let n=e||"unknown";return t?st+n+"."+A(t):st+n}function le(e,t,n){return r=>{if(Pt(r)==="localStorage"){if(r.type===V){pe(e,t,n);return}r.type===$&&me(e,t,n)}}}function R(e){try{let t=JSON.parse(e);if(!t||typeof t!="object")return null;let n=t;if(typeof n.savedAt=="number"&&n.bundle&&typeof n.bundle=="object")return{savedAt:n.savedAt,bundle:n.bundle};let r=t;return typeof r.seed=="string"&&typeof r.version=="number"?{savedAt:0,bundle:r}:null}catch{return null}}async function pe(e,t,n){let r=e();if(r)try{let a=await r.exportState(),i=a.cartId??t?.();a.generative===!0?(Ce(i),G(x(i,a.seed),a)):G(x(i),a);let s=r.peekExportedFramebuffer?.();if(s)try{await Rt(i,a.seed,s)}catch(o){console.warn("Failed to save cart framebuffer",o)}n?.(oe,"transient")}catch(a){console.error("Failed to save cart state",a),n?.(ue,"transient")}}async function me(e,t,n){let r=e();if(!r)return;let a=t?.(),i=r.peekSeed?.(),s=r.isGenerative?.()===!0,o=we(a,s,i);if(!o){console.warn("No saved cart state for",a||"unknown",s&&i?`(hash ${i})`:""),n?.(he,"transient");return}try{let u=await It(a,o.bundle.seed).catch(()=>null);await r.importState(o.bundle,{framebuffer:u}),n?.(de,"transient")}catch(u){console.error("Failed to load cart state",u),n?.(ce,"transient")}}function G(e,t){let n=JSON.stringify({savedAt:Date.now(),bundle:t});fe(e,n)}function fe(e,t){for(;;)try{localStorage.setItem(e,t);return}catch(n){if(!ge(n)||!be(e))throw n}}function ge(e){if(!e||typeof e!="object")return!1;let t=e.name,n=e.code;return t==="QuotaExceededError"||t==="NS_ERROR_DOM_QUOTA_REACHED"||n===22||n===1014}function be(e){let t=null,n=1/0;for(let i of ye()){if(i===e)continue;let s=localStorage.getItem(i);if(!s)continue;let u=R(s)?.savedAt??0;u<n&&(n=u,t=i)}if(!t)return!1;let r=localStorage.getItem(t),a=r?R(r):null;return localStorage.removeItem(t),a?.bundle&&Lt(a.bundle.cartId,a.bundle.seed).catch(()=>{}),!0}function ye(){let e=[];for(let t=0;t<localStorage.length;t++){let n=localStorage.key(t);n&&n.startsWith(st)&&e.push(n)}return e}function we(e,t,n){if(t&&n){let a=localStorage.getItem(x(e,n));if(a)return R(a);let i=localStorage.getItem(x(e));if(!i)return null;let s=R(i);if(!s||!Se(s.bundle,n))return null;try{G(x(e,n),s.bundle)}catch{}return s}let r=localStorage.getItem(x(e));return r?R(r):null}function Se(e,t){return typeof e.seed=="string"&&A(e.seed)===A(t)}function Ce(e){let t=x(e),n=localStorage.getItem(t);if(!n)return;let r=R(n);if(r&&typeof r.bundle.seed=="string"){let a=x(e,r.bundle.seed);if(!localStorage.getItem(a))try{G(a,r.bundle)}catch{return}}localStorage.removeItem(t)}export{O as CYBERART_CANVAS_ATTR,M as HostChannel,g as IncompatibleCartStateError,H as KeyboardManager,B as PointerManager,I as Random,le as attachCartStatePersistence,ne as createRuntime,re as registerCartStateHotkeys};
|
|
8
|
+
var Mn=(e,t)=>()=>(e&&(t=e(e=0)),t);var $n={};function Kn(e,t){let n=e.indexOf(Kt);if(n===-1)return null;let r=n+Kt.length,a=`const __cyberSkip=new Set(${JSON.stringify(t)});const __cyberOrig=registerProcessor;registerProcessor=(n,p)=>__cyberSkip.has(n)?undefined:__cyberOrig(n,p);`;return e.slice(0,r)+a+e.slice(r)}var nt,je,Hn,ke,Kt,$t=Mn(()=>{"use strict";nt=/AudioWorkletProcessor with name:\s*["'`][^"'`]+["'`]\s+is already registered/i,je="__cyberartWorkletPatchApplied";typeof window<"u"&&!window[je]&&(window.addEventListener("error",e=>{let t=e?.message??"";nt.test(t)&&(e.preventDefault(),e.stopImmediatePropagation())},!0),window.addEventListener("unhandledrejection",e=>{let t=e?.reason,n=typeof t=="string"?t:t?.message??"";nt.test(n)&&e.preventDefault()}),window[je]=!0);Hn=!1,ke=(...e)=>{Hn&&console.log("[workletPatch]",...e)},Kt="((AudioWorkletProcessor,registerProcessor)=>{";if(typeof AudioWorklet<"u"&&!AudioWorklet.prototype[je]){let e=new WeakMap,t=/registerProcessor\s*\(\s*['"`]([^'"`]+)['"`]/g,n=AudioWorklet.prototype.addModule,r=0;AudioWorklet.prototype.addModule=async function(a,s){let u=++r,f=e.get(this);f||(f=new Set,e.set(this,f)),ke(`#${u} addModule`,{url:a.slice(0,64),isBlob:a.startsWith("blob:"),knownBefore:[...f]});let v=a,m=null;if(a.startsWith("blob:")){let h=null;try{h=await(await fetch(a)).text()}catch(S){ke(`#${u} blob fetch failed`,S)}if(h!==null){let S=new Set;for(let E of h.matchAll(t))S.add(E[1]);if(S.size>0){let E=[...S].filter(l=>f.has(l)),N=[...S].filter(l=>!f.has(l));if(E.length>0&&N.length===0){ke(`#${u} short-circuit \u2014 all processors already registered`,E);return}if(E.length>0&&N.length>0){let l=Kn(h,E);if(l!==null){let c=new Blob([l],{type:"application/javascript"});m=URL.createObjectURL(c),v=m,ke(`#${u} rewrote blob; skipping`,E,"allowing",N)}else ke(`#${u} blob rewrite failed; passing original through`)}N.forEach(l=>f.add(l))}}}return n.call(this,v,s).then(()=>{m&&URL.revokeObjectURL(m)},h=>{m&&URL.revokeObjectURL(m);let S=(h&&h.message)??"";if(!(nt.test(S)||/already registered/i.test(S)))throw h})},AudioWorklet.prototype[je]=!0}});var xt=()=>{let e=["ms","moz","webkit","o"];for(let t=0;t<e.length&&!window.requestAnimationFrame;++t)window.requestAnimationFrame=window[e[t]+"RequestAnimationFrame"],window.cancelAnimationFrame=window[e[t]+"CancelAnimationFrame"]||window[e[t]+"CancelRequestAnimationFrame"]},j=window.devicePixelRatio||1,de=/Headless/i.test(navigator.userAgent),pi=/Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent)||/Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.platform);function Rt(){return Math.min(window.innerWidth,window.innerHeight)}var _e="data-cyberart-canvas",ue=class{constructor(t,n,r=1,i={}){this.width=t,this.height=n;let a=i.container,s=i.adoptFromDocument??!a,u=a?a.querySelector(`canvas[${_e}]`)??a.querySelector("canvas"):null;if(u)this.canvas=u,this.adopted=!0;else if(s){let f=document.getElementById("canvas")??document.querySelector("canvas");f?(this.canvas=f,this.adopted=!0):(this.canvas=document.createElement("canvas"),this.canvas.setAttribute("id","canvas"),this.adopted=!1)}else this.canvas=document.createElement("canvas"),this.adopted=!1;this.canvas.hasAttribute(_e)||this.canvas.setAttribute(_e,""),this.canvas.style.display="block",this.canvas.style.margin="auto",this.ctx=this.canvas.getContext("2d",{colorSpace:"display-p3",alpha:!1}),this.ctx.globalAlpha=r,this.ctx.imageSmoothingEnabled=!1,this.setSize(t,n)}setSize(t,n){this.width=t,this.height=n,this.canvas.width=t,this.canvas.height=n,this.canvas.style.width=`${t/j}px`,this.canvas.style.height=`${n/j}px`,this.imageData=this.ctx.createImageData(t,n),this.ctx.putImageData(this.imageData,0,0)}_blank(t=1){let n=t?Math.max(t,.03):1;this.ctx.fillStyle=`rgba(0,0,0,${n})`,this.ctx.fillRect(0,0,this.width,this.height)}clearAll(){this.ctx.save(),this.ctx.globalAlpha=1,this._blank(),this.ctx.restore()}};function Dt(e,t){let n=e/t,r,i;return n>1?(r=e,i=~~(e/n)):(i=t,r=~~(t*n)),{width:r,height:i,iWidth:1/r,iHeight:1/i,area:r*i,largeDim:Math.max(r,i),smallDim:Math.min(r,i),aspectRatio:n}}function It(e,t=window.innerWidth,n=window.innerHeight){let r=Rt(),i,a;return n<t?e>1?t>=r*e?(i=r*e,a=r):(i=t,a=i/e):(i=r*e,a=r):e>1?(i=r,a=r/e):n<r/e?(a=n,i=a*e):(i=r,a=r/e),[i,a]}var Oe=class{constructor(t=!1,n=!0){this.debugMode=t,this.actionMap={},this.listening=n,this.checkKeypress=this.checkKeypress.bind(this),this.registerAction=this.registerAction.bind(this),this.listening&&window.addEventListener("keydown",this.checkKeypress,!1)}checkKeypress({key:t}){this.debugMode&&console.log(`Pressed ${t}`),t in this.actionMap&&this.actionMap[t]()}inject(t){this.checkKeypress({key:t})}registerAction(t,n,r=!1){if(t.length!==1)throw new Error(`Only single-character actions may be registered. Cannot register: ${t}`);if(t in this.actionMap&&!r)throw new Error(`Action already exists for code: ${t}`);this.actionMap[t]=n}destroy(){this.listening&&window.removeEventListener("keydown",this.checkKeypress,!1),this.actionMap={}}},Mt=Oe;var Le=class{constructor(t,n={}){this.x=-1;this.y=-1;this.isDown=!1;this.clicks=[];this.canvas=t,this.listening=n.listen!==!1,this.onPointerDown=this.onPointerDown.bind(this),this.onPointerMove=this.onPointerMove.bind(this),this.onPointerUp=this.onPointerUp.bind(this),this.listening&&(t.addEventListener("pointerdown",this.onPointerDown),t.addEventListener("pointermove",this.onPointerMove),t.addEventListener("pointerup",this.onPointerUp))}toCanvasCoords(t){let n=this.canvas.getBoundingClientRect();return{x:(t.clientX-n.left)*(this.canvas.width/n.width),y:(t.clientY-n.top)*(this.canvas.height/n.height)}}onPointerDown(t){let{x:n,y:r}=this.toCanvasCoords(t);this.x=n,this.y=r,this.isDown=!0,this.clicks.push({x:n,y:r})}onPointerMove(t){let{x:n,y:r}=this.toCanvasCoords(t);this.x=n,this.y=r}onPointerUp(t){let{x:n,y:r}=this.toCanvasCoords(t);this.x=n,this.y=r,this.isDown=!1}hasClick(){return this.clicks.length>0}consumeClick(){return this.clicks.shift()??null}inject(t,n,r){this.x=n,this.y=r,t==="down"?(this.isDown=!0,this.clicks.push({x:n,y:r})):t==="up"&&(this.isDown=!1)}destroy(){this.listening&&(this.canvas.removeEventListener("pointerdown",this.onPointerDown),this.canvas.removeEventListener("pointermove",this.onPointerMove),this.canvas.removeEventListener("pointerup",this.onPointerUp))}},Ft=Le;function ce(){return{now:()=>performance.now()}}function Ve(e=0){let t=e;return{now:()=>t,set(n){t=n},advance(n){t+=n}}}var _t=(e,t,n,r)=>({now:e,startTime:t,elapsedSinceStart:e-t,deltaSinceLastUpdate:n===null?0:e-n,deltaSinceLastRender:r===null?0:e-r}),Ne=class{constructor(t,n,r,i,a,s={}){this.startTime=0;this.lastUpdateAt=null;this.lastRenderAt=null;this.clockOffsetMs=0;this.pauseStartedAt=null;this.renderAnimation=this.renderAnimation.bind(this),this.dimensionContext=Dt(t,n),this.R=r,this.gameManager=s.gameManager,this.hostChannel=s.hostChannel,this.clock=s.clock??ce(),this.canvas=new ue(this.dimensionContext.width,this.dimensionContext.height,1,s.canvasMount),this.drawingContext=this.canvas.ctx,this.keyboardManager=new Mt(!1,s.captureKeyboard??!0),this.pointerManager=new Ft(this.canvas.canvas,{listen:s.listenToPointer!==!1}),this.animationCart=a;let u=s.customState;this.featureState=a.getDefaultFeatureState?.(this.R,this.dimensionContext,i,this.keyboardManager,u,this.pointerManager,this.gameManager,this.hostChannel),this.cartState=a.getDefaultState(this.R,this.dimensionContext,i,this.keyboardManager,u,this.pointerManager,this.gameManager,this.featureState,this.hostChannel),this.startTime=this.clock.now(),this.canvas.clearAll()}captureFramebuffer(){let t=this.canvas.imageData,n=new ImageData(t.width,t.height);return n.data.set(t.data),n}restoreFramebuffer(t){let n=this.canvas.imageData;return n.width!==t.width||n.height!==t.height||n.data.length!==t.data.length?!1:(n.data.set(t.data),this.drawingContext.putImageData(n,0,0),!0)}setPaused(t){let n=this.clock.now();if(t){this.pauseStartedAt===null&&(this.pauseStartedAt=n);return}this.pauseStartedAt!==null&&(this.clockOffsetMs+=n-this.pauseStartedAt,this.pauseStartedAt=null)}resolveNow(t){let n=this.pauseStartedAt!==null?t-this.pauseStartedAt:0;return t-this.clockOffsetMs-n}getElapsedSinceStart(t=this.clock.now()){return Math.max(0,this.resolveNow(t)-this.startTime)}restoreClock(t,n){this.pauseStartedAt=null,this.lastUpdateAt=null,this.lastRenderAt=null;let r=Math.max(0,t);if(typeof n=="number"&&Number.isFinite(n)){this.startTime=n,this.clockOffsetMs=this.clock.now()-n-r;return}this.clockOffsetMs=0,this.startTime=this.clock.now()-r}getRandomState(){return this.R.getState()}injectKey(t){this.keyboardManager.inject(t)}injectPointer(t,n,r){this.pointerManager.inject(t,n,r)}async updateAnimationState(t,n,r=this.clock.now()){let i=this.resolveNow(r),a=_t(i,this.startTime,this.lastUpdateAt,this.lastRenderAt);this.cartState=this.animationCart.update(this.R,t,n,this.dimensionContext,this.cartState,this.keyboardManager,this.pointerManager,this.gameManager,a,this.featureState,this.hostChannel),this.lastUpdateAt=i}async renderAnimation(t,n,r=this.clock.now()){let i=this.resolveNow(r),a=_t(i,this.startTime,this.lastUpdateAt,this.lastRenderAt);this.animationCart.render(this.R,t,n,this.dimensionContext,this.cartState,this.drawingContext,this.canvas.imageData,this.pointerManager,this.gameManager,a,this.featureState,this.hostChannel),this.lastRenderAt=i}getCanvas(){return this.canvas.canvas}getDrawDetails(){return[this.canvas.canvas,0,0,this.dimensionContext.width,this.dimensionContext.height]}teardown(){try{this.animationCart.teardown?.(this.cartState,this.featureState)}catch(t){console.error("Error during cart teardown",t)}this.keyboardManager.destroy(),this.pointerManager.destroy()}};var He=class extends ue{constructor(t,n,r=1,i=!0,a={}){if(super(t,n,r,a),this.adopted)return;let s=a.container;if(s){this.canvas.parentNode!==s&&s.appendChild(this.canvas);return}if(i){let u=document.getElementById("canvas");u&&u!==this.canvas&&u.parentNode===document.body&&document.body.removeChild(u),document.body.appendChild(this.canvas)}}};function Ot(e){let t=parseInt(e.substring(0,8),16),n=parseInt(e.substring(8,8),16),r=parseInt(e.substring(16,8),16),i=parseInt(e.substring(24,8),16);return{next:()=>{t|=0,n|=0,r|=0,i|=0;let s=(t+n|0)+i|0;return i=i+1|0,t=n^n>>>9,n=r+(r<<3)|0,r=r<<21|r>>>11,r=r+s|0,(s>>>0)/4294967296},getRegs:()=>({a:t,b:n,c:r,d:i}),setRegs:s=>{t=s.a,n=s.b,r=s.c,i=s.d}}}var Se=class{constructor(t){this.seed=t.hash,this.useA=!1,this.genA=Ot(t.hash.substring(2,32)),this.genB=Ot(t.hash.substring(34,32)),this.prngA=this.genA.next,this.prngB=this.genB.next;for(let n=0;n<1e6;n+=2)this.prngA(),this.prngB()}getState(){let t=this.genA.getRegs(),n=this.genB.getRegs();return{seed:this.seed,useA:this.useA,prngA:{a:t.a,b:t.b,c:t.c,d:t.d},prngB:{a:n.a,b:n.b,c:n.c,d:n.d}}}setState(t){if(t.seed!==this.seed)throw new Error(`Random.setState: seed ${t.seed} does not match ${this.seed}`);this.useA=t.useA,this.genA.setRegs(t.prngA),this.genB.setRegs(t.prngB)}r_zero_one(){return this.useA=!this.useA,this.useA?this.prngA():this.prngB()}dec(t,n){return t===void 0?this.r_zero_one():n===void 0?t*this.r_zero_one():t+(n-t)*this.r_zero_one()}int(t,n){return n===void 0&&(n=t,t=0),Math.floor(this.dec(t,n))}bool(t=.5){return this.r_zero_one()<t}sign(){return this.bool()?1:-1}choose(t){return t[this.int(t.length)]}};var Vt="bafybeicjwcq5lxxtfnyj4p2ugcr7ctyb5wl62cbcechj6tsabdq5whcs7u",Lt="https://ipfs.io/ipfs/";function Fn(e){return e.endsWith("/")?e:`${e}/`}var Ce=null;function _n(e){return[...Array(e)].map(()=>Math.floor(Math.random()*16).toString(16)).join("")}function On(e){return e&&e.length>0?e:[{cid:Vt}]}function Ln(e){return e.startsWith("0x")?e:`0x${e}`}var Nt=/^(0[xX])?[0-9a-fA-F]{64}$/;function Vn(e){let t="",n=2166136261,r=522970236;for(let i=0;t.length<64;i++){for(let a=0;a<e.length;a++)n^=e.charCodeAt(a)+i,n=Math.imul(n,16777619),r^=e.charCodeAt(a)+i*17,r=Math.imul(r,16777619);t+=(n>>>0).toString(16).padStart(8,"0"),t+=(r>>>0).toString(16).padStart(8,"0")}return`0x${t.slice(0,64)}`}function Ke(e){let t=typeof e=="number"?String(e):e;return Nt.test(t)?`0x${(t.startsWith("0x")||t.startsWith("0X")?t.slice(2):t).toLowerCase()}`:Vn(t)}function $e(e,t){if(typeof e=="number"||t==="deterministic")return Ke(e);let n=String(e);return Nt.test(n)?Ke(n):n.startsWith("0x")||n.startsWith("0X")?n:`0x${n}`}function Nn(e){if(!e&&typeof tokenData<"u"&&tokenData?.hash)return{hash:tokenData.hash,tokenId:String(tokenData.tokenId),externalAssetDependencies:On(tokenData.externalAssetDependencies),preferredIPFSGateway:Fn(tokenData.preferredIPFSGateway||Lt),preferredArweaveGateway:tokenData.preferredArweaveGateway};let t=typeof window<"u"?new URLSearchParams(window.location.search):new URLSearchParams;return{hash:e?Ln(e):t.get("hash")||`0x${_n(64)}`,tokenId:"18009999",externalAssetDependencies:[{cid:Vt}],preferredIPFSGateway:Lt}}function tt(e){let t=e?.useCache!==!1;if(t&&Ce&&!e?.hash)return Ce;let n=Nn(e?.hash);return t?Ce=n:Ce||(Ce=n),n}function Ht(e){return new Array(32).fill(null).map((t,n)=>parseInt(e.hash.slice(2+n*2,4+n*2),16))}async function rt(){let e=await import("tone");if(typeof e.start=="function")return e;let t=e.default;return t&&typeof t.start=="function"?t:e}var jt={id:"tone",async load(){await Promise.resolve().then(()=>($t(),$n))},async unlock(){await jt.load(),await(await rt()).start()},async resumeIfSuspended(){let e=await rt();e.context.state==="suspended"&&await e.context.resume()},async suspendIfRunning(){let t=(await rt()).context.rawContext;"suspend"in t&&t.state==="running"&&await t.suspend()}},Ut={tone:jt};function it(e){if(!e)return[];let t=Array.isArray(e)?e:[e],n=[];for(let r of t){if(!(r in Ut)){console.warn(`Unknown audio library "${r}"`);continue}let i=r;n.includes(i)||n.push(i)}return n}function at(e){return e.map(t=>Ut[t])}var Ue="$cyberartTa",$=class extends Error{constructor(t){super(t),this.name="IncompatibleCartStateError"}},ot={Float32Array,Float64Array,Int8Array,Int16Array,Int32Array,Uint8Array,Uint8ClampedArray,Uint16Array,Uint32Array};function jn(e){for(let t of Object.keys(ot))if(e instanceof ot[t])return t}function Wt(e){return ArrayBuffer.isView(e)&&!(e instanceof DataView)}function B(e){if(e===null||typeof e!="object"||Array.isArray(e))return!1;let t=Object.getPrototypeOf(e);return t===Object.prototype||t===null}function st(e){return B(e)?typeof e[Ue]=="string"&&Array.isArray(e.d):!1}function Un(e){return e==null?!1:typeof e=="function"?!0:!(typeof e!="object"||Array.isArray(e)||Wt(e)||B(e))}var Bn=new Set(["audioContextStarted","midiSynths","keepRatioPrimed","audioInitStarted","musicSeekApplied"]);function Bt(e){return e===null?"null":st(e)?String(e[Ue]):Array.isArray(e)?"array":B(e)?"object":typeof e}function Wn(e,t){return e===t||e==="null"||t==="null"}function ee(e){return e.trim().toLowerCase().replace(/^0x/,"")}function zt(e,t,n,r){if(!(!n&&!r)&&ee(e)!==ee(t))throw new $(`Generative cart state is for seed "${t}", but the live token is "${e}"`)}function le(e){return typeof e=="number"&&Number.isFinite(e)&&e>0}function qt(e,t){let n=e.dimensions;if(n&&le(n.width)&&le(n.height))return n;if(t&&le(t.width)&&le(t.height))return{width:t.width,height:t.height};if(B(e.state)){let r=e.state.containerWidth,i=e.state.containerHeight;if(le(r)&&le(i))return{width:r,height:i}}return null}function pe(e,t=new WeakSet){if(e===null||typeof e=="string"||typeof e=="boolean")return e;if(typeof e=="number")return Number.isFinite(e)?e:null;if(!(typeof e=="function"||typeof e>"u"||typeof e=="bigint"||typeof e=="symbol")){if(Wt(e)){let n=jn(e);return n?{[Ue]:n,d:Array.from(e)}:void 0}if(typeof e=="object"&&!t.has(e)){t.add(e);try{if(Array.isArray(e)){let i=[],a=0;for(let s of e){let u=pe(s,t);u!==void 0&&(i.push(u),a++)}return e.length>0&&a===0?void 0:i}if(!B(e)||Un(e))return;let n=Object.keys(e),r={};for(let i of n){if(Bn.has(i))continue;let a=pe(e[i],t);a!==void 0&&(r[i]=a)}return n.length>0&&Object.keys(r).length===0?void 0:r}finally{t.delete(e)}}}}function Pe(e){if(st(e)){let t=ot[e[Ue]];return t?new t(e.d.map(n=>Number(n))):e}if(Array.isArray(e))return e.map(t=>Pe(t));if(B(e)){let t={};for(let n of Object.keys(e))t[n]=Pe(e[n]);return t}return e}function dt(e,t,n,r,i=!0){if(i&&n&&r&&n!==r)throw new $(`Cart state is for "${r}", but the live cart is "${n}"`);if(!B(t)){if(i)throw new $("Cart state snapshot is empty");return}let a=Object.keys(t);if(a.length===0){if(i)throw new $("Cart state snapshot is empty");return}let s=pe(e);if(!B(s)||Object.keys(s).length===0){if(i)throw new $("Live cart has no serializable state");return}let u=a.filter(f=>f in s);if(u.length===0){if(i)throw new $("Cart state snapshot does not overlap the live cart shape");return}for(let f of u){let v=Bt(s[f]),m=Bt(t[f]);if(!Wn(v,m))throw new $(`Cart state field "${f}" has type ${m}, live cart expects ${v}`);if(v==="object"){let h=t[f],S=e[f];B(h)&&!st(h)&&dt(S,h,void 0,void 0,!1)}}}function Gt(e,t){if(!B(e)||!B(t))return;let n=pe(e);if(B(n))for(let r of Object.keys(t))r in n&&(e[r]=Pe(t[r]))}function Yt(e){let t=typeof e=="string"?JSON.parse(e):e;if(!t||typeof t!="object")throw new $("Cart state bundle is not an object");if(t.version!==1)throw new $(`Unsupported cart state version ${String(t.version)}`);if(typeof t.seed!="string"||typeof t.framesElapsed!="number")throw new $("Cart state bundle is missing seed or framesElapsed");return t}var Te="cyberart.asset.ready",xe="cyberart.asset.failed";function te(e){try{return JSON.parse(JSON.stringify(e))}catch{return e}}function W(e){try{return JSON.stringify(e)}catch{return String(e)}}function ut(e,t,n){let r=t??[],i=n??[],a=Math.max(r.length,i.length);for(let s=0;s<a;s++)if(W(r[s])!==W(i[s]))return`${e}[${s}]: ${W(r[s])} vs ${W(i[s])}`}function Jt(e,t){let n=[];e.seed!==t.seed&&n.push(`seed: ${e.seed} vs ${t.seed}`),e.clock.framesElapsed!==t.clock.framesElapsed&&n.push(`framesElapsed: ${e.clock.framesElapsed} vs ${t.clock.framesElapsed}`),e.clock.now!==t.clock.now&&n.push(`now: ${e.clock.now} vs ${t.clock.now}`),e.clock.frameRate!==t.clock.frameRate&&n.push(`frameRate: ${e.clock.frameRate} vs ${t.clock.frameRate}`),W(e.rng)!==W(t.rng)&&n.push(`rng: ${W(e.rng)} vs ${W(t.rng)}`);let r=ut("actions",e.actions,t.actions);r&&n.push(r);let i=ut("applied",e.applied,t.applied);i&&n.push(i);let a=ut("events",e.events,t.events);return a&&n.push(a),W(e.state)!==W(t.state)&&n.push(`state: ${W(e.state)} vs ${W(t.state)}`),n}function Xt(e){return{type:e.status==="ready"?Te:xe,kind:"state",payload:e.detail===void 0?{id:e.id}:{id:e.id,detail:e.detail}}}function qn(e){if(!e||typeof e!="object")return 0;let t=e;return typeof t.startTime=="number"&&typeof t.lastSimulationUpdateAt=="number"?Math.max(0,t.lastSimulationUpdateAt-t.startTime):0}function Gn(e,t){if(typeof window.resizeTo!="function")return!1;try{if(window.top!==window)return!1}catch{return!1}let n=Math.max(1,Math.round(e)),r=Math.max(1,Math.round(t)),i=Math.max(0,window.outerWidth-window.innerWidth),a=Math.max(0,window.outerHeight-window.innerHeight);try{window.resizeTo(n+i,r+a)}catch{return!1}return Math.abs(window.innerWidth-n)<=8&&Math.abs(window.innerHeight-r)<=8}var Yn=[[0,0],[16,9],[9,16],[4,3],[3,4],[2,1],[1,2],[3,1],[1,3],[1,1]],lt=Yn[0],Jn=lt[0]/lt[1],ct=30,Xn=120,Qn=3840/2160,Zn=2160/3840,Be=class{constructor(t=ct,n=!1,r=window.innerWidth,i=window.innerHeight,a=!0,s,u={}){this.framesElapsed=0;this.frameRate=ct;this._paused=!1;this.savedToken=!1;this.prepared=!1;this.loopRunning=!1;this.fallbackAudio=[];this.audioLibrariesInternal=[];this.virtualClock=null;this.scriptedActions=[];this.initialScriptedActions=[];this.appliedActions=[];this.consumedActionIndexes=new Set;this.outboundEvents=[];this.consecutiveErrorCount=0;this.lastErrorLogAt=0;this.reinitStayPaused=!1;this.reinitPending=!1;this.lastExportedFramebuffer=null;this.pinnedOutput=null;this.pinViewportAtImport=null;this.importing=!1;this.drawLoop=this.drawLoop.bind(this),this.loadCart=this.loadCart.bind(this),this.prepareCart=this.prepareCart.bind(this),this.beginPlayback=this.beginPlayback.bind(this),this.reinit=this.reinit.bind(this),this.tuneFramerate=this.tuneFramerate.bind(this),this.container=u.container,this.seed=u.seed,this.captureKeyboard=u.captureKeyboard??!0,this.customState=u.customState,this.hostChannel=u.hostChannel,this.fallbackAudio=it(u.audio),this.audioLibrariesInternal=this.fallbackAudio;let f=u.deterministic;this.deterministic=!!f;let v=f&&typeof f=="object"?f:{};if(this.deterministic){if(u.clock){let m=u.clock;if(typeof m.advance!="function")throw new Error("deterministic mode requires a virtual clock with advance()");this.virtualClock=m,this.clock=m}else this.virtualClock=Ve(v.origin??0),this.clock=this.virtualClock;this.initialScriptedActions=te(v.actions??[]),this.scriptedActions=te(this.initialScriptedActions)}else this.clock=u.clock??ce();this.isBodyCanvas=this.container?!1:a,this.autoMode=n,this.aspectRatio=Jn,this.selectedAsp=lt,this.isFullscreen=!!document.fullscreenElement,this.updateFrameRate(t),this.tempUnpause=!1,this.synchronous=de,this.containerWidth=r,this.containerHeight=i,this.gameManager=s,this.init(!0)}setGameManager(t){this.gameManager=t}reinit(t){if(!this.importing){if(this.shouldKeepPinnedOutput()){this.fitPinnedCanvas();return}this.reinitPending||(this.reinitStayPaused=this._paused,this.reinitPending=!0),this.paused=!0,clearTimeout(this.reinitTimeout),this.reinitTimeout=setTimeout(()=>{if(this.importing){this.paused=this.reinitStayPaused,this.reinitPending=!1;return}this.updateInProgress?this.reinit(t):(this.requestedAnimationFrame&&cancelAnimationFrame(this.requestedAnimationFrame),this.clearPinnedOutput(),this.init(),this.reloadCart(),this.paused=this.reinitStayPaused,this.reinitPending=!1)},150)}}initSeed(){this.tokenData=this.seed?tt({hash:this.seed,useCache:!1}):tt(),this.rawParams=Ht(this.tokenData),this.mintNo=parseInt(this.tokenData.tokenId)%1e6,console.log("token",this.mintNo,this.tokenData.hash)}init(t=!1){t&&this.initSeed(),this.pureAspectUpdate(this.selectedAsp);let n=this.aspectRatio,[r,i]=It(n,this.containerWidth,this.containerHeight);r*=j,i*=j,this.width=~~r,this.height=~~i,this.atCenterX=this.width/2,this.atCenterY=this.height/2,this.mainCanvas?this.mainCanvas.setSize(this.width,this.height):this.mainCanvas=new He(this.width,this.height,1,this.isBodyCanvas,this.canvasMountOptions())}loadCart(t){this.prepareCart(t)&&this.beginPlayback()}prepareCart(t){if(this.prepared)return!0;try{let n=~~(this.width/1),r=~~(this.height/this.width*n),i=new Se(this.tokenData),a=new Ne(n,r,i,this.rawParams,t,{gameManager:this.gameManager,canvasMount:this.canvasMountOptions(),customState:this.customState,captureKeyboard:this.deterministic?!1:this.captureKeyboard,hostChannel:this.hostChannel,clock:this.clock,listenToPointer:!this.deterministic});return this.animation=a,this.animationCart=t,this.paused=!1,this.framesElapsed=0,this.outboundEvents=[],this.scriptedActions=te(this.initialScriptedActions),this.appliedActions=[],this.consumedActionIndexes=new Set,this.outboundUnsubscribe?.(),this.outboundUnsubscribe=void 0,this.deterministic&&this.hostChannel&&(this.outboundUnsubscribe=this.hostChannel.onEvent(s=>{this.outboundEvents.push(s)})),this.R=i,this.applyCartFrameRate(t,a),this.resolveAudioLibraries(t),this.prepared=!0,!0}catch(n){return console.error(n),!1}}applyCartFrameRate(t,n){let r=t.metadata,i=r?.frameRate??ct,a=n.cartState?.visualMode??n.featureState?.visualMode,s=a&&r?.frameRateByVisualMode?r.frameRateByVisualMode[a]:void 0;this.updateFrameRate(typeof s=="number"&&s>0?s:i)}get audioLibraries(){return this.audioLibrariesInternal}get needsAudio(){return this.audioLibrariesInternal.length>0}resolveAudioLibraries(t){this.audioLibrariesInternal=it(t.metadata?.audio)}async unlockAudio(){if(this.audioLibrariesInternal.length===0)return;let t=at(this.audioLibrariesInternal);if(de){for(let n of t)await n.load();return}for(let n of t)await n.unlock()}async resumeAudioLibraries(){if(!(this.paused||de))for(let t of at(this.audioLibrariesInternal))try{await t.resumeIfSuspended()}catch(n){console.error(`Failed to resume audio library "${t.id}" after reload`,n)}}beginPlayback(){!this.prepared||this.loopRunning||(this.prepared=!1,this.loopRunning=!0,this.consecutiveErrorCount=0,!this.deterministic&&(this.requestedAnimationFrame=requestAnimationFrame(this.drawLoop)))}get isPrepared(){return this.prepared}get isLoopRunning(){return this.loopRunning}get isDeterministic(){return this.deterministic}getClock(){return{now:this.clock.now(),framesElapsed:this.framesElapsed,frameRate:this.frameRate}}getRandomState(){return this.requirePreparedAnimation().getRandomState()}schedule(t){this.scriptedActions.push(te(t))}async getReplayMetadata(){let t=await this.exportState();return{seed:this.tokenData.hash,clock:this.getClock(),rng:this.getRandomState(),actions:te(this.scriptedActions),applied:te(this.appliedActions),events:te(this.outboundEvents),state:t.state}}async step(t=1){if(!this.deterministic)throw new Error("step() requires createRuntime({ deterministic: true })");this.requirePreparedAnimation(),this.prepared&&!this.loopRunning&&this.beginPlayback(),this.paused=!1;let n=Math.max(0,Math.floor(t));for(let r=0;r<n&&this.animation;r++){let i=this.framesElapsed;if(this.applyScheduledActions(i),await this.processFrame(this.clock.now(),{ignorePause:!0,scheduleNext:!1}),this.framesElapsed===i||(this.virtualClock?.advance(this.targetDrawTime),!this.loopRunning))break}}async advance(t){let n=Math.max(0,Math.round(t/this.targetDrawTime));await this.step(n)}applyScheduledActions(t){let n=this.animation;n&&this.scriptedActions.forEach((r,i)=>{r.atFrame===t&&(this.consumedActionIndexes.has(i)||(this.consumedActionIndexes.add(i),r.type==="pointer"?n.injectPointer(r.pointer.kind,r.pointer.x,r.pointer.y):r.type==="key"?n.injectKey(r.key):r.type==="event"?this.hostChannel?.dispatch(r.event):r.type==="asset"&&this.hostChannel?.dispatch(Xt(r)),this.appliedActions.push({frame:t,action:r})))})}get paused(){return this._paused}set paused(t){this._paused!==t&&(this._paused=t,this.animation?.setPaused(t))}async waitUntilUpdateIdle(){for(;this.updateInProgress;)await new Promise(t=>{this.deterministic?queueMicrotask(t):typeof requestAnimationFrame=="function"?requestAnimationFrame(()=>t()):setTimeout(t,0)})}requirePreparedAnimation(){if(!this.animation)throw new Error("No prepared cart");return this.animation}async exportState(){let t=this.requirePreparedAnimation(),n=this._paused;this.paused=!0,await this.waitUntilUpdateIdle();try{this.lastExportedFramebuffer=t.captureFramebuffer();let r=t.getCanvas();return{version:1,cartId:this.animationCart?.metadata?.id,generative:this.animationCart?.metadata?.generative,seed:this.tokenData.hash,framesElapsed:this.framesElapsed,elapsedSinceStart:t.getElapsedSinceStart(),dimensions:{width:r.width,height:r.height,cssWidth:this.containerWidth,cssHeight:this.containerHeight,dpr:j},state:pe(t.cartState)}}finally{this.paused=n}}async exportStateJSON(){return JSON.stringify(await this.exportState())}peekExportedFramebuffer(){return this.lastExportedFramebuffer}peekSeed(){return this.tokenData?.hash}isGenerative(){return this.animationCart?.metadata?.generative===!0}async importState(t,n){let r=this.requirePreparedAnimation(),i=Yt(t),a=this._paused,s=this.loopRunning;this.paused=!0,await this.waitUntilUpdateIdle(),dt(r.cartState,i.state,this.animationCart?.metadata?.id,i.cartId),zt(this.tokenData.hash,i.seed,this.animationCart?.metadata?.generative,i.generative),clearTimeout(this.reinitTimeout),this.reinitPending=!1,this.importing=!0;let u=qt(i,n?.framebuffer??this.lastExportedFramebuffer);if(u){this.pinOutputSize(u);let m=u.cssWidth??u.width/j,h=u.cssHeight??u.height/j;Gn(m,h),this.syncContainerFromDom(),this.pinViewportAtImport={w:this.containerWidth,h:this.containerHeight}}let f=this.customState,v=!1;try{if(this.customState=Pe(i.state),this.unloadCart(),!this.prepareCart(this.animationCart))throw new Error("Failed to prepare cart while loading state");this.animation.cartState!==this.customState&&Gt(this.animation.cartState,i.state),this.framesElapsed=i.framesElapsed;let m=this.animation,h=m.cartState,S=typeof i.elapsedSinceStart=="number"?i.elapsedSinceStart:qn(h);m.restoreClock(S,typeof h?.startTime=="number"?h.startTime:void 0),a&&m.setPaused(!0);let E=n?.framebuffer??this.lastExportedFramebuffer;E&&m.restoreFramebuffer(E)?h.keepRatioPrimed=!0:await m.renderAnimation(this.framesElapsed,this.rawParams),this.fitPinnedCanvas(),v=!0}catch(m){throw this.clearPinnedOutput(),this.customState=f,this.paused=a,this.animationCart&&(this.syncContainerFromDom(),this.init(),this.reloadCart()),m}finally{this.importing=!1,v&&(this.customState=f,this.paused=a,s&&this.beginPlayback())}}unloadCart(){this.loopRunning=!1,this.requestedAnimationFrame&&cancelAnimationFrame(this.requestedAnimationFrame),this.drawLoopTimeout&&clearTimeout(this.drawLoopTimeout),this.animation?.teardown(),this.animation=void 0,this.prepared=!1,this.audioLibrariesInternal=this.fallbackAudio,this.mainCanvas?.clearAll()}reloadCart(t=!1){let n=this.animationCart;if(!n)return;this.pinnedOutput&&(this.clearPinnedOutput(),this.syncContainerFromDom(),this.init());let r=this.reinitPending?this.reinitStayPaused:this._paused;this.unloadCart(),t&&this.initSeed(),this.loadCart(n),this.paused=r,!this.paused&&!de&&this.resumeAudioLibraries()}getFrameRate(){return this.frameRate}updateFrameRate(t){if(this.frameRate=t,t>=1)this.targetDrawTime=1e3/t;else{let r=1/(2-t);this.targetDrawTime=1e3/r}this.drawTime=this.targetDrawTime}canvasMountOptions(){return{container:this.container,adoptFromDocument:!this.container}}syncContainerFromDom(){if(this.container){this.containerWidth=this.container.clientWidth||this.containerWidth,this.containerHeight=this.container.clientHeight||this.containerHeight;return}this.containerWidth=window.innerWidth,this.containerHeight=window.innerHeight}pinOutputSize(t){this.pinnedOutput=t,this.width=~~t.width,this.height=~~t.height,this.atCenterX=this.width/2,this.atCenterY=this.height/2,this.aspectRatio=this.width/this.height,this.mainCanvas&&this.mainCanvas.setSize(this.width,this.height)}shouldKeepPinnedOutput(){return this.pinnedOutput?(this.syncContainerFromDom(),this.viewportMatches(this.pinnedCssSize())?!0:!!(this.pinViewportAtImport&&this.viewportMatches(this.pinViewportAtImport))):!1}pinnedCssSize(){let t=this.pinnedOutput;return{w:t.cssWidth??t.width/j,h:t.cssHeight??t.height/j}}viewportMatches(t){return Math.abs(this.containerWidth-t.w)<8&&Math.abs(this.containerHeight-t.h)<8}fitPinnedCanvas(){let t=this.pinnedOutput,n=this.canvas;if(!t||!n)return;this.container&&!this.container.style.position&&(this.container.style.position="relative");let r=this.containerWidth||window.innerWidth,i=this.containerHeight||window.innerHeight,a=t.width/j,s=t.height/j;if(a<1||s<1||r<1||i<1)return;let u=Math.min(r/a,i/s),f=a*u,v=s*u;n.style.position="absolute",n.style.left="50%",n.style.top="50%",n.style.transform="translate(-50%, -50%)",n.style.margin="0",n.style.width=`${f}px`,n.style.height=`${v}px`,n.style.imageRendering=Math.abs(u-1)<.01?"auto":"pixelated"}clearPinnedOutput(){let t=this.canvas;this.pinnedOutput=null,this.pinViewportAtImport=null,t&&(t.style.position="",t.style.left="",t.style.top="",t.style.transform="",t.style.imageRendering="",t.style.width="",t.style.height="",t.style.margin="auto")}get canvas(){return this.animation?.getCanvas()??this.mainCanvas?.canvas}destroy(){this.outboundUnsubscribe?.(),this.outboundUnsubscribe=void 0,this.clearPinnedOutput(),this.unloadCart(),clearTimeout(this.reinitTimeout),this.mainCanvas&&!this.mainCanvas.adopted&&this.mainCanvas.canvas.remove()}drawIt(){}tuneFramerate(t){let n=this.lastCall||t;this.lastCall=t,this.drawTime=Math.max(0,this.drawTime+this.targetDrawTime-this.lastCall+n)}async processFrame(t,n={}){let r=n.ignorePause===!0,i=n.scheduleNext!==!1;if(!this.loopRunning&&!r)return;let a=!1,s,u="update";try{if(r||!this.paused||this.tempUnpause){if(!de&&!r&&this.tuneFramerate(t),!this.animation)return;if(u="update",this.updateInProgress=!0,await this.animation.updateAnimationState(this.framesElapsed,this.rawParams,t),this.updateInProgress=!1,u="render",await this.animation.renderAnimation(this.framesElapsed,this.rawParams,t),this.autoMode&&this.animation.cartState.saveToken&&!this.savedToken&&!new URLSearchParams(window.location.search).get("hash")){let m=localStorage.getItem("goodones"),h=JSON.stringify(m?[...JSON.parse(m),this.tokenData.hash]:[this.tokenData.hash]);localStorage.setItem("goodones",h),this.savedToken=!0,localStorage.setItem(this.tokenData.hash,this.mainCanvas.canvas.toDataURL("image/png")),window.location.reload(),this.loopRunning=!1;return}this.framesElapsed++,this.tempUnpause&&(this.tempUnpause=!1)}u="draw",this.drawIt()}catch(f){a=!0,s=f,this.handleFrameError(f,u)}finally{this.updateInProgress=!1}a?(this.consecutiveErrorCount++,this.consecutiveErrorCount>=Xn&&(this.loopRunning=!1,this.onError?.(s,{phase:u,consecutive:this.consecutiveErrorCount,stopped:!0}))):this.consecutiveErrorCount=0,!(!this.loopRunning||!i)&&(this.drawLoopTimeout=setTimeout(()=>{this.requestedAnimationFrame=requestAnimationFrame(this.drawLoop)},this.drawTime))}async drawLoop(t){await this.processFrame(t,{scheduleNext:!0})}handleFrameError(t,n){let r=performance.now();r-this.lastErrorLogAt>=1e3&&(this.lastErrorLogAt=r,console.error(`Animation ${n} error:`,t)),this.consecutiveErrorCount===0&&this.onError?.(t,{phase:n,consecutive:1,stopped:!1})}pureAspectUpdate(t){if(t[0]===0&&t[1]===0){let r=this.containerWidth||window.innerWidth,i=this.containerHeight||window.innerHeight,a=r/i;this.aspectRatio=Math.min(Qn,Math.max(Zn,a))}else this.aspectRatio=t[0]/t[1]}};var er="HostChannel has been destroyed",fe=class{constructor(){this.inbound=[];this.listeners=[];this.closed=!1}dispatch(t){this.requireOpen(),this.inbound.push(t),this.inbound.length>32&&this.inbound.splice(0,this.inbound.length-32)}consume(){if(this.requireOpen(),this.inbound.length===0)return[];let t=this.inbound;return this.inbound=[],t}emit(t){this.requireOpen();for(let n of this.listeners)n(t)}onEvent(t){return this.requireOpen(),this.listeners.push(t),()=>{this.listeners=this.listeners.filter(n=>n!==t)}}clearInbound(){this.requireOpen(),this.inbound=[]}clear(){this.inbound=[],this.listeners=[]}close(){this.closed=!0,this.clear()}requireOpen(){if(this.closed)throw new Error(er)}};var nn=["image","audio","font","spritesheet"],rn=["timeout","cors","not-found","invalid","aborted","resolver"],me="cyberart:fallback/silent",Qt="Asset preloader has been destroyed",tr=4,nr=new Set(nn),rr=new Set(rn),ir={timeout:"asset load timed out",cors:"CORS blocked this asset","not-found":"asset was not found",invalid:"asset declaration or resource is invalid",aborted:"asset load was aborted",resolver:"asset resolver failed"};function an(e){return typeof e=="string"&&nr.has(e)}function on(e){return typeof e=="string"&&rr.has(e)}function We(e){if(!e||typeof e!="object")return!1;let t=e;return typeof t.id=="string"&&typeof t.ref=="string"&&on(t.code)&&typeof t.message=="string"}function F(e){return{id:e.id,ref:e.ref,code:e.code,message:e.message&&e.message.length>0?e.message:ir[e.code]}}function ae(e,t){return{type:e==="ready"?Te:xe,kind:"state",payload:t}}function sn(e){if(!e?.aborted)return;let t=new Error("aborted");throw t.name="AbortError",t}function ar(e){return e instanceof Error&&e.name==="AbortError"||typeof DOMException<"u"&&e instanceof DOMException&&e.name==="AbortError"}function pt(e){return e?{...e}:void 0}function X(e){let t={id:e.id,ref:e.ref,type:e.type,url:e.url};return e.integrity!==void 0&&(t.integrity=e.integrity),e.provenance&&(t.provenance=pt(e.provenance)),e.cors!==void 0&&(t.cors=e.cors),e.usedFallback&&(t.usedFallback=!0),e.managed&&(t.managed=!0),t}function Re(e){return{id:e.id,ref:e.ref,code:e.code,message:e.message}}function or(e){if(e.state==="ready"){let t={state:"ready",resource:X(e.resource)};return e.failure&&(t.failure=Re(e.failure)),t}return e.state==="failed"?{state:"failed",failure:Re(e.failure)}:{state:e.state}}function sr(e){return/^https?:/i.test(e)}function dn(e){return e.endsWith("/")?e:`${e}/`}function un(e,t){let n=dn(t);if(/^(https?:|data:|blob:)/i.test(e))return e;let r=/^moltazine:post\/([^#]+)#(.+)$/.exec(e);if(r)return`${n}moltazine/${encodeURIComponent(r[1])}/${r[2]}`;let i=/^world:asset\/(.+)$/.exec(e);if(i)return`${n}world/${i[1]}`;let a=/^library:(.+)$/.exec(e);return a?`${n}library/${a[1]}`:`${n}${e.replace(/^\//,"")}`}function dr(e){return{async resolve(t,n){sn(n);let r=e[t.ref];if(!r)throw F({id:t.id,ref:t.ref,code:"not-found",message:`no fixture for ${t.ref}`});if("error"in r)throw F({id:t.id,ref:t.ref,code:r.error,message:r.message});return{id:t.id,ref:t.ref,type:t.type,url:r.url,integrity:r.integrity??t.integrity,cors:r.cors??"omit",provenance:r.provenance??t.provenance}}}}function ur(e){let t=dn(e.cdnBase);return{async resolve(n,r){sn(r);let i=e.failures?.[n.ref];if(i){let s=typeof i=="string"?i:i.code,u=typeof i=="string"?void 0:i.message;throw F({id:n.id,ref:n.ref,code:s,message:u})}let a=un(n.ref,t);return{id:n.id,ref:n.ref,type:n.type,url:a,integrity:n.integrity,cors:sr(a)?"anonymous":"omit",provenance:n.provenance}}}}function cr(e){if(!e||typeof e!="object")return F({id:"",ref:"",code:"invalid",message:"declaration is required"});if(typeof e.id!="string"||e.id.length===0)return F({id:typeof e.id=="string"?e.id:"",ref:typeof e.ref=="string"?e.ref:"",code:"invalid",message:"id is required"});if(typeof e.ref!="string"||e.ref.length===0)return F({id:e.id,ref:"",code:"invalid",message:"ref is required"});if(!an(e.type))return F({id:e.id,ref:e.ref,code:"invalid",message:`unsupported type ${String(e.type)}`});if(e.integrity!==void 0&&typeof e.integrity!="string")return F({id:e.id,ref:e.ref,code:"invalid",message:"integrity must be a string"});if(e.timeoutMs!==void 0&&(typeof e.timeoutMs!="number"||!Number.isFinite(e.timeoutMs)||e.timeoutMs<0))return F({id:e.id,ref:e.ref,code:"invalid",message:"timeoutMs must be a non-negative finite number"})}function Zt(e){return{id:e.id,ref:me,type:e.type,url:me,usedFallback:!0,cors:"omit"}}function en(e,t){return We(t)?{id:e.id,ref:e.ref,code:t.code,message:t.message}:ar(t)?F({id:e.id,ref:e.ref,code:"aborted"}):F({id:e.id,ref:e.ref,code:"resolver",message:t instanceof Error?t.message:String(t)})}function tn(e,t){if(!t||typeof t.url!="string"||t.url.length===0)throw F({id:e.id,ref:e.ref,code:"invalid",message:"resolver returned no url"});if(t.integrity&&e.integrity&&t.integrity!==e.integrity)throw F({id:e.id,ref:e.ref,code:"invalid",message:"integrity mismatch"});let n=X(t);return n.id=e.id,n.ref=t.ref||e.ref,n.type=e.type,!n.integrity&&e.integrity&&(n.integrity=e.integrity),!n.provenance&&e.provenance&&(n.provenance=pt(e.provenance)),n}function ft(e){if(typeof e?.resolver?.resolve!="function")throw new Error("createAssetPreloader: resolver is required");let t=e.emitEvents!==!1,n=e.wallClockTimeout!==!1,r=new Map,i=[],a=new Map,s=new Map,u=new Map,f=new Map,v=new Map,m=new Set,h=!1;function S(o){return o.code==="aborted"||o.code==="timeout"}function E(o,g,b){let k=en(o,b);return k.code==="timeout"?k:g.aborted?F({id:o.id,ref:o.ref,code:"aborted"}):k}function N(o,g){let b=v.get(o);b||(b=new Set,v.set(o,b)),b.add(g)}function l(o){if(o?.state==="ready")return`${o.resource.type}\0${o.resource.ref}`}function c(o,g){for(let[b,k]of v)if(b!==g&&k.has(o))return!0;for(let[b,k]of r)if(b!==g&&l(k)===o)return!0;return!1}function D(o,g){let b=new Set(v.get(o)),k=l(g);k&&b.add(k),v.delete(o);for(let H of b)c(H,o)||u.delete(H)}function x(){let o=0,g=0,b=0,k=0,H={};for(let[R,T]of r)H[R]=or(T),T.state==="pending"||T.state==="loading"?o+=1:T.state==="failed"?b+=1:(g+=1,(T.resource.usedFallback||T.failure)&&(k+=1));return{total:r.size,pending:o,ready:g,failed:b,fallbacks:k,items:H,failures:i.map(Re)}}function q(){if(m.size===0)return;let o=x();for(let g of m)g(o)}function I(o){i.push(Re(o))}function L(o){h||!t||!e.dispatch||e.dispatch(o)}function G(o){if(o?.managed&&o.url.startsWith("blob:")&&!(typeof URL>"u"||typeof URL.revokeObjectURL!="function"))try{URL.revokeObjectURL(o.url)}catch{}}function P(o,g){if(h)return;let b=r.get(o);b?.state==="ready"&&G(b.resource),r.set(o,g),q()}function Q(o){if(o.fallback!==void 0)return o.fallback==="silent"?{id:`${o.id}::fallback`,ref:me,type:o.type}:typeof o.fallback=="string"?{id:`${o.id}::fallback`,ref:o.fallback,type:o.type}:{...o.fallback,id:typeof o.fallback.id=="string"&&o.fallback.id.length>0?o.fallback.id:`${o.id}::fallback`}}function _(o,g,b){let k=`${o.type}\0${o.ref}`,H=u.get(k);if(H)return g.aborted?Promise.reject(F({id:o.id,ref:o.ref,code:"aborted"})):(N(b,k),Promise.resolve(X(H.resource)));if(g.aborted)return Promise.reject(F({id:o.id,ref:o.ref,code:"aborted"}));let R=f.get(k);if(!R){let C=new AbortController,M={id:o.id,ref:o.ref,type:o.type};o.integrity!==void 0&&(M.integrity=o.integrity),o.provenance&&(M.provenance=pt(o.provenance)),R={promise:Promise.resolve().then(()=>e.resolver.resolve(M,C.signal)).then(O=>{let J=tn(o,O);if(C.signal.aborted)throw F({id:o.id,ref:o.ref,code:"aborted"});return u.set(k,{resource:X(J)}),J}).catch(O=>{throw en(o,O)}).finally(()=>{f.delete(k)}),controller:C,waiters:0},f.set(k,R)}let T=R;return T.waiters+=1,new Promise((C,M)=>{let Y=!1,O=(Z,et)=>{Y||(Y=!0,g.removeEventListener("abort",J),T.waiters-=1,et&&T.waiters<=0&&T.controller.abort(),Z())},J=()=>{O(()=>M(F({id:o.id,ref:o.ref,code:"aborted"})),!0)};if(g.aborted){J();return}g.addEventListener("abort",J,{once:!0}),T.promise.then(Z=>{if(!Y){if(g.aborted){J();return}N(b,k),O(()=>C(X(Z)),!1)}},Z=>{if(!Y){if(g.aborted){J();return}O(()=>M(Z),!1)}})})}function oe(o,g,b){if(g.aborted)return Promise.reject(F({id:o.id,ref:o.ref,code:"aborted"}));if(o.ref===me)return Promise.resolve(Zt(o));let k=new AbortController,H=()=>k.abort();g.addEventListener("abort",H,{once:!0});let R=o.timeoutMs??e.timeoutMs,T,C=!1;return n&&R!==void 0&&R>0&&Number.isFinite(R)&&(T=setTimeout(()=>{C=!0,k.abort()},R)),_(o,k.signal,b).then(M=>{let Y=tn(o,M);return Y.id=o.id,Y}).catch(M=>{throw C?F({id:o.id,ref:o.ref,code:"timeout"}):M}).finally(()=>{T!==void 0&&clearTimeout(T),g.removeEventListener("abort",H)})}async function Ee(o,g,b,k){try{return{resource:await oe(o,g,k)}}catch(H){let R=E(o,g,H);if(S(R)||g.aborted||b>=tr)throw R;let T=Q(o);if(!T)throw R;if(g.aborted)throw F({id:o.id,ref:o.ref,code:"aborted"});if(o.fallback==="silent"||T.ref===me)return{resource:Zt(o),failure:R};try{let C=await Ee(T,g,b+1,k);return{resource:{...X(C.resource),id:o.id,usedFallback:!0},failure:R}}catch(C){let M=We(C)&&!S(C)&&!g.aborted?C:E(o,g,C);throw!S(M)&&!g.aborted&&I(R),M}}}async function Ae(o,g){if(h)throw new Error(Qt);let b=r.get(o.id);if(b?.state==="ready"||b?.state==="failed")return;let k=a.get(o.id);if(k){await k;return}let H=(async()=>{let R=cr(o);if(R){P(o.id,{state:"failed",failure:R}),I(R),L(ae("failed",{id:o.id,ref:o.ref,failure:R}));return}P(o.id,{state:"loading"});let T=s.get(o.id)??new AbortController;s.has(o.id)||s.set(o.id,T);try{let C=await Ee(o,T.signal,g,o.id);if(h)return;if(T.signal.aborted){let M=F({id:o.id,ref:o.ref,code:"aborted"});P(o.id,{state:"failed",failure:M}),I(M),L(ae("failed",{id:o.id,ref:o.ref,failure:M}));return}if(C.resource.id=o.id,C.failure){P(o.id,{state:"ready",resource:C.resource,failure:C.failure}),I(C.failure),L(ae("failed",{id:o.id,ref:o.ref,failure:C.failure,resource:X(C.resource)})),L(ae("ready",{id:o.id,ref:C.resource.ref,resource:X(C.resource),failure:C.failure}));return}P(o.id,{state:"ready",resource:C.resource}),L(ae("ready",{id:o.id,ref:o.ref,resource:X(C.resource)}))}catch(C){if(h)return;let M=We(C)&&!S(C)&&!T.signal.aborted?{...Re(C),id:o.id}:E(o,T.signal,C);P(o.id,{state:"failed",failure:M}),I(M),L(ae("failed",{id:o.id,ref:o.ref,failure:M}))}finally{s.get(o.id)===T&&s.delete(o.id)}})();a.set(o.id,H);try{await H}finally{a.delete(o.id)}}return{async preload(o){if(h)throw new Error(Qt);let g=Array.isArray(o)?o:[];for(let b of g)b&&typeof b.id=="string"&&b.id.length>0&&(r.has(b.id)||r.set(b.id,{state:"pending"}),s.has(b.id)||s.set(b.id,new AbortController));return q(),await Promise.all(g.map(b=>Ae(b,0))),x()},get(o){let g=r.get(o);if(g?.state==="ready")return X(g.resource)},getProgress(){return x()},onProgress(o){return m.add(o),()=>{m.delete(o)}},abort(o){if(!h){if(o!==void 0){s.get(o)?.abort();return}for(let g of s.values())g.abort()}},forget(o){if(h)return;if(o===void 0){for(let b of r.values())b.state==="ready"&&G(b.resource);r.clear(),i.length=0,u.clear(),v.clear(),q();return}let g=r.get(o);g?.state==="ready"&&G(g.resource),D(o,g),r.delete(o);for(let b=i.length-1;b>=0;b--)i[b].id===o&&i.splice(b,1);q()},dispose(){if(!h){h=!0;for(let o of s.values())o.abort();s.clear();for(let o of r.values())o.state==="ready"&&G(o.resource);r.clear(),i.length=0,a.clear();for(let o of f.values())o.controller.abort();u.clear(),f.clear(),v.clear(),m.clear()}}}}function lr(e){xt();let{container:t,seed:n,captureKeyboard:r=!1,audio:i,deterministic:a,assets:s}=e,u=n===void 0?void 0:$e(n,a?"deterministic":"live"),f=new fe;if(s&&typeof s.resolver?.resolve!="function")throw new Error("createRuntime: assets.resolver is required");let v=s?ft({resolver:s.resolver,timeoutMs:s.timeoutMs,emitEvents:s.emitEvents??!a,wallClockTimeout:!a,dispatch:I=>f.dispatch(I)}):void 0,m=!1,h=null,S,E=!1,N=t.clientWidth||window.innerWidth,l=t.clientHeight||window.innerHeight,c=new Be(30,!1,N,l,!1,void 0,{container:t,seed:u,captureKeyboard:r,hostChannel:f,audio:i,deterministic:a}),D=()=>{let I=t.clientWidth||window.innerWidth,L=t.clientHeight||window.innerHeight;return I===c.containerWidth&&L===c.containerHeight?!1:(c.containerWidth=I,c.containerHeight=L,!0)},x=null;return!c.isDeterministic&&typeof ResizeObserver<"u"&&(x=new ResizeObserver(()=>{if(m||!h)return;D()&&E&&c.reinit()}),x.observe(t)),c.onError=(I,L)=>{S?.(I,L)},{get tokenData(){return c.tokenData},get hostChannel(){return f},get assets(){return v},get onError(){return S},set onError(I){S=I},mount(I,L={}){if(m)throw new Error("createRuntime: cannot mount on a destroyed runtime");h?.destroy(),E=!1,c.customState=L.initialState,c.setGameManager(L.gameManager);let G=L.onEvent?f.onEvent(L.onEvent):()=>{};if(!c.prepareCart(I))throw G(),new Error("createRuntime: failed to prepare cart");let P=!1,Q={async start(){if(!P){if(D()&&c.isPrepared&&(c.init(),c.unloadCart(),!c.prepareCart(I)))throw new Error("createRuntime: failed to prepare cart");c.needsAudio&&(await c.unlockAudio(),P)||(E=!0,c.beginPlayback())}},pause(){P||(c.paused=!0)},resume(){P||(c.paused=!1)},dispatch(_){P||f.dispatch(_)},snapshot(){if(P)return{seed:"",pngDataUrl:""};let _=c.canvas;return{seed:c.tokenData.hash,metadata:I.metadata,pngDataUrl:_?_.toDataURL("image/png"):""}},destroy(){P||(P=!0,E=!1,G(),f.clearInbound(),c.unloadCart(),h===Q&&(h=null))},reload(){if(!P){if(!E){if(c.isPrepared&&(c.unloadCart(),!c.prepareCart(I)))throw new Error("createRuntime: failed to prepare cart");return}c.reloadCart()}},reinit(_){if(!P){if(!E){D();return}c.reinit(_)}},getCartState(){return c.animation?.cartState},exportState(){return P?Promise.reject(new Error("Cart handle has been destroyed")):c.exportState()},exportStateJSON(){return P?Promise.reject(new Error("Cart handle has been destroyed")):c.exportStateJSON()},async importState(_,oe){if(P)throw new Error("Cart handle has been destroyed");await c.importState(_,oe)},peekExportedFramebuffer(){return P?null:c.peekExportedFramebuffer()},peekSeed(){if(!P)return c.peekSeed()},isGenerative(){return P?!1:c.isGenerative()},async step(_=1){if(P)throw new Error("Cart handle has been destroyed");await c.step(_)},async advance(_){if(P)throw new Error("Cart handle has been destroyed");await c.advance(_)},schedule(_){P||c.schedule(_)},getClock(){if(P)throw new Error("Cart handle has been destroyed");return c.getClock()},getRandomState(){if(P)throw new Error("Cart handle has been destroyed");return c.getRandomState()},getReplayMetadata(){return P?Promise.reject(new Error("Cart handle has been destroyed")):c.getReplayMetadata()},get canvas(){return c.canvas},get paused(){return c.paused},set paused(_){P||(c.paused=_)},get tokenData(){return c.tokenData},get isPrepared(){return c.isPrepared},get isLoopRunning(){return c.isLoopRunning},get needsAudio(){return c.needsAudio},get audioLibraries(){return c.audioLibraries}};return h=Q,Q},async unlockAudio(){await c.unlockAudio()},destroy(){m||(m=!0,h?.destroy(),h=null,x?.disconnect(),x=null,v?.dispose(),f.close(),c.destroy())}}}var he="cyberart.state.save",ye="cyberart.state.load";function pr(e,t,n={}){let r=n.persist,i=r?{persist:r}:void 0,a=()=>{t.emit({type:he,payload:i})},s=()=>{t.emit({type:ye,payload:i})};e.registerAction("w",a,!0),e.registerAction("W",a,!0),e.registerAction("e",s,!0),e.registerAction("E",s,!0)}function cn(e){if(e.type!==he&&e.type!==ye)return;let t=e.payload?.persist;return t==="localStorage"?t:void 0}var fr="cyberart-io",mr=1,ne="framebuffers";function mt(e,t){return`${e||"unknown"}:${t?ee(t):"none"}`}function pn(e,t){return`${e||"unknown"}:${t||"none"}`}function ht(){return new Promise((e,t)=>{let n=indexedDB.open(fr,mr);n.onupgradeneeded=()=>{let r=n.result;r.objectStoreNames.contains(ne)||r.createObjectStore(ne)},n.onsuccess=()=>e(n.result),n.onerror=()=>t(n.error??new Error("indexedDB open failed"))})}function hr(e){let t=e.data;return t.buffer.slice(t.byteOffset,t.byteOffset+t.byteLength)}function ln(e,t){return new Promise((n,r)=>{let a=e.transaction(ne,"readonly").objectStore(ne).get(t);a.onsuccess=()=>n(a.result),a.onerror=()=>r(a.error??new Error("indexedDB read failed"))})}async function fn(e,t,n){if(typeof indexedDB>"u")return;let r=await ht(),i={width:n.width,height:n.height,data:hr(n)};try{await new Promise((a,s)=>{let u=r.transaction(ne,"readwrite");u.oncomplete=()=>a(),u.onerror=()=>s(u.error??new Error("indexedDB write failed")),u.objectStore(ne).put(i,mt(e,t))})}finally{r.close()}}async function mn(e,t){if(typeof indexedDB>"u")return null;let n=await ht();try{let r=mt(e,t),i=pn(e,t),a=await ln(n,r)??(r===i?void 0:await ln(n,i));if(!a||!a.data||a.width<1||a.height<1)return null;let s=new ImageData(a.width,a.height);return s.data.set(new Uint8ClampedArray(a.data)),s}finally{n.close()}}async function hn(e,t){if(typeof indexedDB>"u")return;let n=await ht();try{await new Promise((r,i)=>{let a=n.transaction(ne,"readwrite");a.oncomplete=()=>r(),a.onerror=()=>i(a.error??new Error("indexedDB delete failed"));let s=a.objectStore(ne);s.delete(mt(e,t)),s.delete(pn(e,t))})}finally{n.close()}}var yt="cyberart.state.",yr="Saved state",gr="Loaded state",br="Failed to save state",vr="Failed to load state",Er="No saved state";function re(e,t){let n=e||"unknown";return t?yt+n+"."+ee(t):yt+n}function Ar(e,t,n){return r=>{if(cn(r)==="localStorage"){if(r.type===he){wr(e,t,n);return}r.type===ye&&Sr(e,t,n)}}}function ge(e){try{let t=JSON.parse(e);if(!t||typeof t!="object")return null;let n=t;if(typeof n.savedAt=="number"&&n.bundle&&typeof n.bundle=="object")return{savedAt:n.savedAt,bundle:n.bundle};let r=t;return typeof r.seed=="string"&&typeof r.version=="number"?{savedAt:0,bundle:r}:null}catch{return null}}async function wr(e,t,n){let r=e();if(r)try{let i=await r.exportState(),a=i.cartId??t?.();i.generative===!0?(Dr(a),ze(re(a,i.seed),i)):ze(re(a),i);let s=r.peekExportedFramebuffer?.();if(s)try{await fn(a,i.seed,s)}catch(u){console.warn("Failed to save cart framebuffer",u)}n?.(yr,"transient")}catch(i){console.error("Failed to save cart state",i),n?.(br,"transient")}}async function Sr(e,t,n){let r=e();if(!r)return;let i=t?.(),a=r.peekSeed?.(),s=r.isGenerative?.()===!0,u=xr(i,s,a);if(!u){console.warn("No saved cart state for",i||"unknown",s&&a?`(hash ${a})`:""),n?.(Er,"transient");return}try{let f=await mn(i,u.bundle.seed).catch(()=>null);await r.importState(u.bundle,{framebuffer:f}),n?.(gr,"transient")}catch(f){console.error("Failed to load cart state",f),n?.(vr,"transient")}}function ze(e,t){let n=JSON.stringify({savedAt:Date.now(),bundle:t});Cr(e,n)}function Cr(e,t){for(;;)try{localStorage.setItem(e,t);return}catch(n){if(!kr(n)||!Pr(e))throw n}}function kr(e){if(!e||typeof e!="object")return!1;let t=e.name,n=e.code;return t==="QuotaExceededError"||t==="NS_ERROR_DOM_QUOTA_REACHED"||n===22||n===1014}function Pr(e){let t=null,n=1/0;for(let a of Tr()){if(a===e)continue;let s=localStorage.getItem(a);if(!s)continue;let f=ge(s)?.savedAt??0;f<n&&(n=f,t=a)}if(!t)return!1;let r=localStorage.getItem(t),i=r?ge(r):null;return localStorage.removeItem(t),i?.bundle&&hn(i.bundle.cartId,i.bundle.seed).catch(()=>{}),!0}function Tr(){let e=[];for(let t=0;t<localStorage.length;t++){let n=localStorage.key(t);n&&n.startsWith(yt)&&e.push(n)}return e}function xr(e,t,n){if(t&&n){let i=localStorage.getItem(re(e,n));if(i)return ge(i);let a=localStorage.getItem(re(e));if(!a)return null;let s=ge(a);if(!s||!Rr(s.bundle,n))return null;try{ze(re(e,n),s.bundle)}catch{}return s}let r=localStorage.getItem(re(e));return r?ge(r):null}function Rr(e,t){return typeof e.seed=="string"&&ee(e.seed)===ee(t)}function Dr(e){let t=re(e),n=localStorage.getItem(t);if(!n)return;let r=ge(n);if(r&&typeof r.bundle.seed=="string"){let i=re(e,r.bundle.seed);if(!localStorage.getItem(i))try{ze(i,r.bundle)}catch{return}}localStorage.removeItem(t)}var De=1,Ie="cyberart.diagnostic.rejected",Ge=8,Ir=new Set(["intent","state","diagnostic"]),Mr=new Set([he,ye]);function qe(e){return typeof e=="string"&&Ir.has(e)}function yn(e){if(e.kind!==void 0)return qe(e.kind)?e.kind:void 0;if(!(typeof e.type!="string"||e.type.length===0)){if(Mr.has(e.type))return"intent";for(let t of e.type.split("."))if(qe(t))return t}}function gn(e,t){let n=e.split("."),r=t.split(".");if(n.length!==r.length)return!1;for(let i=0;i<n.length;i++)if(n[i]!=="*"&&n[i]!==r[i])return!1;return!0}function ie(e,t){for(let n of e)if(gn(n,t))return!0;return!1}function be(e){if(e===void 0)return{ok:!0,value:void 0};try{return{ok:!0,value:JSON.parse(JSON.stringify(e))}}catch{return{ok:!1}}}function Ye(e,t){if(!e||typeof e.type!="string"||e.type.length===0)return{ok:!1,reason:"malformed",detail:"type is required"};if(e.schemaVersion!==void 0&&e.schemaVersion!==De)return{ok:!1,reason:"malformed",detail:`unsupported schemaVersion ${String(e.schemaVersion)}`};let n=yn(e);if(!n)return{ok:!1,reason:"malformed",detail:`cannot infer kind from type "${e.type}"`};let r=be(e.payload);if(!r.ok)return{ok:!1,reason:"malformed",detail:"payload is not JSON-serializable"};let i=t.createId(),a=typeof e.correlationId=="string"&&e.correlationId.length>0?e.correlationId:i,s=typeof e.causationId=="string"&&e.causationId.length>0?e.causationId:t.causationId,u=typeof e.target=="string"&&e.target.length>0?e.target:void 0,f=t.maxHops??Ge,v=t.parentHops===void 0?f:Math.max(0,t.parentHops-1),m=typeof e.idempotencyKey=="string"&&e.idempotencyKey.length>0?e.idempotencyKey:void 0,h={schemaVersion:De,type:e.type,kind:n,source:t.source,id:i,correlationId:a,seq:t.seq,hops:v};return u&&(h.target=u),s&&(h.causationId=s),m&&(h.idempotencyKey=m),r.value!==void 0&&(h.payload=r.value),{ok:!0,envelope:h}}var Fr=new Set(["rate-limited","storm-detected","hop-limit","loop-detected","not-subscribed"]),_r=8,Or=8,Lr=16,Vr=256,Nr=["*.intent.*"];function Hr(e={}){let t=e.hostSource??"host",n="router",r=e.maxPerTurn??_r,i=e.maxCausationDepth??Or,a=e.maxHops??Ge,s=e.maxCorrelationPerTurn??Lr,u=e.maxIndex??Vr,f=e.maxPerWindow,v=e.windowMs??1e3,m=e.now??Date.now,h=0,S=e.createId??(()=>`evt-${++h}`),E=new Map,N=[],l=new Map,c=new Map,D=new Map,x=0,q=0,I=[],L=!1;function G(d,p,A){for(d.set(p,A);d.size>u;){let w=d.keys().next().value;if(w===void 0)break;d.delete(w)}}function P(d){G(l,d.id,{type:d.type,source:d.source,causationId:d.causationId,hops:d.hops,correlationId:d.correlationId,idempotencyKey:d.idempotencyKey})}function Q(d,p){p&&(d.correlationId||(d.correlationId=p.correlationId),d.causationId||(d.causationId=p.id),!d.idempotencyKey&&p.idempotencyKey&&(d.idempotencyKey=`${p.idempotencyKey}::${d.type}`))}function _(d,p){if(d){let A=l.get(d);return A?A.hops:a}return p}function oe(d,p){return`${d}\0${p}`}function Ee(d,p){d.idempotencyKey&&G(D,oe(d.source,d.idempotencyKey),p)}function Ae(d,p){if(p)return D.get(oe(d,p))}function o(d){return Ae(d.source,d.idempotencyKey)}function g(d){return d.seq+=1,d.seq}function b(d,p){if(!(typeof p.causationId=="string"&&p.causationId.length>0))return d.lastDelivered}function k(d){let p=new Set,A=new Set([`${d.type}\0${d.source}`]),w=d.causationId,y=0;for(;w;){if(y+=1,y>i||p.has(w))return!0;p.add(w);let V=l.get(w);if(!V)break;let U=`${V.type}\0${V.source}`;if(A.has(U))return!0;A.add(U),w=V.causationId}return!1}function H(d){let p=(c.get(d)??0)+1;return c.set(d,p),p}function R(d,p){f!==void 0&&(d.emitTimestamps=d.emitTimestamps.filter(A=>p-A<v))}function T(d,p){return(p.kind==="state"||p.kind==="diagnostic")&&!d.authoritative?!1:ie(d.emit,p.type)}function C(d){for(let p of N)if(ie(p.patterns,d.type))try{p.listener(d)}catch{}}function M(d){if(d.target){let p=E.get(d.target);if(!p||!ie(p.subscribe,d.type))return;p.channel.dispatch(d),p.lastDelivered={id:d.id,hops:d.hops,correlationId:d.correlationId,idempotencyKey:d.idempotencyKey};return}for(let p of E.values())ie(p.subscribe,d.type)&&(p.channel.dispatch(d),p.lastDelivered={id:d.id,hops:d.hops,correlationId:d.correlationId,idempotencyKey:d.idempotencyKey})}function Y(d,p,A,w,y,V,U){let z={reason:w,eventType:p,source:A};y&&(z.detail=y),q+=1;let we=S(),se={schemaVersion:De,type:Ie,kind:"diagnostic",source:n,id:we,correlationId:V??we,seq:q,hops:0,payload:z};U&&(se.causationId=U),P(se);let Tt=E.get(d);Tt&&Tt.channel.dispatch(se),C(se)}function O(d,p,A,w,y){Y(d,y?.type??(typeof p.type=="string"?p.type:""),y?.source??d,A,w,y?.correlationId,y?.id),y?.idempotencyKey&&!Fr.has(A)&&Ee(y,{status:"rejected"})}function J(d){P(d),Ee(d,{status:"accepted",envelope:d}),H(d.correlationId),C(d),M(d)}function Z(d,p,A,w){if(!A.ok){O(d,p,"malformed",A.detail);return}let y=A.envelope,V=o(y);if(V)return V.status==="accepted"?V.envelope:void 0;if(y.hops<1){O(d,p,"hop-limit","no hops remaining",y);return}if(y.target&&y.target!==t){let U=E.get(y.target);if(!U){O(d,p,"unknown-target",`unknown target "${y.target}"`,y);return}if(!ie(U.subscribe,y.type)){O(d,p,"not-subscribed",`target "${y.target}" does not subscribe to ${y.type}`,y);return}}if(w){if(!T(w,y)){O(d,p,"unauthorized","source may not emit this event",y);return}if(w.emitsThisTurn+=1,w.emitsThisTurn>r){O(d,p,"rate-limited","maxPerTurn exceeded",y);return}if(f!==void 0){let z=m();if(R(w,z),w.emitTimestamps.push(z),w.emitTimestamps.length>f){O(d,p,"rate-limited","maxPerWindow exceeded",y);return}}if(k(y)){O(d,p,"loop-detected","causation chain cycle or depth exceeded",y);return}if((c.get(y.correlationId)??0)+1>s){O(d,p,"storm-detected","correlationId storm",y);return}if(e.validate){let z;try{z=e.validate(y)}catch(we){let se=we instanceof Error?we.message:"validate threw";O(d,p,"host-rejected",se,y);return}if(z!==!0){O(d,p,"host-rejected",z.detail,y);return}}}else{if(k(y)){O(d,p,"loop-detected","causation chain cycle or depth exceeded",y);return}if((c.get(y.correlationId)??0)+1>s){O(d,p,"storm-detected","correlationId storm",y);return}}return J(y),y}function et(d,p){let A=E.get(d);if(!A)return;let w={...p},y=b(A,w);if(Q(w,y),Ae(A.id,w.idempotencyKey))return;let U=g(A),z=Ye(w,{source:A.id,createId:S,seq:U,causationId:y?.id,maxHops:a,parentHops:_(w.causationId??y?.id,y?.hops)});Z(d,w,z,A)}function Dn(d,p){let A=p?.cause,w={...d};Q(w,A);let y=Ae(t,w.idempotencyKey);if(y)return y.status==="accepted"?y.envelope:void 0;x+=1;let V=Ye(w,{source:t,createId:S,seq:x,causationId:w.causationId,maxHops:a,parentHops:_(w.causationId,A?.hops)});return Z(t,w,V,void 0)}function In(){if(!L){L=!0;try{for(;I.length>0;){let d=I.shift();d&&(d.kind==="cart"?et(d.participantId,d.event):d.result=Dn(d.event,d.extras))}}finally{L=!1}}}function Pt(d){I.push(d),In()}return{attach(d,p,A={}){if(d===t||d===n)throw new Error(`createEventRouter: "${d}" is a reserved participant id`);for(let V of E.values())if(V.channel===p&&V.id!==d)throw new Error(`createEventRouter: channel already attached as "${V.id}"`);E.get(d)?.unsubscribe();let y={id:d,channel:p,emit:A.emit??Nr,subscribe:A.subscribe??[],authoritative:A.authoritative===!0,seq:0,emitsThisTurn:0,emitTimestamps:[],unsubscribe:()=>{}};y.unsubscribe=p.onEvent(V=>{Pt({kind:"cart",participantId:d,event:V})}),E.set(d,y)},detach(d){let p=E.get(d);p&&(p.unsubscribe(),E.delete(d))},publish(d,p){let A={kind:"publish",event:d,extras:p};return Pt(A),A.result},subscribe(d,p){let A={patterns:d,listener:p};return N.push(A),()=>{let w=N.indexOf(A);w>=0&&N.splice(w,1)}},turn(){c.clear();for(let d of E.values())d.emitsThisTurn=0,d.lastDelivered=void 0,f!==void 0&&R(d,m())}}}var Kr=new Set(["string","number","boolean","object","array"]);function K(e,t,n){return n?{code:e,detail:t,path:n}:{code:e,detail:t}}function bn(e){if(!(typeof e!="string"||e.length===0)){for(let t of e.split("."))if(qe(t))return t}}function vn(e){let t=e.split(".");return t.length===0?"*":(t[t.length-1]="*",t.join("."))}function gt(e){let t=[];if(!e||typeof e!="object")return[K("invalid-schema","payload schema is required")];if((typeof e.version!="number"||!Number.isInteger(e.version)||e.version<1)&&t.push(K("invalid-version","payload schema version must be an integer >= 1")),!e.fields||typeof e.fields!="object"||Array.isArray(e.fields))return t.push(K("invalid-fields","payload schema fields must be an object")),t;for(let[n,r]of Object.entries(e.fields)){if(!n){t.push(K("invalid-field","field name is required",n));continue}(!r||typeof r!="object"||!Kr.has(r.type))&&t.push(K("invalid-field-type",`field "${n}" type must be string | number | boolean | object | array`,n))}return t}function $r(e){return e===null?"null":e===void 0?"undefined":Array.isArray(e)?"array":typeof e=="string"?"string":typeof e=="number"&&Number.isFinite(e)?"number":typeof e=="boolean"?"boolean":typeof e=="object"?"object":"other"}function jr(e,t){if(t===void 0){let i=Object.entries(e.fields).filter(([,a])=>a.optional!==!0);return i.length===0?{ok:!0,payload:void 0}:{ok:!1,errors:i.map(([a])=>K("missing-field",`payload is missing required field "${a}"`,a))}}if(t===null||typeof t!="object"||Array.isArray(t))return{ok:!1,errors:[K("invalid-payload","payload must be a JSON object")]};let n=t,r=[];for(let[i,a]of Object.entries(e.fields)){if(!(i in n)||n[i]===void 0){a.optional!==!0&&r.push(K("missing-field",`payload is missing required field "${i}"`,i));continue}let s=$r(n[i]);s!==a.type&&r.push(K("field-type",`payload.${i} expected ${a.type}, got ${s}`,i))}return r.length>0?{ok:!1,errors:r}:{ok:!0,payload:t}}function Ur(e,t,n){let r=vn(e);return{type:e,kind:t,payloadSchema:{version:n.version,fields:{...n.fields}},emitPattern:r,subscribePattern:r,toManifest(){let i={};for(let[a,s]of Object.entries(this.payloadSchema.fields))i[a]={type:s.type,optional:s.optional===!0};return{type:this.type,kind:this.kind,version:this.payloadSchema.version,fields:i,emitPattern:this.emitPattern,subscribePattern:this.subscribePattern}},validatePayload(i){return jr(this.payloadSchema,i)}}}function bt(e,t,n){let r=[];if(typeof t!="string"||t.length===0)r.push(K("invalid-type","event type is required"));else if(t.split(".").some(i=>i.length===0))r.push(K("invalid-type",`event type "${t}" has empty dotted segments`));else{let i=bn(t);i?i!==e&&r.push(K("kind-mismatch",`type "${t}" infers kind "${i}" but this helper defines "${e}"`)):r.push(K("kind-mismatch",`type "${t}" has no "${e}" segment; Adventure-style names like adventure.presentation.* need e.g. adventure.presentation.${e}.cue-started`))}return r.push(...gt(n)),r.length>0?{ok:!1,errors:r}:{ok:!0,contract:Ur(t,e,n)}}function Br(e,t){return bt("intent",e,t)}function Wr(e,t){return bt("state",e,t)}function zr(e,t){return bt("diagnostic",e,t)}function qr(e,t){let n=gt(e),r=gt(t);if(n.length>0||r.length>0||t.version<e.version)return"breaking";let i=Object.keys(e.fields),a=Object.keys(t.fields),s=new Set(i),u=new Set(a);for(let m of i){if(!u.has(m))return"breaking";let h=e.fields[m],S=t.fields[m];if(h.type!==S.type)return"breaking";let E=h.optional!==!0,N=S.optional===!0;if(E&&N||!E&&S.optional!==!0&&h.optional===!0)return"breaking"}let f=!1;for(let m of a)if(!s.has(m)){if(t.fields[m].optional!==!0)return"breaking";f=!0}let v=i.length===a.length&&i.every(m=>t.fields[m].type===e.fields[m].type&&t.fields[m].optional===!0==(e.fields[m].optional===!0));return v&&t.version===e.version?"identical":t.version>e.version&&(f||v)?"backward-compatible":t.version===e.version&&f?"breaking":v?"identical":"breaking"}function Gr(e,t){let n=[],r=[],i=new Set,a=new Set;for(let s of e)(t==="authoritative"||s.kind==="intent")&&!i.has(s.emitPattern)&&(i.add(s.emitPattern),n.push(s.emitPattern)),s.kind!=="intent"&&!a.has(s.subscribePattern)&&(a.add(s.subscribePattern),r.push(s.subscribePattern));return{emit:n,subscribe:r,authoritative:t==="authoritative"}}function Yr(e,t){let n=e.emit??["*.intent.*"],r=e.authoritative===!0,i=[];for(let a of t)ie(n,a.type)&&(a.kind==="state"||a.kind==="diagnostic")&&!r&&i.push(K("unauthorized-emit",`emit pattern allows ${a.kind} "${a.type}" but attach is not authoritative`,a.type));return i.length>0?{ok:!1,errors:i}:{ok:!0}}function Jr(e){let t=new Map;for(let i of e)t.set(i.type,i);let n=i=>{let a=t.get(i.type);return a?a.kind!==i.kind?{ok:!1,errors:[K("kind-mismatch",`envelope kind "${i.kind}" does not match contract "${a.kind}"`,i.type)]}:a.validatePayload(i.payload):{ok:!1,errors:[K("unknown-type",`no contract registered for "${i.type}"`,i.type)]}};return{get(i){return t.get(i)},manifest(){return[...t.values()].sort((i,a)=>i.type.localeCompare(a.type)).map(i=>i.toManifest())},validateEnvelope:n,asRouterValidate:i=>{let a=n(i);return a.ok?!0:{reason:"host-rejected",detail:a.errors.map(s=>s.detail).join("; ")}}}}var ve=1,Et="presentation.state.model",An="presentation.intent.unsupported",wn=["loading","ready","error","unsupported"],Xr=["presentation.state.*",Ie],Qr="Presentation adapter has been destroyed",Sn="Presentation adapter: model is invalid or not JSON-serializable",Zr=new Set(wn);function Cn(e){return typeof e=="string"&&Zr.has(e)}function At(e){if(e===null||typeof e!="object")return!1;let t=e;return!(t.contractVersion!==ve||!Cn(t.phase)||t.reason!==void 0&&typeof t.reason!="string"||t.view!==void 0&&(t.view===null||typeof t.view!="object"))}function vt(e,t){let n={type:Et,kind:"state",payload:e};return t?.correlationId&&(n.correlationId=t.correlationId),t?.causationId&&(n.causationId=t.causationId),t?.idempotencyKey&&(n.idempotencyKey=t.idempotencyKey),n}function wt(){return{contractVersion:ve,phase:"loading"}}function St(e){return{contractVersion:ve,phase:"unsupported",reason:e}}function Ct(e){let t=be(e);return t.ok?At(t.value)?{ok:!0,model:t.value}:t.value!==null&&typeof t.value=="object"&&"contractVersion"in t.value&&t.value.contractVersion!==ve?{ok:!1,reason:"unknown-contract-version",contractVersion:t.value.contractVersion}:{ok:!1,reason:"invalid-model"}:{ok:!1,reason:"model-not-serializable"}}function Je(e){let t=Ct(e);if(!t.ok)throw new Error(Sn);return t.model}function ei(e){let t=be(e);return t.ok&&At(t.value)?t.value:St("model-not-serializable")}function ti(e,t){if(e.type!==Et)return{model:t};let n=Ct(e.payload);return n.ok?{model:n.model}:{model:St(n.reason),rejected:{reason:n.reason,contractVersion:n.contractVersion}}}function ni(e){if(e===null||typeof e!="object"||!("regions"in e))return[];let t=e.regions;return Array.isArray(t)?t:[]}function ri(e,t,n){return e>=n.x&&e<n.x+n.width&&t>=n.y&&t<n.y+n.height}function ii(e,t){let n=t.intent;if(!n||typeof n.type!="string"||n.type.length===0)return;let r={type:n.type,kind:"intent"};if(n.payload!==void 0){let i=be(n.payload);if(!i.ok)return;r.payload=i.value}n.target&&(r.target=n.target),n.correlationId&&(r.correlationId=n.correlationId),n.idempotencyKey&&(r.idempotencyKey=n.idempotencyKey),e.emit(r)}function kn(e,t,n){let r={reason:t};if(n!==void 0){let i=be(n);i.ok&&(r.contractVersion=i.value)}e.emit({type:An,kind:"intent",payload:r})}function En(e,t,n){let r=t;for(let i of e){let a=ti(i,r);a.rejected&&n&&kn(n,a.rejected.reason,a.rejected.contractVersion),r=a.model}return r}function Pn(){return{metadata:{id:"cyberart.presentation.reference",name:"Reference presentation",frameRate:30},getDefaultState:(e,t,n,r,i,a,s,u,f)=>{let v=i?.model;if(v===void 0)return{model:wt()};let m=Ct(v);return m.ok?{model:m.model}:(f&&kn(f,m.reason,m.contractVersion),{model:St(m.reason)})},update:(e,t,n,r,i,a,s,u,f,v,m)=>{let h=En(m?.consume()??[],i.model,m);for(;s?.hasClick();){let S=s.consumeClick();if(!(!S||h.phase!=="ready"||!m)){for(let E of ni(h.view))if(ri(S.x,S.y,E)){ii(m,E);break}}}return m&&(h=En(m.consume(),h,m)),h===i.model?i:{...i,model:h}},render:(e,t,n,r,i,a)=>{let s=i.model.view?.background;a.fillStyle=s??"#111",a.fillRect(0,0,r.width,r.height)}}}function Tn(e,t={}){let n=e,r=t.model?Je(t.model):wt(),i=!1,a=t.present??t.model!==void 0,s=()=>{if(i)throw new Error(Qr)},u=f=>{s(),r=Je(f),n.dispatch(vt(r))};return a&&n.dispatch(vt(r)),{version:ve,get target(){return n},get model(){return ei(r)},get phase(){return r.phase},get paused(){return n.paused===!0},present:u,async start(){return s(),n.start()},pause(){s(),n.pause()},resume(){s(),n.resume()},retarget(f){s(),n=f},destroy(){i||(i=!0,n.destroy())}}}function ai(e,t={}){let n=t.model?Je(t.model):t.initialState?.model!==void 0?Je(t.initialState.model):wt(),r=t.cart??Pn(),i=e.mount(r,{initialState:{...t.initialState,model:n},onEvent:t.onEvent});return Tn(i,{model:n,present:!1})}var Me="cue.started",Qe="cue.completed",Fe="cue.cancelled",Ze="cue.replaced",oi=[Me,Qe,Fe,Ze];function si(e){return e===Me||e===Qe||e===Fe||e===Ze}function Rn(e,t){let n=e<0?0:e>1?1:e;return t==="ease-out"?1-(1-n)*(1-n):n}function xn(e){return{type:e.type,atFrame:e.atFrame,name:e.name,idempotencyKey:e.idempotencyKey,progress:e.progress}}function Xe(e){return{name:e.name,idempotencyKey:e.idempotencyKey,phase:e.phase,startFrame:e.startFrame,durationFrames:e.durationFrames,delayFrames:e.delayFrames,easing:e.easing,progress:e.progress,repeatIndex:e.repeatIndex}}function kt(e){return e.startFrame+e.delayFrames}function di(e,t){if(!e||typeof e!="object")return{ok:!1,reason:"invalid",detail:"spec is required"};if(typeof e.name!="string"||e.name.length===0)return{ok:!1,reason:"invalid",detail:"name is required"};if(typeof e.idempotencyKey!="string"||e.idempotencyKey.length===0)return{ok:!1,reason:"invalid",detail:"idempotencyKey is required"};if(typeof e.durationFrames!="number"||!Number.isFinite(e.durationFrames)||e.durationFrames<0)return{ok:!1,reason:"invalid",detail:"durationFrames must be a non-negative finite number"};let n=e.startFrame===void 0?t:e.startFrame;if(typeof n!="number"||!Number.isFinite(n))return{ok:!1,reason:"invalid",detail:"startFrame must be finite"};let r=e.delayFrames??0;if(typeof r!="number"||!Number.isFinite(r)||r<0)return{ok:!1,reason:"invalid",detail:"delayFrames must be a non-negative finite number"};let i=e.easing??"linear";if(i!=="linear"&&i!=="ease-out")return{ok:!1,reason:"invalid",detail:"unsupported easing"};let a=e.onDuplicate??"replace";if(a!=="ignore"&&a!=="replace"&&a!=="reject")return{ok:!1,reason:"invalid",detail:"unsupported onDuplicate"};let s=0;if(e.repeat)if("forever"in e.repeat&&e.repeat.forever)s="forever";else if("count"in e.repeat){if(typeof e.repeat.count!="number"||!Number.isFinite(e.repeat.count)||e.repeat.count<0)return{ok:!1,reason:"invalid",detail:"repeat.count must be >= 0"};s=Math.floor(e.repeat.count)}else return{ok:!1,reason:"invalid",detail:"unsupported repeat"};let u=e.reducedMotion??"complete";return u!=="skip"&&u!=="complete"&&(typeof u!="object"||typeof u.durationFrames!="number"||!Number.isFinite(u.durationFrames)||u.durationFrames<0)?{ok:!1,reason:"invalid",detail:"unsupported reducedMotion"}:{name:e.name,idempotencyKey:e.idempotencyKey,startFrame:n,durationFrames:e.durationFrames,delayFrames:r,easing:i,repeatRemaining:s,repeatIndex:0,reducedMotion:u,onDuplicate:a,phase:"scheduled",progress:0}}function ui(e,t){if(!t)return e;let n=e.reducedMotion;return n==="skip"||n==="complete"?{...e,durationFrames:0,delayFrames:0,startFrame:e.startFrame}:{...e,durationFrames:n.durationFrames}}function ci(e={}){let t=!!e.reducedMotion,n=e.originFrame??0,r=new Map,i=[];function a(l){i.push(l)}function s(l,c){let D=kt(l)-kt(c);return D!==0?D:l.name<c.name?-1:l.name>c.name?1:l.idempotencyKey<c.idempotencyKey?-1:l.idempotencyKey>c.idempotencyKey?1:0}function u(l,c){a({type:c,atFrame:n,name:l.name,idempotencyKey:l.idempotencyKey,progress:l.progress})}function f(l){if(l.phase="completed",l.progress=1,u(l,Qe),l.repeatRemaining==="forever"||l.repeatRemaining>0){if(typeof l.repeatRemaining=="number"&&(l.repeatRemaining-=1),l.repeatIndex+=1,l.phase="scheduled",l.progress=0,l.startFrame=n,l.delayFrames=0,l.durationFrames===0){r.delete(l.idempotencyKey);return}l.phase="active",u(l,Me)}else r.delete(l.idempotencyKey)}function v(l){if(l.phase==="cancelled"||l.phase==="completed")return;let c=kt(l);if(!(n<c)){if(l.phase==="scheduled"&&(l.phase="active",l.progress=l.durationFrames===0?1:0,u(l,Me),l.durationFrames===0)){f(l);return}if(l.phase==="active"){let D=n-c;D>=l.durationFrames?(l.progress=1,f(l)):l.progress=Rn(l.durationFrames===0?1:D/l.durationFrames,l.easing)}}}function m(){let l=[...r.values()].sort(s);for(let c of l)v(c)}function h(l){let c=di(l,n);if("ok"in c&&c.ok===!1)return c;let D=ui(c,t),x=r.get(D.idempotencyKey);if(x&&(x.phase==="scheduled"||x.phase==="active")){if(x.onDuplicate==="reject"||D.onDuplicate==="reject")return{ok:!1,reason:"duplicate",detail:`cue ${D.idempotencyKey} is already ${x.phase}`};if(x.onDuplicate==="ignore"||D.onDuplicate==="ignore")return{ok:!0,cue:Xe(x)};x.phase="cancelled",x.progress=x.progress,u(x,Ze),r.delete(x.idempotencyKey)}r.set(D.idempotencyKey,D),v(D);let q=r.get(D.idempotencyKey)??D;return{ok:!0,cue:Xe(q)}}function S(l){let c=r.get(l);return!c||c.phase==="completed"||c.phase==="cancelled"?!1:(c.phase="cancelled",u(c,Fe),r.delete(l),!0)}function E(){for(let l of[...r.values()].sort(s))(l.phase==="scheduled"||l.phase==="active")&&(l.phase="cancelled",u(l,Fe));r.clear(),i.length=0,n=e.originFrame??0}function N(l=1){let c=Number.isFinite(l)?Math.max(0,Math.floor(l)):0,D=i.length;for(let x=0;x<c;x++)n+=1,m();return i.slice(D).map(xn)}return{play:h,step:N,cancel:S,reset:E,snapshot(){return{frame:n,reducedMotion:t,cues:[...r.values()].sort(s).map(Xe),events:i.map(xn)}},get(l){let c=r.get(l);return c?Xe(c):void 0},get frame(){return n},get reducedMotion(){return t}}}export{xe as ASSET_FAILED_EVENT,rn as ASSET_FAILURE_CODES,nn as ASSET_KINDS,Te as ASSET_READY_EVENT,Fe as CUE_CANCELLED_EVENT,Qe as CUE_COMPLETED_EVENT,oi as CUE_LIFECYCLE_EVENTS,Ze as CUE_REPLACED_EVENT,Me as CUE_STARTED_EVENT,_e as CYBERART_CANVAS_ATTR,Ge as DEFAULT_MAX_HOPS,De as EVENT_ENVELOPE_VERSION,fe as HostChannel,Sn as INVALID_PRESENTATION_MODEL_MESSAGE,$ as IncompatibleCartStateError,Oe as KeyboardManager,ve as PRESENTATION_ADAPTER_VERSION,Et as PRESENTATION_MODEL_EVENT,wn as PRESENTATION_PHASES,Xr as PRESENTATION_SUBSCRIBE_PATTERNS,An as PRESENTATION_UNSUPPORTED_EVENT,Le as PointerManager,Ie as REJECTED_EVENT_TYPE,Se as Random,me as SILENT_ASSET_FALLBACK_REF,Rn as applyCueEasing,ae as assetStatusEvent,Ar as attachCartStatePersistence,Tn as attachPresentationAdapter,Ke as canonicalizeSeed,qr as comparePayloadSchemas,F as createAssetFailure,ft as createAssetPreloader,Jr as createContractRegistry,Hr as createEventRouter,dr as createFixtureAssetResolver,ur as createHostedAssetResolver,vt as createPresentationModelEvent,ci as createPresentationTimeline,Pn as createReferencePresentationCart,lr as createRuntime,Ve as createVirtualClock,ce as createWallClock,zr as defineDiagnostic,Br as defineIntent,Wr as defineStateEvent,Gr as deriveAttachOptions,Jt as describeReplayMismatch,vn as familyPatternForType,yn as inferEventKind,We as isAssetFailure,on as isAssetFailureCode,an as isAssetKind,si as isCueLifecycleType,At as isPresentationModel,Cn as isPresentationPhase,bn as kindSegmentInType,gn as matchEventPattern,ai as mountPresentationAdapter,Ye as normalizeEvent,pr as registerCartStateHotkeys,$e as resolveRuntimeSeed,un as rewriteHostedAssetRef,Yr as verifyAttachOptions};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# Asset resolver / preloader
|
|
2
|
+
|
|
3
|
+
Host-owned URL policy. The engine caches, dedupes, tracks progress, applies timeouts and fallbacks, and (in live mode) dispatches `ASSET_READY_EVENT` / `ASSET_FAILED_EVENT`. Authored carts keep logical refs (`moltazine:post/<id>#primary-image`, `world:asset/…`, `library:…`, or ordinary URLs). Adventure Kit, Cyberart, and a local preview each supply a different `AssetResolver`.
|
|
4
|
+
|
|
5
|
+
Deterministic timing: [deterministic mode](deterministic-mode.md). CI: [headless harness](headless-harness.md). Event mailbox: [events](events.md).
|
|
6
|
+
|
|
7
|
+
Back to the [package README](../README.md).
|
|
8
|
+
|
|
9
|
+
## One-command reproduce (this repo)
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pnpm exec vitest run packages/engine/src/canvas/cyb-26-asset-resolver.repro.spec.ts
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Why this exists
|
|
16
|
+
|
|
17
|
+
The same room must load through two resolvers without changing cart or world content:
|
|
18
|
+
|
|
19
|
+
| Host | Resolver | Example URL |
|
|
20
|
+
|---|---|---|
|
|
21
|
+
| Local fixtures | `createFixtureAssetResolver` | `fixture://images/porch.png` |
|
|
22
|
+
| Hosted / Library | `createHostedAssetResolver({ cdnBase })` | `https://cdn.example/moltazine/porch-1/primary-image` |
|
|
23
|
+
|
|
24
|
+
Resolution policy stays in the host. The engine never fetches over the network.
|
|
25
|
+
|
|
26
|
+
## `createRuntime({ assets })`
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import {
|
|
30
|
+
createRuntime,
|
|
31
|
+
createFixtureAssetResolver,
|
|
32
|
+
createHostedAssetResolver,
|
|
33
|
+
} from '@cyberart-io/engine';
|
|
34
|
+
|
|
35
|
+
const runtime = createRuntime({
|
|
36
|
+
container,
|
|
37
|
+
assets: {
|
|
38
|
+
resolver: createHostedAssetResolver({
|
|
39
|
+
cdnBase: 'https://cdn.example/library/',
|
|
40
|
+
failures: { 'world:asset/stream-loop': 'cors' },
|
|
41
|
+
}),
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
await runtime.assets!.preload([
|
|
46
|
+
{ id: 'room-bg', ref: 'moltazine:post/porch-1#primary-image', type: 'image' },
|
|
47
|
+
{
|
|
48
|
+
id: 'ambience',
|
|
49
|
+
ref: 'world:asset/stream-loop',
|
|
50
|
+
type: 'audio',
|
|
51
|
+
fallback: 'silent',
|
|
52
|
+
},
|
|
53
|
+
]);
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`runtime.assets` is undefined when `assets` is omitted. It survives cart remount. `runtime.destroy()` disposes it (aborts in-flight work, revokes `managed` blob URLs).
|
|
57
|
+
|
|
58
|
+
| Option | Default | Meaning |
|
|
59
|
+
|---|---|---|
|
|
60
|
+
| `resolver` | required | Host `resolve(request, signal?)`. |
|
|
61
|
+
| `timeoutMs` | none | Default per-asset timeout. Ignored under `deterministic` (no wall-clock). |
|
|
62
|
+
| `emitEvents` | `!deterministic` | When true, preload dispatches `ASSET_READY_EVENT` / `ASSET_FAILED_EVENT`. |
|
|
63
|
+
|
|
64
|
+
Under `deterministic`, emit and wall-clock timeout are off so scripted `{ type: 'asset', atFrame, id, status }` own when the cart `consume()`s ready/failed. Preload still fills the cache and progress snapshot immediately.
|
|
65
|
+
|
|
66
|
+
## Declarations
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import type { AssetDeclaration } from '@cyberart-io/engine';
|
|
70
|
+
|
|
71
|
+
const roomBg: AssetDeclaration = {
|
|
72
|
+
id: 'room-bg',
|
|
73
|
+
ref: 'moltazine:post/porch-1#primary-image',
|
|
74
|
+
type: 'image', // 'image' | 'audio' | 'font' | 'spritesheet'
|
|
75
|
+
integrity: 'sha256-…', // optional; mismatch → `invalid`
|
|
76
|
+
provenance: { source: 'moltazine', postId: 'porch-1' },
|
|
77
|
+
timeoutMs: 8_000,
|
|
78
|
+
fallback: 'silent', // or another ref string, or a nested declaration
|
|
79
|
+
};
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`fallback: 'silent'` synthesizes `SILENT_ASSET_FALLBACK_REF` (`cyberart:fallback/silent`) so a failed audio load does not hang on a black screen. A string is another logical ref of the same type. A nested `AssetDeclaration` may itself declare `fallback` (up to four levels). The host still sees the typed failure on `getProgress().failures`. `abort` and live `timeout` skip fallbacks and terminate as `aborted` / `timeout`.
|
|
83
|
+
|
|
84
|
+
## Resolvers (built-in)
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
createFixtureAssetResolver({
|
|
88
|
+
'moltazine:post/porch-1#primary-image': { url: 'fixture://images/porch.png' },
|
|
89
|
+
'world:asset/stream-loop': { error: 'cors' },
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
createHostedAssetResolver({
|
|
93
|
+
cdnBase: 'https://cdn.example/library/',
|
|
94
|
+
failures: { 'world:asset/stream-loop': { code: 'timeout', message: 'cdn stalled' } },
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`rewriteHostedAssetRef(ref, cdnBase)`:
|
|
99
|
+
|
|
100
|
+
| Authored ref | Rewritten |
|
|
101
|
+
|---|---|
|
|
102
|
+
| `https://…` / `data:` / `blob:` | unchanged |
|
|
103
|
+
| `moltazine:post/<id>#fragment` | `{cdnBase}moltazine/<id>/<fragment>` |
|
|
104
|
+
| `world:asset/<name>` | `{cdnBase}world/<name>` |
|
|
105
|
+
| `library:<path>` | `{cdnBase}library/<path>` |
|
|
106
|
+
| other | `{cdnBase}{ref}` |
|
|
107
|
+
|
|
108
|
+
Hosted HTTP(S) results use `cors: 'anonymous'`. CORS / timeout / not-found are **structured failures the resolver emits** — do not `fetch` in tests or CI. Custom hosts implement `AssetResolver`.
|
|
109
|
+
|
|
110
|
+
`createAssetFailure({ id, ref, code, message? })` builds a typed error. Codes: `timeout` | `cors` | `not-found` | `invalid` | `aborted` | `resolver`.
|
|
111
|
+
|
|
112
|
+
## Preloader
|
|
113
|
+
|
|
114
|
+
`createAssetPreloader({ resolver, timeoutMs, emitEvents, wallClockTimeout, dispatch })` is the same object `createRuntime` stores on `runtime.assets`.
|
|
115
|
+
|
|
116
|
+
| Member | Meaning |
|
|
117
|
+
|---|---|
|
|
118
|
+
| `preload(declarations)` | Validate, resolve, cache, fallback. Returns a snapshot. |
|
|
119
|
+
| `get(id)` | Copy of the ready resource, including silent fallback. |
|
|
120
|
+
| `getProgress()` | Copy: `total` / `pending` / `ready` / `failed` / `fallbacks` / `items` / `failures`. |
|
|
121
|
+
| `onProgress(listener)` | Fires after each item state change. Unsubscribe by calling the return. |
|
|
122
|
+
| `abort(id?)` | Cancel that id (or all), including in-flight fallback work. Terminates as `aborted`, or `timeout` if a live timer already fired. Does not apply silent, string, or nested fallbacks. A sibling that shares type+ref is not aborted. |
|
|
123
|
+
| `forget(id?)` | Drop item(s) and revoke `managed` blob URLs. If no other id still uses those type+ref keys (including fallback refs), drop the resolver cache so the next preload re-resolves. |
|
|
124
|
+
| `dispose()` | Idempotent teardown. Further `preload` throws. |
|
|
125
|
+
|
|
126
|
+
Repeated `preload` of the same `id` joins the in-flight promise or returns the cached terminal state. The same `type`+`ref` under two ids shares one `resolver.resolve` call.
|
|
127
|
+
|
|
128
|
+
Live `dispatch` uses the existing envelope: `{ type: ASSET_READY_EVENT \| ASSET_FAILED_EVENT, kind: 'state', payload: { id, ref?, resource?, failure? } }`. Payload still always has `id`. Do not `await fetch` inside cart `update` / `render`.
|
|
129
|
+
|
|
130
|
+
## Deterministic tests
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
const runtime = createRuntime({
|
|
134
|
+
container,
|
|
135
|
+
seed: 42,
|
|
136
|
+
deterministic: {
|
|
137
|
+
actions: [
|
|
138
|
+
{ type: 'asset', atFrame: 2, id: 'room-bg', status: 'ready' },
|
|
139
|
+
{
|
|
140
|
+
type: 'asset',
|
|
141
|
+
atFrame: 3,
|
|
142
|
+
id: 'ambience',
|
|
143
|
+
status: 'failed',
|
|
144
|
+
detail: { code: 'cors' },
|
|
145
|
+
},
|
|
146
|
+
],
|
|
147
|
+
},
|
|
148
|
+
assets: { resolver: fixtureResolver },
|
|
149
|
+
});
|
|
150
|
+
await runtime.assets!.preload(ROOM_ASSETS);
|
|
151
|
+
// progress is already terminal; the cart has not seen events yet
|
|
152
|
+
await cart.step(4);
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
A resolver used under `deterministic` must not be what tests wait on for ready/failed. Scripted actions remain the clock.
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Deterministic mode
|
|
2
|
+
|
|
3
|
+
Host-controlled time, input, and asset completion so two runs with the same seed, actions, and `step` count match. Production kaleidoscope / Art Blocks **leave `deterministic` unset** (rAF, `performance.now()`, token hash).
|
|
4
|
+
|
|
5
|
+
CI should use the [headless harness](headless-harness.md) on this path, not a mock engine. Router hosts: [events](events.md). Frame-local effects: [presentation cue](presentation-cue.md) (`timeline.step` beside `cart.step`).
|
|
6
|
+
|
|
7
|
+
Back to the [package README](../README.md).
|
|
8
|
+
|
|
9
|
+
## Opt in
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import {
|
|
13
|
+
createRuntime,
|
|
14
|
+
ASSET_READY_EVENT,
|
|
15
|
+
describeReplayMismatch,
|
|
16
|
+
} from '@cyberart-io/engine';
|
|
17
|
+
|
|
18
|
+
const runtime = createRuntime({
|
|
19
|
+
container,
|
|
20
|
+
seed: 42, // or a 64-hex token hash — always canonicalized in this mode
|
|
21
|
+
deterministic: {
|
|
22
|
+
origin: 0,
|
|
23
|
+
actions: [
|
|
24
|
+
{ type: 'asset', atFrame: 2, id: 'room', status: 'ready' },
|
|
25
|
+
{ type: 'pointer', atFrame: 7, pointer: { kind: 'down', x: 40, y: 20 } },
|
|
26
|
+
{ type: 'key', atFrame: 8, key: 'x' },
|
|
27
|
+
{ type: 'event', atFrame: 9, event: { type: 'host.ping', payload: { n: 1 } } },
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
const cart = runtime.mount(artProject);
|
|
32
|
+
await cart.step(10);
|
|
33
|
+
const replay = await cart.getReplayMetadata();
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`deterministic: true` is the same as `{ origin: 0, actions: [] }`. Live DOM keyboard/pointer listeners are off; inject with `schedule` / harness `click` / `key`.
|
|
37
|
+
|
|
38
|
+
`createRuntime` uses `resolveRuntimeSeed(seed, 'deterministic')`: every seed is mixed into a 64-hex hash. Live mode keeps a short `?hash=` string as `0x…` so kaleidoscope local hashes do not change.
|
|
39
|
+
|
|
40
|
+
## `DeterministicRuntimeOptions`
|
|
41
|
+
|
|
42
|
+
| Option | Default | Meaning |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| `origin` | `0` | Virtual clock start in ms. |
|
|
45
|
+
| `actions` | `[]` | Applied at the start of `atFrame`, before `update`. Each fires once. |
|
|
46
|
+
|
|
47
|
+
## Clock
|
|
48
|
+
|
|
49
|
+
| Export | Role |
|
|
50
|
+
|---|---|
|
|
51
|
+
| `createVirtualClock(origin?)` | `{ now, set, advance }`. Mode constructs one from `origin` unless you inject a clock on the manager (not via `createRuntime` options today). |
|
|
52
|
+
| `createWallClock()` | `{ now: () => performance.now() }`. Live playback. |
|
|
53
|
+
| `Clock` | `{ now(): number }`. |
|
|
54
|
+
| `VirtualClock` | `Clock` plus `set(ms)` / `advance(ms)`. |
|
|
55
|
+
|
|
56
|
+
`cart.getClock()` returns `ClockSnapshot`: `{ now, framesElapsed, frameRate }`.
|
|
57
|
+
|
|
58
|
+
`step` advances the virtual clock by `1000 / frameRate` ms **after** each successful tick. A throwing `update` does **not** increment `framesElapsed` and `step(n)` stops after that tick.
|
|
59
|
+
|
|
60
|
+
## `CartHandle` (deterministic)
|
|
61
|
+
|
|
62
|
+
| Member | Meaning |
|
|
63
|
+
|---|---|
|
|
64
|
+
| `step(frames?)` | Run that many update/render ticks. Default `1`. No rAF. Clears `paused` so `timing.now` advances. Throws if `deterministic` was not set. Throws if the handle is destroyed. |
|
|
65
|
+
| `advance(ms)` | `step(round(ms * fps / 1000))`. |
|
|
66
|
+
| `schedule(action)` | Append a `ScriptedAction` for a future `atFrame`. |
|
|
67
|
+
| `getClock()` | Virtual clock snapshot. |
|
|
68
|
+
| `getRandomState()` | Dual sfc32 snapshot (`RandomState`). |
|
|
69
|
+
| `getReplayMetadata()` | Full replay bundle (below). |
|
|
70
|
+
|
|
71
|
+
`atFrame` is the `framesElapsed` passed into that tick’s `update`. The first tick is `0`. After `step(5)`, `framesElapsed === 5` and that value **is** the next `atFrame`.
|
|
72
|
+
|
|
73
|
+
Remount / `prepareCart` restores the **constructor** action list. `schedule` / harness `click` / `key` from the previous mount are dropped; constructor `actions` run again.
|
|
74
|
+
|
|
75
|
+
## `ScriptedAction`
|
|
76
|
+
|
|
77
|
+
Every action has `atFrame: number`, plus one of:
|
|
78
|
+
|
|
79
|
+
| `type` | Fields | Effect |
|
|
80
|
+
|---|---|---|
|
|
81
|
+
| `pointer` | `pointer: { kind: 'down' \| 'move' \| 'up', x, y }` | `PointerManager.inject` in **canvas pixels** (not CSS). `down` pushes a click for `hasClick` / `consumeClick`. |
|
|
82
|
+
| `key` | `key: string` | `KeyboardManager.inject`. The cart must `registerAction` in `getDefaultState`. |
|
|
83
|
+
| `event` | `event: HostEvent` | `hostChannel.dispatch` before `update`. |
|
|
84
|
+
| `asset` | `id`, `status: 'ready' \| 'failed'`, optional `detail` | Dispatch a host **state** event (not a Promise). |
|
|
85
|
+
|
|
86
|
+
Asset events:
|
|
87
|
+
|
|
88
|
+
| Export | Value |
|
|
89
|
+
|---|---|
|
|
90
|
+
| `ASSET_READY_EVENT` | `'cyberart.asset.ready'` |
|
|
91
|
+
| `ASSET_FAILED_EVENT` | `'cyberart.asset.failed'` |
|
|
92
|
+
|
|
93
|
+
Payload is `{ id }` or `{ id, detail }`. Carts that need assets `hostChannel.consume()`; they must not `await fetch` inside `update` / `render`.
|
|
94
|
+
|
|
95
|
+
## Replay
|
|
96
|
+
|
|
97
|
+
`getReplayMetadata()` → `ReplayMetadata`:
|
|
98
|
+
|
|
99
|
+
| Field | Meaning |
|
|
100
|
+
|---|---|
|
|
101
|
+
| `seed` | Token hash. |
|
|
102
|
+
| `clock` | `ClockSnapshot`. |
|
|
103
|
+
| `rng` | `RandomState`. |
|
|
104
|
+
| `actions` | Current scripted tape (constructor + `schedule`). |
|
|
105
|
+
| `applied` | `{ frame, action }[]` already fired. |
|
|
106
|
+
| `events` | Outbound `hostChannel.emit` log. |
|
|
107
|
+
| `state` | Exported serializable cart state. |
|
|
108
|
+
|
|
109
|
+
`describeReplayMismatch(a, b)` returns `string[]`. Empty means identical; otherwise each string names the first disagreement on that field (`seed`, `framesElapsed`, `now`, `frameRate`, `rng`, `actions[i]`, `applied[i]`, `events[i]`, `state`).
|
|
110
|
+
|
|
111
|
+
## Cart rules
|
|
112
|
+
|
|
113
|
+
Use `R` and `timing.now` / `timing.elapsedSinceStart`. Do **not** call `Math.random()`, `Date.now()`, `performance.now()`, `setTimeout`, or `requestAnimationFrame` from `update` / `render`.
|
|
114
|
+
|
|
115
|
+
Host routers: `now: () => cart.getClock().now`, seed-stable `createId`, `router.turn()` once per `step`. See [events](events.md).
|
|
116
|
+
|
|
117
|
+
## Seed and RNG
|
|
118
|
+
|
|
119
|
+
- `canonicalizeSeed(value)` → 64-hex hash.
|
|
120
|
+
- `resolveRuntimeSeed(seed, 'deterministic' \| 'live')` — deterministic always canonicalizes.
|
|
121
|
+
- `Random.getState()` / `setState()` — `setState` throws if the snapshot seed does not match.
|
|
122
|
+
- `metadata.generative: true` — saves only load onto the same hash.
|
|
123
|
+
|
|
124
|
+
## Errors
|
|
125
|
+
|
|
126
|
+
`runtime.onError(error, info)` with `FrameErrorInfo`: `{ phase: 'update' \| 'render' \| 'draw', consecutive, stopped }`.
|
|
127
|
+
|
|
128
|
+
The first throw is `{ consecutive: 1, stopped: false }`. The breaker is **120** consecutive failures (`stopped: true`). One throwing frame does not increment `framesElapsed`.
|
|
129
|
+
|
|
130
|
+
## Related types
|
|
131
|
+
|
|
132
|
+
`AppliedAction`, `ClockSnapshot`, `DeterministicRuntimeOptions`, `ReplayMetadata`, `ScriptedAction`, `RandomState`, `FrameErrorInfo`.
|