@utsp/runtime-client 0.8.0-nightly.20251209024647.83e7069 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";var I=Object.defineProperty;var z=Object.getOwnPropertyDescriptor;var N=Object.getOwnPropertyNames;var W=Object.prototype.hasOwnProperty;var q=(p,e,t)=>e in p?I(p,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):p[e]=t;var v=(p,e)=>I(p,"name",{value:e,configurable:!0});var j=(p,e)=>{for(var t in e)I(p,t,{get:e[t],enumerable:!0})},_=(p,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of N(e))!W.call(p,r)&&r!==t&&I(p,r,{get:()=>e[r],enumerable:!(n=z(e,r))||n.enumerable});return p};var X=p=>_(I({},"__esModule",{value:!0}),p);var s=(p,e,t)=>(q(p,typeof e!="symbol"?e+"":e,t),t);var V={};j(V,{AudioManager:()=>H.AudioManager,ClientRuntime:()=>A,RendererType:()=>x,ScalingMode:()=>G.ScalingMode});module.exports=X(V);var b=require("@utsp/core"),C=require("@utsp/render"),O=require("@utsp/types"),T=require("@utsp/input"),D=require("@utsp/network-client"),B=require("@utsp/audio");var Y=require("@utsp/types"),x=(t=>(t.TerminalGL="webgl",t.Terminal2D="terminal2d",t))(x||{});var F=class F{constructor(e=60){s(this,"frameCount",0);s(this,"fps",0);s(this,"fpsUpdateTime",0);s(this,"lastCoreTime",0);s(this,"lastRenderTime",0);s(this,"lastTotalTime",0);s(this,"coreTimeSamples",[]);s(this,"renderTimeSamples",[]);s(this,"totalTimeSamples",[]);s(this,"maxSamples");if(e<=0)throw new Error("maxSamples must be positive");this.maxSamples=e}startTracking(e){this.fpsUpdateTime=e,this.frameCount=0,this.fps=0}updateFPS(e){this.frameCount++,e-this.fpsUpdateTime>=1e3&&(this.fps=Math.round(this.frameCount*1e3/(e-this.fpsUpdateTime)),this.frameCount=0,this.fpsUpdateTime=e)}recordFrameTiming(e,t){let n=e+t;this.lastCoreTime=e,this.lastRenderTime=t,this.lastTotalTime=n,this.coreTimeSamples.push(e),this.renderTimeSamples.push(t),this.totalTimeSamples.push(n),this.coreTimeSamples.length>this.maxSamples&&(this.coreTimeSamples.shift(),this.renderTimeSamples.shift(),this.totalTimeSamples.shift())}getFPS(){return this.fps}getLastFrameTiming(){if(!(this.lastCoreTime===0&&this.lastRenderTime===0))return{coreTime:this.lastCoreTime,renderTime:this.lastRenderTime,totalTime:this.lastTotalTime}}getAverageFrameTiming(){if(this.coreTimeSamples.length!==0)return{coreTime:this.average(this.coreTimeSamples),renderTime:this.average(this.renderTimeSamples),totalTime:this.average(this.totalTimeSamples)}}getStats(){return{fps:this.fps,lastFrame:this.getLastFrameTiming(),avgFrame:this.getAverageFrameTiming(),sampleCount:this.coreTimeSamples.length,maxSamples:this.maxSamples}}reset(){this.frameCount=0,this.fps=0,this.fpsUpdateTime=0,this.lastCoreTime=0,this.lastRenderTime=0,this.lastTotalTime=0,this.coreTimeSamples=[],this.renderTimeSamples=[],this.totalTimeSamples=[]}average(e){return e.length===0?0:e.reduce((t,n)=>t+n,0)/e.length}};v(F,"PerformanceMonitor");var w=F;var U=require("@utsp/core");var E=class E{constructor(e,t={}){s(this,"renderer");s(this,"options");this.renderer=e,this.options={debug:t.debug??!1,useImageDataRendering:t.useImageDataRendering??!1}}async initialize(e){await this.loadDefaultFont(e),await this.waitForReady()}async waitForReady(e=100,t=50){this.log("Waiting for renderer to be ready");let n=0;return new Promise((r,i)=>{let o=v(()=>{if(n++,this.renderer.isReady())this.log(`Renderer ready after ${n*t}ms`),r();else if(n>=e){let l=`Renderer failed to be ready after ${e*t}ms`;this.log(l),i(new Error(l))}else setTimeout(o,t)},"check");o()})}async loadDefaultFont(e){this.log("Loading ASCII 8x8 font");let t=(0,U.createASCII8x8FontLoad)(1);e.hasBitmapFont(t.fontId)||e.loadBitmapFontById(t.fontId,{charWidth:t.width,charHeight:t.height,cellWidth:t.cellWidth,cellHeight:t.cellHeight,glyphs:new Map(t.characters.map(n=>[n.charCode,n.bitmap]))}),"setImageDataRendering"in this.renderer&&this.options.useImageDataRendering&&(this.renderer.setImageDataRendering(!0),this.log("ImageData rendering enabled")),this.log("Font loaded and event triggered")}render(e,t){if(!this.renderer.isReady())return console.warn("[RENDERER MANAGER] Renderer not ready"),!1;let n=e.getRenderState(t);if(!n||n.displays.length===0)return console.warn("[RENDERER MANAGER] No render state or no displays"),!1;let r=n.displays[0];return this.renderer.renderDisplayData(r),!0}getRenderer(){return this.renderer}getCanvas(){return this.renderer.getCanvas()}isReady(){return this.renderer.isReady()}destroy(){this.log("Destroying renderer"),this.renderer.destroy()}log(e){this.options.debug&&console.warn(`[RendererManager] ${e}`)}};v(E,"RendererManager");var P=E;var M=require("@utsp/input");var L=class L{constructor(e,t,n,r,i){s(this,"network");s(this,"core");s(this,"rendererManager");s(this,"performanceMonitor");s(this,"options");s(this,"audioManager",null);s(this,"postProcessCallback",null);s(this,"userId","");s(this,"lastReceivedTick",-1);s(this,"connected",!1);this.network=e,this.core=t,this.rendererManager=n,this.performanceMonitor=r,this.options={serverUrl:i.serverUrl,username:i.username,token:i.token??"",debug:i.debug??!1,autoReconnect:i.autoReconnect??!0,canvasWidth:i.canvasWidth,canvasHeight:i.canvasHeight}}async connect(){this.log(`Connecting to ${this.options.serverUrl}`),await this.network.connect(),this.connected=!0,this.log("Connected to server"),this.setupNetworkHandlers()}async joinGame(e){return new Promise((t,n)=>{this.network.send("join",{username:this.options.username,token:this.options.token}),this.network.on("join_response",r=>{if(r.success){this.userId=r.userId,this.log(`Joined game as ${this.userId}`);let i=this.core.createUser(this.userId,this.options.username);this.audioManager&&i.setAudioProcessor(this.audioManager),e.initUser(this.core,i,{username:this.options.username,token:this.options.token}),t(this.userId)}else n(new Error(r.error||"Failed to join game"))}),setTimeout(()=>n(new Error("Join timeout")),5e3)})}sendInput(e){let t=this.core.getUser(this.userId);if(!t)return;let n=this.rendererManager.getCanvas();if(n){let h=this.rendererManager.getRenderer()?.getOffsets?.(),R={offsetX:h?.offsetX??0,offsetY:h?.offsetY??0},y=M.InputCollector.collectMousePosition(e,n,this.options.canvasWidth,this.options.canvasHeight,R);t.setMousePosition(y.x,y.y,y.over),t.updateMacroMouse(y.x,y.y,y.isLeftDown),M.InputCollector.collectTouchPositions(e,n,this.options.canvasWidth,this.options.canvasHeight,10,R).forEach(S=>{t.setTouchPosition(S.id,S.x,S.y,S.over)})}let r=t.getInputBindingRegistry(),i=r.getAllAxes(),o=r.getAllButtons(),d=M.InputCollector.collectAxisSources(i,e),l=M.InputCollector.collectButtonSources(o,e),a=M.InputCollector.collectTextInputs(e);a.length>0&&t.setTextInputs(a);let c={};i.forEach(g=>{let h=r.evaluateAxis(g.bindingId,d);c[g.name]=h,t.setAxis(g.name,h)});let u={};o.forEach(g=>{let h=r.evaluateButton(g.bindingId,l);u[g.name]=h,t.setButton(g.name,h)});let m=t.getMouseDisplayInfo(),f=m?{x:m.localX,y:m.localY}:null;this.network.send("input",{timestamp:Date.now(),axes:c,buttons:u,mousePosition:f,textInputs:a})}disconnect(){this.connected&&(this.network.send("leave",{}),this.network.disconnect(),this.connected=!1,this.log("Disconnected from server"))}getUserId(){return this.userId}isConnected(){return this.connected}destroy(){this.disconnect(),this.network.destroy(),this.log("NetworkSync destroyed")}setupNetworkHandlers(){this.network.on("update",e=>{try{let t=this.convertToUint8Array(e,"update");if(!t)return;let n=this.core.applyUpdatePacketBuffer(this.userId,t);n?(this.postProcessCallback&&n.postProcessOrders&&n.postProcessOrders.length>0&&this.postProcessCallback(n.postProcessOrders),this.rendererManager.render(this.core,this.userId)):this.log("Failed to apply update packet")}catch(t){this.log(`Error applying update packet: ${t}`)}}),this.network.on("update-static",e=>{this.handleUpdatePacket(e,"update-static")}),this.network.on("update-dynamic",e=>{this.handleUpdatePacket(e,"update-dynamic")}),this.network.on("load",e=>{try{let t=this.convertToUint8Array(e,"load");if(!t)return;this.core.applyLoadPacket(t)?this.log("Load packet applied successfully"):this.log("Failed to apply load packet")}catch(t){this.log(`Error applying load packet: ${t}`)}}),this.network.on("input-bindings",e=>{this.core.applyInputBindingsLoadPacket(this.userId,e)?this.log("Input bindings configured"):this.log("Failed to apply input bindings")}),this.audioManager&&this.setupSoundHandlers(),this.network.on("disconnect",()=>{this.connected=!1,this.log("Disconnected from server")})}handleUpdatePacket(e,t){try{let n=this.convertToUint8Array(e,t);if(!n)return;let r=new DataView(n.buffer,n.byteOffset,n.byteLength),i=Number(r.getBigUint64(0,!1));if(t==="update-dynamic"&&i<this.lastReceivedTick)return;i>this.lastReceivedTick&&(this.lastReceivedTick=i);let o=this.core.applyUpdatePacketBuffer(this.userId,n);if(o){console.warn(`[CLIENT] ${t}: ${n.length}B (tick ${i}) - APPLIED`),this.postProcessCallback&&o.postProcessOrders&&o.postProcessOrders.length>0&&this.postProcessCallback(o.postProcessOrders),this.options.debug&&this.debugRenderState();let d=performance.now();this.rendererManager.render(this.core,this.userId);let l=performance.now()-d;this.performanceMonitor.recordFrameTiming(0,l),this.options.debug&&console.warn(`[CLIENT] render() completed for ${t} (${l.toFixed(2)}ms)`)}else console.warn(`[CLIENT] Failed to apply ${t} packet (tick ${i})`)}catch(n){this.log(`Error applying ${t} update packet: ${n}`),console.error(n)}}convertToUint8Array(e,t){return e instanceof Uint8Array?e:e instanceof ArrayBuffer?new Uint8Array(e):Array.isArray(e)?new Uint8Array(e):e.data&&Array.isArray(e.data)?new Uint8Array(e.data):typeof e=="object"&&e.type==="Buffer"?new Uint8Array(e.data):(this.log(`Unknown data type for ${t} packet: ${typeof e}`),null)}debugRenderState(){let e=this.core.getUser(this.userId);if(e){let n=e.getLayers();console.warn(`[CLIENT] User has ${n.length} layers`),n.forEach((r,i)=>{let o=r.getOrders(),d=r.getStatic();(o.length>0||d)&&console.warn(` Layer ${i}: ${o.length} orders, static=${d}, z=${r.getZOrder()}`)})}let t=this.rendererManager.isReady();console.warn(`[CLIENT] Renderer ready: ${t}`)}log(e){this.options.debug&&console.warn(`[NetworkSync] ${e}`)}setAudioManager(e){if(this.audioManager=e,e&&this.userId){let t=this.core.getUser(this.userId);t&&t.setAudioProcessor(e)}this.connected&&e&&this.setupSoundHandlers()}setPostProcessCallback(e){this.postProcessCallback=e}setupSoundHandlers(){this.audioManager&&(this.network.on("sound-load",async e=>{e.mode==="file"?await this.handleSoundLoad(e):e.mode==="external"&&await this.handleSoundExternalLoad(e)}),this.log("Sound handlers registered"))}async handleSoundLoad(e){if(!this.audioManager){this.log("Cannot load sounds: AudioManager not initialized");return}let t=this.audioManager.getSoundBank();if(!t){this.log("Cannot load sounds: SoundBank not initialized");return}for(let n of e.sounds)try{await t.loadFromData(n.soundId,n.name,n.data),this.log(`Loaded sound: ${n.name} (${n.data.length} bytes)`),this.sendAudioAck({type:"sound-loaded",soundId:n.soundId,name:n.name})}catch(r){console.error(`[NetworkSync] Failed to load sound "${n.name}":`,r),this.sendAudioAck({type:"sound-error",soundId:n.soundId,name:n.name,error:String(r)})}}async handleSoundExternalLoad(e){if(!this.audioManager){this.log("Cannot load external sounds: AudioManager not initialized");return}let t=this.audioManager.getSoundBank();if(!t){this.log("Cannot load external sounds: SoundBank not initialized");return}for(let n of e.sounds)try{await t.loadFromUrl(n.soundId,n.name,n.url),this.log(`Loaded external sound: ${n.name} from ${n.url}`),this.sendAudioAck({type:"sound-loaded",soundId:n.soundId,name:n.name})}catch(r){console.error(`[NetworkSync] Failed to load external sound "${n.name}":`,r),this.sendAudioAck({type:"sound-error",soundId:n.soundId,name:n.name,error:String(r)})}}sendAudioAck(e){this.network.send("audio-ack",e),this.log(`Sent audio-ack: ${e.type}`)}};v(L,"NetworkSync");var k=L;var $=class ${constructor(e){s(this,"core");s(this,"rendererManager");s(this,"rendererType");s(this,"input",null);s(this,"networkSync",null);s(this,"options");s(this,"running",!1);s(this,"startTime",0);s(this,"lastTimestamp",0);s(this,"userId","");s(this,"mode");s(this,"visibilityChangeHandler");s(this,"tickRate",30);s(this,"accumulatedTime",0);s(this,"FRAME_TIME_MIN",1e3/60);s(this,"lastRenderTimestamp",0);s(this,"rafId",0);s(this,"firstTickDone",!1);s(this,"performanceMonitor");s(this,"renderMode","continuous");s(this,"renderRequested",!1);s(this,"autoplayOverlay",null);s(this,"autoplay",!0);s(this,"audioManager",null);s(this,"postProcessOverlay",null);s(this,"postProcessOrderCollector",new b.PostProcessOrderCollector);this.mode=e.mode,e.mode==="local"?(this.options={mode:"local",application:e.application,container:e.container,debug:e.debug??!1,width:e.width??80,height:e.height??25,userId:e.userId??"local",username:e.username??"User",renderer:e.renderer??"webgl",useImageDataRendering:e.useImageDataRendering??!0,mobileInputConfig:e.mobileInputConfig,captureInput:e.captureInput??!1,inputEnabled:e.inputEnabled??!0,autoplay:e.autoplay??!0,autoplayOptions:e.autoplayOptions},this.userId=e.userId??"local",this.autoplay=e.autoplay??!0):(this.options={mode:"connected",application:e.application,container:e.container,serverUrl:e.serverUrl,username:e.username??"Player",debug:e.debug??!1,width:e.width??80,height:e.height??25,autoReconnect:e.autoReconnect??!0,token:e.token,renderer:e.renderer??"webgl",useImageDataRendering:e.useImageDataRendering??!0,mobileInputConfig:e.mobileInputConfig,captureInput:e.captureInput??!1,inputEnabled:e.inputEnabled??!0,autoplay:e.autoplay??!0,autoplayOptions:e.autoplayOptions},this.autoplay=e.autoplay??!0),this.log(`Initializing ClientRuntime (${this.mode} mode)`),this.core=new b.Core({mode:"client",maxUsers:this.mode==="local"?1:100}),this.core.onPaletteChanged(n=>{this.onCorePaletteChanged(n)}),this.core.onBitmapFontChanged(n=>{this.onCoreBitmapFontChanged(n)}),this.visibilityChangeHandler=()=>{!document.hidden&&this.running&&(this.lastTimestamp=performance.now(),this.accumulatedTime=0,this.log("Tab visible: Reset timing"))},document.addEventListener("visibilitychange",this.visibilityChangeHandler);let t=this.createRenderer();if(this.rendererType=this.options.renderer??"webgl",this.rendererManager=new P(t,{debug:this.options.debug,useImageDataRendering:this.options.useImageDataRendering}),this.options.inputEnabled?this.input=new T.UnifiedInputRouter({enableKeyboardMouse:!0,enableGamepad:!0,enableMobile:!0,targetElement:window,mobileTargetElement:void 0,debug:this.options.debug,keyboardConfig:{preventDefault:this.options.captureInput??!1,stopPropagation:this.options.captureInput??!1},mouseConfig:{preventDefault:this.options.captureInput??!1,stopPropagation:this.options.captureInput??!1},mobileConfig:{preventDefault:this.options.mobileInputConfig?.preventDefault??!0,passive:this.options.mobileInputConfig?.passive??!1,maxTouches:this.options.mobileInputConfig?.maxTouches??10}}):this.log("Input disabled (inputEnabled: false)"),this.performanceMonitor=new w(60),this.mode==="connected"){let n=this.options,r=new D.SocketIOClient({url:n.serverUrl,autoReconnect:n.autoReconnect,auth:{username:n.username,token:n.token},debug:this.options.debug});this.networkSync=new k(r,this.core,this.rendererManager,this.performanceMonitor,{serverUrl:n.serverUrl,username:n.username,token:n.token,debug:this.options.debug,autoReconnect:n.autoReconnect,canvasWidth:this.options.width,canvasHeight:this.options.height})}this.tickRate=e.tickRate??30,this.tickRate===0&&!e.renderMode?(this.renderMode="on-demand",this.log("tickRate is 0: automatically enabling on-demand render mode")):this.renderMode=e.renderMode??"continuous",this.log(`Configured: tickRate=${this.tickRate}, renderMode=${this.renderMode}`),this.log("ClientRuntime initialized")}getMode(){return this.mode}getRendererType(){return this.rendererType}isRunning(){return this.running}createRenderer(){if((this.options.renderer??"webgl")==="terminal2d"){this.log("Creating Terminal 2D renderer");let i=new C.Terminal2D(this.options.container,{fixedCols:this.options.width,fixedRows:this.options.height,cellAspectRatio:1});return this.options.useImageDataRendering&&(this.log("Enabling ImageData rendering (pixel-perfect, optimized)"),i.setImageDataRendering(!0)),this.log("Canvas 2D renderer created successfully"),i}this.log("Creating TerminalGL renderer");let n=document.createElement("canvas").getContext("webgl");if(!n)throw new Error("WebGL not supported. TerminalGL requires WebGL 1.0.");if(!n.getExtension("OES_element_index_uint"))throw new Error("OES_element_index_uint extension not supported. TerminalGL requires this extension for large terminals (256x256). Supported on 97% of devices (Android 4.3+, iOS 8+, Desktop).");if(!(this.options.container instanceof HTMLDivElement))throw new Error(`TerminalGL requires container to be an HTMLDivElement. Received: ${this.options.container.tagName}`);let r=new C.TerminalGL(this.options.container,{cols:this.options.width,rows:this.options.height,charWidth:8,charHeight:8,scalingMode:O.ScalingMode.None});return this.log("TerminalGL created successfully"),r}setTickRate(e){if(e<0||e>1e3)throw new Error(`Invalid tick rate: ${e}. Must be between 0 and 1000.`);if(this.mode==="connected"){this.log("setTickRate() has no effect in connected mode");return}this.tickRate=e,e===0&&this.renderMode==="continuous"?(this.renderMode="on-demand",this.log(`Tick rate set to ${e} TPS (update loop disabled, automatically switched to on-demand render mode)`)):this.log(`Tick rate set to ${e} TPS ${e===0?"(update loop disabled)":""}`)}setRenderMode(e){this.renderMode=e,this.log(`Render mode set to ${e}`)}setMaxFPS(e){if(e<=0||e>240)throw new Error(`Invalid FPS: ${e}. Must be between 1 and 240.`);this.FRAME_TIME_MIN=1e3/e,this.log(`Max FPS set to ${e}`)}getTickRate(){return this.tickRate}requestRender(){this.renderMode==="on-demand"?(this.renderRequested=!0,this.log("Render requested")):this.log("requestRender() has no effect in continuous mode")}async start(){if(this.running){this.log("Already running");return}this.log("Starting ClientRuntime"),this.audioManager=new B.AudioManager({debug:this.options.debug}),this.autoplay?(this.log("Autoplay enabled, initializing AudioManager..."),this.audioManager.initialize()):(this.log("Autoplay disabled, showing overlay..."),await new Promise(n=>{this.autoplayOverlay=new C.AutoplayOverlay(this.options.container,{...this.options.autoplayOptions,onStart:()=>{this.log("User clicked start button"),this.audioManager&&(this.audioManager.initialize(),this.audioManager.playStartSound()),n()}})}),this.log("Autoplay overlay dismissed, continuing startup...")),await this.rendererManager.initialize(this.core),this.log("Renderer is ready"),this.postProcessOverlay=new C.PostProcessOverlay(this.options.container),this.log("PostProcessOverlay created"),this.onCorePaletteChanged(this.core.getPalette()),this.log("Initial palette sent to renderer"),this.log("Calling application.init()"),await this.options.application.init(this.core,this);let e=this.rendererManager.getCanvas();e&&this.input&&this.input.setMobileTarget(e),this.mode==="connected"&&this.networkSync?(this.log("Connecting to server"),this.audioManager&&this.networkSync.setAudioManager(this.audioManager),this.networkSync.setPostProcessCallback(n=>{this.applyPostProcessOrders(n)}),await this.networkSync.connect(),this.userId=await this.networkSync.joinGame(this.options.application)):await this.createLocalUser();let t=this.core.getUser(this.userId);if(t){let n=t.getDisplays();if(n.length>0){let i=n[0].getSize(),o=i.x,d=i.y,l=this.rendererManager.getRenderer(),a,c;if(this.rendererType==="webgl"){let m=l.getGridSize();a=m.cols,c=m.rows}else{let u=l;a=u.getCols(),c=u.getRows()}(a!==o||c!==d)&&(this.log(`Adjusting renderer from ${a}\xD7${c} to match display ${o}\xD7${d}`),l.resize(o,d))}}this.input&&this.input.start(),this.running=!0,this.startTime=performance.now(),this.lastTimestamp=this.startTime,this.lastRenderTimestamp=this.startTime,this.accumulatedTime=0,this.firstTickDone=!1,this.performanceMonitor.reset(),this.performanceMonitor.startTracking(this.startTime),this.log("Starting main loop"),this.mainLoop(this.lastTimestamp)}async stop(){if(!this.running)return;this.log("Stopping ClientRuntime"),this.running=!1,this.rafId&&(cancelAnimationFrame(this.rafId),this.rafId=0),this.audioManager&&this.audioManager.stopAll(),this.input&&this.input.stop(),this.mode==="connected"&&this.networkSync&&this.networkSync.disconnect();let e=this.core.getUser(this.userId);e&&this.options.application.destroyUser&&this.options.application.destroyUser(this.core,e,"Runtime stopped"),this.log("ClientRuntime stopped")}getStats(){let e=this.performanceMonitor.getStats();return{mode:this.mode,running:this.running,userCount:this.core.getUsers().length,fps:e.fps,uptime:this.running?performance.now()-this.startTime:0,totalFrames:0,latency:void 0,lastFrameTiming:e.lastFrame,avgFrameTiming:e.avgFrame}}async destroy(){await this.stop(),this.log("Destroying ClientRuntime"),this.visibilityChangeHandler&&(document.removeEventListener("visibilitychange",this.visibilityChangeHandler),this.visibilityChangeHandler=void 0),this.autoplayOverlay&&(this.autoplayOverlay.destroy(),this.autoplayOverlay=null),this.audioManager&&(this.audioManager.destroy(),this.audioManager=null),this.postProcessOverlay&&(this.postProcessOverlay.destroy(),this.postProcessOverlay=null);let e=this.rendererManager.getRenderer();e&&"clearOnResizeCallback"in e&&e.clearOnResizeCallback(),this.options.application.destroy&&this.options.application.destroy(),this.rendererManager.destroy(),this.input&&this.input.destroy(),this.networkSync&&this.networkSync.destroy(),this.log("ClientRuntime destroyed")}async createLocalUser(){let e=this.options;this.log(`Creating local user: ${this.userId}`);let t=this.core.createUser(this.userId,e.username);if(this.audioManager&&t.setAudioProcessor(this.audioManager),this.log("Calling application.initUser()"),this.options.application.initUser(this.core,t,{username:e.username}),t.hasAudioConfigCommands()&&(this.log("Applying audio config commands from initUser()"),t.applyAudioConfigCommands(t.flushAudioConfigCommands())),t.hasPendingMacroOrders()){this.log("Applying macro orders from initUser()");let n=t.flushMacroOrders();t.applyMacroOrders(n)}if(t.hasPostProcessCommands()){this.log("Applying post-process commands from initUser()");let n=t.flushPostProcessCommands(),r=this.postProcessOrderCollector.convertCommands(n);this.applyPostProcessOrders(r)}t.needsSendSounds()&&(await this.loadSoundsFromRegistry(),t.clearSendSounds()),this.log("Performing initial render"),this.core.endTick(),this.render(),this.log("Initial render complete")}async loadSoundsFromRegistry(){if(!this.audioManager){this.log("Cannot load sounds: AudioManager not initialized");return}let e=this.audioManager.getSoundBank();if(!e){this.log("Cannot load sounds: SoundBank not initialized");return}let n=this.core.getSoundRegistry().getAll();if(n.length===0){this.log("No sounds to load from registry");return}this.log(`Loading ${n.length} sounds from Core registry into AudioManager...`);for(let r of n)try{r.loadType==="file"&&r.data?(await e.loadFromData(r.soundId,r.name,r.data),this.log(`\u{1F50A} Loaded sound: ${r.name} (${r.data.length} bytes)`)):r.loadType==="external"&&r.url&&(await e.loadFromUrl(r.soundId,r.name,r.url),this.log(`\u{1F50A} Loaded external sound: ${r.name} from ${r.url}`))}catch(i){console.error(`[ClientRuntime] Failed to load sound "${r.name}":`,i)}this.log(`\u2713 ${e.size} sounds loaded into AudioManager`)}mainLoop(e){if(!this.running){this.rafId&&(cancelAnimationFrame(this.rafId),this.rafId=0);return}if(this.renderMode==="on-demand"){this.renderRequested&&(this.renderRequested=!1,this.performanceMonitor.updateFPS(e),this.render()),this.rafId=requestAnimationFrame(o=>this.mainLoop(o));return}let t=e-this.lastRenderTimestamp;if(this.lastRenderTimestamp>0&&t<this.FRAME_TIME_MIN-1){this.rafId=requestAnimationFrame(o=>this.mainLoop(o));return}this.lastRenderTimestamp=e;let n=(e-this.lastTimestamp)/1e3,r=Math.min(n,.1);this.lastTimestamp=e,this.performanceMonitor.updateFPS(e);let i=this.core.getUser(this.userId);if(!i){this.rafId=requestAnimationFrame(o=>this.mainLoop(o));return}if(this.mode==="local"){if(this.tickRate===0){let a=performance.now();this.render();let c=performance.now()-a;this.performanceMonitor.recordFrameTiming(0,c),this.rafId=requestAnimationFrame(u=>this.mainLoop(u));return}if(!this.firstTickDone){this.firstTickDone=!0,this.lastTimestamp=e,this.accumulatedTime=0,this.rafId=requestAnimationFrame(a=>this.mainLoop(a));return}this.accumulatedTime+=r;let o=1/this.tickRate;this.accumulatedTime>.5&&(this.accumulatedTime=o);let d=0,l=0;for(;this.accumulatedTime>=o&&d<5;){let a=performance.now();if(this.collectAndApplyInput(i),this.options.application.update(this.core,o),this.options.application.updateUser(this.core,i,o),i.clearTextInputs(),i.hasAudioConfigCommands()&&i.applyAudioConfigCommands(i.flushAudioConfigCommands()),i.hasSoundCommands()&&i.applyAudioCommands(i.flushSoundCommands()),i.hasPostProcessCommands()){let u=i.flushPostProcessCommands(),m=this.postProcessOrderCollector.convertCommands(u);this.applyPostProcessOrders(m)}if(i.hasPendingMacroOrders()){let u=i.flushMacroOrders();i.applyMacroOrders(u)}let c=i.updateMacros();i.processMacroEvents(c),this.core.endTickSplit(),l+=performance.now()-a,this.accumulatedTime-=o,d++}if(d>0){let a=performance.now();this.render();let c=performance.now()-a;this.performanceMonitor.recordFrameTiming(l,c)}}else{let o=i.updateMacros();i.processMacroEvents(o),this.collectAndSendInput()}this.rafId=requestAnimationFrame(o=>this.mainLoop(o))}collectAndApplyInput(e){if(!this.input)return;let t=this.rendererManager.getCanvas();if(t){let d=e.getDisplays(),l=d.length>0?d[0].size.x:this.options.width,a=d.length>0?d[0].size.y:this.options.height,u=this.rendererManager.getRenderer()?.getOffsets?.(),m={offsetX:u?.offsetX??0,offsetY:u?.offsetY??0},f=T.InputCollector.collectMousePosition(this.input,t,l,a,m);e.setMousePosition(f.x,f.y,f.over),e.updateMacroMouse(f.x,f.y,f.isLeftDown);let g=T.InputCollector.collectTouchPositions(this.input,t,l,a,10,m);this.options.debug&&g.length>0&&console.warn("\u{1F4F1} Collected touches:",g),g.forEach(h=>{e.setTouchPosition(h.id,h.x,h.y,h.over)})}let n=T.InputCollector.collectTextInputs(this.input);n.length>0&&e.setTextInputs(n);let r=e.getInputBindingRegistry(),i=r.getAllAxes(),o=r.getAllButtons();if(i.length>0||o.length>0){let d=T.InputCollector.collectAxisSources(i,this.input),l=T.InputCollector.collectButtonSourcesWithTransitions(o,this.input);i.forEach(a=>{let c=r.evaluateAxis(a.bindingId,d);e.setAxis(a.name,c)}),o.forEach(a=>{let c=new Map,u=new Map,m=new Map;for(let[R,y]of l)c.set(R,y.pressed),u.set(R,y.justPressed),m.set(R,y.justReleased);let f=r.evaluateButton(a.bindingId,c),g=r.evaluateButton(a.bindingId,u),h=r.evaluateButton(a.bindingId,m);e.setButton(a.name,f),e.setButton(`${a.name}_justPressed`,g),e.setButton(`${a.name}_justReleased`,h)})}this.input.poll?.()}collectAndSendInput(){!this.networkSync||!this.input||this.networkSync.sendInput(this.input)}render(){let e=this.core.getUser(this.userId);if(e){let t=e.getMacroRenderOrders();for(let[n,r]of t){let i=e.getLayerById(n);i&&r.length>0&&i.addTemporaryOrders(r)}}this.rendererManager.render(this.core,this.userId)}onCorePaletteChanged(e){this.log(`Palette changed, updating renderer (${e.size} colors)`);let t=[];for(let r=0;r<256;r++){let i=e.get(r);i?t.push({r:i.r,g:i.g,b:i.b,a:i.a}):t.push({r:0,g:0,b:0,a:0})}let n=this.rendererManager.getRenderer();if(!n){console.warn("[ClientRuntime] Cannot update palette: renderer is null");return}"setPalette"in n&&typeof n.setPalette=="function"?(n.setPalette(t),this.log("\u2713 Renderer palette updated successfully")):console.warn("[ClientRuntime] Renderer does not have setPalette method")}onCoreBitmapFontChanged(e){this.log(`Bitmap font ${e} changed, updating renderer`);let t=this.core.getBitmapFont(e);if(!t){console.warn(`[ClientRuntime] Font ${e} not found in registry`);return}let n=new Map,r=0;for(let o=0;o<256;o++){let d=t.getGlyph(o);d&&(n.set(o,d),r++)}this.log(`Built bitmap font map with ${r} glyphs, dimensions: ${t.getCharWidth()}x${t.getCharHeight()} (cell: ${t.getCellWidth()}x${t.getCellHeight()})`);let i=this.rendererManager.getRenderer();if(!i){console.warn("[ClientRuntime] Cannot update font: renderer is null");return}"setBitmapFont"in i&&typeof i.setBitmapFont=="function"?(i.setBitmapFont(n,t.getCharWidth(),t.getCharHeight(),t.getCellWidth(),t.getCellHeight()),this.log("\u2713 Renderer bitmap font updated successfully"),this.postProcessOverlay&&"syncWithRenderer"in this.postProcessOverlay&&"getCellWidth"in i&&"getCellHeight"in i&&"getCurrentScale"in i&&"getGridSize"in i&&(this.postProcessOverlay.syncWithRenderer(i),this.log("\u2713 PostProcessOverlay synced with renderer dimensions"))):console.warn("[ClientRuntime] Renderer does not have setBitmapFont method")}getAudioManager(){return this.audioManager}getAudioContext(){return this.audioManager?.getContext()??null}applyAmbientEffectConfig(e){let t=this.rendererManager?.getRenderer();t&&"setAmbientEffect"in t&&(e.enabled?t.setAmbientEffect({blur:e.blur,scale:e.scale}):t.setAmbientEffect(!1),this.log(`Ambient effect ${e.enabled?"enabled":"disabled"}`))}applyPostProcessOrders(e){if(!this.postProcessOverlay){this.log("PostProcessOverlay not initialized, skipping post-process orders");return}for(let t of e)switch(t.type){case b.PostProcessOrderType.SetConfig:{let n={};t.scanlines&&(n.scanlines={enabled:t.scanlines.enabled,opacity:t.scanlines.opacity,pattern:this.patternFromType(t.scanlines.pattern),color:{r:t.scanlines.colorR,g:t.scanlines.colorG,b:t.scanlines.colorB}}),t.ambientEffect&&(n.ambientEffect={enabled:t.ambientEffect.enabled,blur:t.ambientEffect.blur,scale:t.ambientEffect.scale}),this.postProcessOverlay.setConfig(Object.keys(n).length>0?n:null),n.ambientEffect&&this.applyAmbientEffectConfig(n.ambientEffect);break}case b.PostProcessOrderType.SetScanlines:{this.postProcessOverlay.setScanlines({enabled:t.enabled,opacity:t.opacity,pattern:this.patternFromType(t.pattern),color:{r:t.colorR,g:t.colorG,b:t.colorB}});break}case b.PostProcessOrderType.SetAmbientEffect:{this.applyAmbientEffectConfig({enabled:t.enabled,blur:t.blur,scale:t.scale});break}case b.PostProcessOrderType.SetScalingMode:{this.applyScalingMode(t.mode);break}case b.PostProcessOrderType.SetGrid:{this.applyGridConfig(t);break}}}applyScalingMode(e){let t=this.rendererManager.getRenderer();if(t&&"setScalingMode"in t){let n=(0,O.valueToScalingMode)(e);t.setScalingMode(n),this.log(`Scaling mode set to ${n}`)}}applyGridConfig(e){let t=this.rendererManager.getRenderer();if(t&&"setGrid"in t){let n=e.colorA/255;t.setGrid({enabled:e.enabled,color:`rgba(${e.colorR},${e.colorG},${e.colorB},${n})`,lineWidth:e.lineWidth}),this.log(`Grid ${e.enabled?"enabled":"disabled"}`)}}patternFromType(e){switch(e){case 0:return"horizontal";case 1:return"vertical";case 2:return"grid";default:return"horizontal"}}getPostProcessOverlay(){return this.postProcessOverlay}log(e){this.options.debug&&console.warn(`[ClientRuntime/${this.mode}] ${e}`)}};v($,"ClientRuntime");var A=$;var G=require("@utsp/render"),H=require("@utsp/audio");
1
+ "use strict";var M=Object.defineProperty;var z=Object.getOwnPropertyDescriptor;var W=Object.getOwnPropertyNames;var N=Object.prototype.hasOwnProperty;var q=(p,e,t)=>e in p?M(p,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):p[e]=t;var v=(p,e)=>M(p,"name",{value:e,configurable:!0});var j=(p,e)=>{for(var t in e)M(p,t,{get:e[t],enumerable:!0})},_=(p,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of W(e))!N.call(p,i)&&i!==t&&M(p,i,{get:()=>e[i],enumerable:!(n=z(e,i))||n.enumerable});return p};var X=p=>_(M({},"__esModule",{value:!0}),p);var s=(p,e,t)=>(q(p,typeof e!="symbol"?e+"":e,t),t);var V={};j(V,{AudioManager:()=>G.AudioManager,ClientRuntime:()=>A,RendererType:()=>x,ScalingMode:()=>H.ScalingMode});module.exports=X(V);var b=require("@utsp/core"),C=require("@utsp/render"),O=require("@utsp/types"),T=require("@utsp/input"),U=require("@utsp/network-client"),D=require("@utsp/audio");var Y=require("@utsp/types"),x=(t=>(t.TerminalGL="webgl",t.Terminal2D="terminal2d",t))(x||{});var B=class B{constructor(e=60){s(this,"frameCount",0);s(this,"fps",0);s(this,"fpsUpdateTime",0);s(this,"lastCoreTime",0);s(this,"lastRenderTime",0);s(this,"lastTotalTime",0);s(this,"coreTimeSamples",[]);s(this,"renderTimeSamples",[]);s(this,"totalTimeSamples",[]);s(this,"maxSamples");if(e<=0)throw new Error("maxSamples must be positive");this.maxSamples=e}startTracking(e){this.fpsUpdateTime=e,this.frameCount=0,this.fps=0}updateFPS(e){this.frameCount++,e-this.fpsUpdateTime>=1e3&&(this.fps=Math.round(this.frameCount*1e3/(e-this.fpsUpdateTime)),this.frameCount=0,this.fpsUpdateTime=e)}recordFrameTiming(e,t){let n=e+t;this.lastCoreTime=e,this.lastRenderTime=t,this.lastTotalTime=n,this.coreTimeSamples.push(e),this.renderTimeSamples.push(t),this.totalTimeSamples.push(n),this.coreTimeSamples.length>this.maxSamples&&(this.coreTimeSamples.shift(),this.renderTimeSamples.shift(),this.totalTimeSamples.shift())}getFPS(){return this.fps}getLastFrameTiming(){if(!(this.lastCoreTime===0&&this.lastRenderTime===0))return{coreTime:this.lastCoreTime,renderTime:this.lastRenderTime,totalTime:this.lastTotalTime}}getAverageFrameTiming(){if(this.coreTimeSamples.length!==0)return{coreTime:this.average(this.coreTimeSamples),renderTime:this.average(this.renderTimeSamples),totalTime:this.average(this.totalTimeSamples)}}getStats(){return{fps:this.fps,lastFrame:this.getLastFrameTiming(),avgFrame:this.getAverageFrameTiming(),sampleCount:this.coreTimeSamples.length,maxSamples:this.maxSamples}}reset(){this.frameCount=0,this.fps=0,this.fpsUpdateTime=0,this.lastCoreTime=0,this.lastRenderTime=0,this.lastTotalTime=0,this.coreTimeSamples=[],this.renderTimeSamples=[],this.totalTimeSamples=[]}average(e){return e.length===0?0:e.reduce((t,n)=>t+n,0)/e.length}};v(B,"PerformanceMonitor");var S=B;var L=require("@utsp/core");var F=class F{constructor(e,t={}){s(this,"renderer");s(this,"options");this.renderer=e,this.options={debug:t.debug??!1,useImageDataRendering:t.useImageDataRendering??!1}}async initialize(e){await this.loadDefaultFont(e),await this.waitForReady()}async waitForReady(e=100,t=50){this.log("Waiting for renderer to be ready");let n=0;return new Promise((i,r)=>{let o=v(()=>{if(n++,this.renderer.isReady())this.log(`Renderer ready after ${n*t}ms`),i();else if(n>=e){let c=`Renderer failed to be ready after ${e*t}ms`;this.log(c),r(new Error(c))}else setTimeout(o,t)},"check");o()})}async loadDefaultFont(e){this.log("Loading ASCII 8x8 font");let t=(0,L.createASCII8x8FontLoad)(1);e.hasBitmapFont(t.fontId)||e.loadBitmapFontById(t.fontId,{charWidth:t.width,charHeight:t.height,cellWidth:t.cellWidth,cellHeight:t.cellHeight,glyphs:new Map(t.characters.map(n=>[n.charCode,n.bitmap]))}),"setImageDataRendering"in this.renderer&&this.options.useImageDataRendering&&(this.renderer.setImageDataRendering(!0),this.log("ImageData rendering enabled")),this.log("Font loaded and event triggered")}render(e,t){if(!this.renderer.isReady())return console.warn("[RENDERER MANAGER] Renderer not ready"),!1;let n=e.getRenderState(t);if(!n||n.displays.length===0)return console.warn("[RENDERER MANAGER] No render state or no displays"),!1;let i=n.displays[0];return this.renderer.renderDisplayData(i),!0}getRenderer(){return this.renderer}getCanvas(){return this.renderer.getCanvas()}isReady(){return this.renderer.isReady()}destroy(){this.log("Destroying renderer"),this.renderer.destroy()}log(e){this.options.debug&&console.warn(`[RendererManager] ${e}`)}};v(F,"RendererManager");var I=F;var w=require("@utsp/input");var E=class E{constructor(e,t,n,i,r){s(this,"network");s(this,"core");s(this,"rendererManager");s(this,"performanceMonitor");s(this,"options");s(this,"audioManager",null);s(this,"postProcessCallback",null);s(this,"userId","");s(this,"lastReceivedTick",-1);s(this,"connected",!1);this.network=e,this.core=t,this.rendererManager=n,this.performanceMonitor=i,this.options={serverUrl:r.serverUrl,username:r.username,token:r.token??"",debug:r.debug??!1,autoReconnect:r.autoReconnect??!0,canvasWidth:r.canvasWidth,canvasHeight:r.canvasHeight}}async connect(){this.log(`Connecting to ${this.options.serverUrl}`),await this.network.connect(),this.connected=!0,this.log("Connected to server"),this.setupNetworkHandlers()}async joinGame(e){return new Promise((t,n)=>{this.network.send("join",{username:this.options.username,token:this.options.token}),this.network.on("join_response",i=>{if(i.success){this.userId=i.userId,this.log(`Joined game as ${this.userId}`);let r=this.core.createUser(this.userId,this.options.username);this.audioManager&&r.setAudioProcessor(this.audioManager),e.initUser(this.core,r,{username:this.options.username,token:this.options.token}),t(this.userId)}else n(new Error(i.error||"Failed to join game"))}),setTimeout(()=>n(new Error("Join timeout")),5e3)})}sendInput(e){let t=this.core.getUser(this.userId);if(!t)return;let n=this.rendererManager.getCanvas();if(n){let h=this.rendererManager.getRenderer()?.getOffsets?.(),R={offsetX:h?.offsetX??0,offsetY:h?.offsetY??0},y=w.InputCollector.collectMousePosition(e,n,this.options.canvasWidth,this.options.canvasHeight,R);t.setMousePosition(y.x,y.y,y.over),t.updateMacroMouse(y.x,y.y,y.isLeftDown),w.InputCollector.collectTouchPositions(e,n,this.options.canvasWidth,this.options.canvasHeight,10,R).forEach(k=>{t.setTouchPosition(k.id,k.x,k.y,k.over)})}let i=t.getInputBindingRegistry(),r=i.getAllAxes(),o=i.getAllButtons(),a=w.InputCollector.collectAxisSources(r,e),c=w.InputCollector.collectButtonSources(o,e),d=w.InputCollector.collectTextInputs(e);d.length>0&&t.setTextInputs(d);let u={};r.forEach(g=>{let h=i.evaluateAxis(g.bindingId,a);u[g.name]=h,t.setAxis(g.name,h)});let l={};o.forEach(g=>{let h=i.evaluateButton(g.bindingId,c);l[g.name]=h,t.setButton(g.name,h)});let m=t.getMouseDisplayInfo(),f=m?{x:m.localX,y:m.localY}:null;this.network.send("input",{timestamp:Date.now(),axes:u,buttons:l,mousePosition:f,textInputs:d})}disconnect(){this.connected&&(this.network.send("leave",{}),this.network.disconnect(),this.connected=!1,this.log("Disconnected from server"))}getUserId(){return this.userId}isConnected(){return this.connected}destroy(){this.disconnect(),this.network.destroy(),this.log("NetworkSync destroyed")}setupNetworkHandlers(){this.network.on("update",e=>{try{let t=this.convertToUint8Array(e,"update");if(!t)return;let n=this.core.applyUpdatePacketBuffer(this.userId,t);n?(this.postProcessCallback&&n.postProcessOrders&&n.postProcessOrders.length>0&&this.postProcessCallback(n.postProcessOrders),this.rendererManager.render(this.core,this.userId)):this.log("Failed to apply update packet")}catch(t){this.log(`Error applying update packet: ${t}`)}}),this.network.on("update-static",e=>{this.handleUpdatePacket(e,"update-static")}),this.network.on("update-dynamic",e=>{this.handleUpdatePacket(e,"update-dynamic")}),this.network.on("load",e=>{try{let t=this.convertToUint8Array(e,"load");if(!t)return;this.core.applyLoadPacket(t)?this.log("Load packet applied successfully"):this.log("Failed to apply load packet")}catch(t){this.log(`Error applying load packet: ${t}`)}}),this.network.on("input-bindings",e=>{this.core.applyInputBindingsLoadPacket(this.userId,e)?this.log("Input bindings configured"):this.log("Failed to apply input bindings")}),this.audioManager&&this.setupSoundHandlers(),this.network.on("disconnect",()=>{this.connected=!1,this.log("Disconnected from server")})}handleUpdatePacket(e,t){try{let n=this.convertToUint8Array(e,t);if(!n)return;let i=new DataView(n.buffer,n.byteOffset,n.byteLength),r=Number(i.getBigUint64(0,!1));if(t==="update-dynamic"&&r<this.lastReceivedTick)return;r>this.lastReceivedTick&&(this.lastReceivedTick=r);let o=this.core.applyUpdatePacketBuffer(this.userId,n);if(o){console.warn(`[CLIENT] ${t}: ${n.length}B (tick ${r}) - APPLIED`),this.postProcessCallback&&o.postProcessOrders&&o.postProcessOrders.length>0&&this.postProcessCallback(o.postProcessOrders),this.options.debug&&this.debugRenderState();let a=performance.now();this.rendererManager.render(this.core,this.userId);let c=performance.now()-a;this.performanceMonitor.recordFrameTiming(0,c),this.options.debug&&console.warn(`[CLIENT] render() completed for ${t} (${c.toFixed(2)}ms)`)}else console.warn(`[CLIENT] Failed to apply ${t} packet (tick ${r})`)}catch(n){this.log(`Error applying ${t} update packet: ${n}`),console.error(n)}}convertToUint8Array(e,t){return e instanceof Uint8Array?e:e instanceof ArrayBuffer?new Uint8Array(e):Array.isArray(e)?new Uint8Array(e):e.data&&Array.isArray(e.data)?new Uint8Array(e.data):typeof e=="object"&&e.type==="Buffer"?new Uint8Array(e.data):(this.log(`Unknown data type for ${t} packet: ${typeof e}`),null)}debugRenderState(){let e=this.core.getUser(this.userId);if(e){let n=e.getLayers();console.warn(`[CLIENT] User has ${n.length} layers`),n.forEach((i,r)=>{let o=i.getOrders(),a=i.getStatic();(o.length>0||a)&&console.warn(` Layer ${r}: ${o.length} orders, static=${a}, z=${i.getZOrder()}`)})}let t=this.rendererManager.isReady();console.warn(`[CLIENT] Renderer ready: ${t}`)}log(e){this.options.debug&&console.warn(`[NetworkSync] ${e}`)}setAudioManager(e){if(this.audioManager=e,e&&this.userId){let t=this.core.getUser(this.userId);t&&t.setAudioProcessor(e)}this.connected&&e&&this.setupSoundHandlers()}setPostProcessCallback(e){this.postProcessCallback=e}setupSoundHandlers(){this.audioManager&&(this.network.on("sound-load",async e=>{e.mode==="file"?await this.handleSoundLoad(e):e.mode==="external"&&await this.handleSoundExternalLoad(e)}),this.log("Sound handlers registered"))}async handleSoundLoad(e){if(!this.audioManager){this.log("Cannot load sounds: AudioManager not initialized");return}let t=this.audioManager.getSoundBank();if(!t){this.log("Cannot load sounds: SoundBank not initialized");return}for(let n of e.sounds)try{await t.loadFromData(n.soundId,n.name,n.data),this.log(`Loaded sound: ${n.name} (${n.data.length} bytes)`),this.sendAudioAck({type:"sound-loaded",soundId:n.soundId,name:n.name})}catch(i){console.error(`[NetworkSync] Failed to load sound "${n.name}":`,i),this.sendAudioAck({type:"sound-error",soundId:n.soundId,name:n.name,error:String(i)})}}async handleSoundExternalLoad(e){if(!this.audioManager){this.log("Cannot load external sounds: AudioManager not initialized");return}let t=this.audioManager.getSoundBank();if(!t){this.log("Cannot load external sounds: SoundBank not initialized");return}for(let n of e.sounds)try{await t.loadFromUrl(n.soundId,n.name,n.url),this.log(`Loaded external sound: ${n.name} from ${n.url}`),this.sendAudioAck({type:"sound-loaded",soundId:n.soundId,name:n.name})}catch(i){console.error(`[NetworkSync] Failed to load external sound "${n.name}":`,i),this.sendAudioAck({type:"sound-error",soundId:n.soundId,name:n.name,error:String(i)})}}sendAudioAck(e){this.network.send("audio-ack",e),this.log(`Sent audio-ack: ${e.type}`)}sendBridge(e,t){this.network.sendBridge(e,t),this.log(`Bridge sent on channel '${e}'`)}onBridge(e,t){this.network.onBridge(e,t),this.log(`Bridge handler registered for channel '${e}'`)}offBridge(e,t){this.network.offBridge(e,t),this.log(`Bridge handler removed for channel '${e}'`)}removeAllBridgeHandlers(e){this.network.removeAllBridgeHandlers(e),this.log(e?`All bridge handlers removed for '${e}'`:"All bridge handlers removed")}};v(E,"NetworkSync");var P=E;var $=class ${constructor(e){s(this,"core");s(this,"rendererManager");s(this,"rendererType");s(this,"input",null);s(this,"networkSync",null);s(this,"options");s(this,"running",!1);s(this,"startTime",0);s(this,"lastTimestamp",0);s(this,"userId","");s(this,"mode");s(this,"visibilityChangeHandler");s(this,"tickRate",30);s(this,"accumulatedTime",0);s(this,"FRAME_TIME_MIN",1e3/60);s(this,"lastRenderTimestamp",0);s(this,"rafId",0);s(this,"firstTickDone",!1);s(this,"performanceMonitor");s(this,"renderMode","continuous");s(this,"renderRequested",!1);s(this,"autoplayOverlay",null);s(this,"autoplay",!0);s(this,"audioManager",null);s(this,"postProcessOverlay",null);s(this,"postProcessOrderCollector",new b.PostProcessOrderCollector);s(this,"localBridgeHandlers",new Map);s(this,"localBridgeWildcardHandlers",new Set);this.mode=e.mode,e.mode==="local"?(this.options={mode:"local",application:e.application,container:e.container,debug:e.debug??!1,width:e.width??80,height:e.height??25,userId:e.userId??"local",username:e.username??"User",renderer:e.renderer??"webgl",useImageDataRendering:e.useImageDataRendering??!0,mobileInputConfig:e.mobileInputConfig,captureInput:e.captureInput??!1,inputEnabled:e.inputEnabled??!0,autoplay:e.autoplay??!0,autoplayOptions:e.autoplayOptions},this.userId=e.userId??"local",this.autoplay=e.autoplay??!0):(this.options={mode:"connected",application:e.application,container:e.container,serverUrl:e.serverUrl,username:e.username??"Player",debug:e.debug??!1,width:e.width??80,height:e.height??25,autoReconnect:e.autoReconnect??!0,token:e.token,renderer:e.renderer??"webgl",useImageDataRendering:e.useImageDataRendering??!0,mobileInputConfig:e.mobileInputConfig,captureInput:e.captureInput??!1,inputEnabled:e.inputEnabled??!0,autoplay:e.autoplay??!0,autoplayOptions:e.autoplayOptions},this.autoplay=e.autoplay??!0),this.log(`Initializing ClientRuntime (${this.mode} mode)`),this.core=new b.Core({mode:"client",maxUsers:this.mode==="local"?1:100}),this.core.onPaletteChanged(n=>{this.onCorePaletteChanged(n)}),this.core.onBitmapFontChanged(n=>{this.onCoreBitmapFontChanged(n)}),this.visibilityChangeHandler=()=>{!document.hidden&&this.running&&(this.lastTimestamp=performance.now(),this.accumulatedTime=0,this.log("Tab visible: Reset timing"))},document.addEventListener("visibilitychange",this.visibilityChangeHandler);let t=this.createRenderer();if(this.rendererType=this.options.renderer??"webgl",this.rendererManager=new I(t,{debug:this.options.debug,useImageDataRendering:this.options.useImageDataRendering}),this.options.inputEnabled?this.input=new T.UnifiedInputRouter({enableKeyboardMouse:!0,enableGamepad:!0,enableMobile:!0,targetElement:window,mobileTargetElement:void 0,debug:this.options.debug,keyboardConfig:{preventDefault:this.options.captureInput??!1,stopPropagation:this.options.captureInput??!1},mouseConfig:{preventDefault:this.options.captureInput??!1,stopPropagation:this.options.captureInput??!1},mobileConfig:{preventDefault:this.options.mobileInputConfig?.preventDefault??!0,passive:this.options.mobileInputConfig?.passive??!1,maxTouches:this.options.mobileInputConfig?.maxTouches??10}}):this.log("Input disabled (inputEnabled: false)"),this.performanceMonitor=new S(60),this.mode==="connected"){let n=this.options,i=new U.SocketIOClient({url:n.serverUrl,autoReconnect:n.autoReconnect,auth:{username:n.username,token:n.token},debug:this.options.debug});this.networkSync=new P(i,this.core,this.rendererManager,this.performanceMonitor,{serverUrl:n.serverUrl,username:n.username,token:n.token,debug:this.options.debug,autoReconnect:n.autoReconnect,canvasWidth:this.options.width,canvasHeight:this.options.height})}this.tickRate=e.tickRate??30,this.tickRate===0&&!e.renderMode?(this.renderMode="on-demand",this.log("tickRate is 0: automatically enabling on-demand render mode")):this.renderMode=e.renderMode??"continuous",this.log(`Configured: tickRate=${this.tickRate}, renderMode=${this.renderMode}`),this.log("ClientRuntime initialized")}getMode(){return this.mode}getRendererType(){return this.rendererType}isRunning(){return this.running}createRenderer(){if((this.options.renderer??"webgl")==="terminal2d"){this.log("Creating Terminal 2D renderer");let r=new C.Terminal2D(this.options.container,{fixedCols:this.options.width,fixedRows:this.options.height,cellAspectRatio:1});return this.options.useImageDataRendering&&(this.log("Enabling ImageData rendering (pixel-perfect, optimized)"),r.setImageDataRendering(!0)),this.log("Canvas 2D renderer created successfully"),r}this.log("Creating TerminalGL renderer");let n=document.createElement("canvas").getContext("webgl");if(!n)throw new Error("WebGL not supported. TerminalGL requires WebGL 1.0.");if(!n.getExtension("OES_element_index_uint"))throw new Error("OES_element_index_uint extension not supported. TerminalGL requires this extension for large terminals (256x256). Supported on 97% of devices (Android 4.3+, iOS 8+, Desktop).");if(!(this.options.container instanceof HTMLDivElement))throw new Error(`TerminalGL requires container to be an HTMLDivElement. Received: ${this.options.container.tagName}`);let i=new C.TerminalGL(this.options.container,{cols:this.options.width,rows:this.options.height,charWidth:8,charHeight:8,scalingMode:O.ScalingMode.None});return this.log("TerminalGL created successfully"),i}setTickRate(e){if(e<0||e>1e3)throw new Error(`Invalid tick rate: ${e}. Must be between 0 and 1000.`);if(this.mode==="connected"){this.log("setTickRate() has no effect in connected mode");return}this.tickRate=e,e===0&&this.renderMode==="continuous"?(this.renderMode="on-demand",this.log(`Tick rate set to ${e} TPS (update loop disabled, automatically switched to on-demand render mode)`)):this.log(`Tick rate set to ${e} TPS ${e===0?"(update loop disabled)":""}`)}setRenderMode(e){this.renderMode=e,this.log(`Render mode set to ${e}`)}setMaxFPS(e){if(e<=0||e>240)throw new Error(`Invalid FPS: ${e}. Must be between 1 and 240.`);this.FRAME_TIME_MIN=1e3/e,this.log(`Max FPS set to ${e}`)}getTickRate(){return this.tickRate}requestRender(){this.renderMode==="on-demand"?(this.renderRequested=!0,this.log("Render requested")):this.log("requestRender() has no effect in continuous mode")}async start(){if(this.running){this.log("Already running");return}this.log("Starting ClientRuntime"),this.audioManager=new D.AudioManager({debug:this.options.debug}),this.autoplay?(this.log("Autoplay enabled, initializing AudioManager..."),this.audioManager.initialize()):(this.log("Autoplay disabled, showing overlay..."),await new Promise(n=>{this.autoplayOverlay=new C.AutoplayOverlay(this.options.container,{...this.options.autoplayOptions,onStart:()=>{this.log("User clicked start button"),this.audioManager&&(this.audioManager.initialize(),this.audioManager.playStartSound()),n()}})}),this.log("Autoplay overlay dismissed, continuing startup...")),await this.rendererManager.initialize(this.core),this.log("Renderer is ready"),this.postProcessOverlay=new C.PostProcessOverlay(this.options.container),this.log("PostProcessOverlay created"),this.onCorePaletteChanged(this.core.getPalette()),this.log("Initial palette sent to renderer"),this.log("Calling application.init()"),await this.options.application.init(this.core,this);let e=this.rendererManager.getCanvas();e&&this.input&&this.input.setMobileTarget(e),this.mode==="connected"&&this.networkSync?(this.log("Connecting to server"),this.audioManager&&this.networkSync.setAudioManager(this.audioManager),this.networkSync.setPostProcessCallback(n=>{this.applyPostProcessOrders(n)}),await this.networkSync.connect(),this.userId=await this.networkSync.joinGame(this.options.application)):await this.createLocalUser();let t=this.core.getUser(this.userId);if(t){let n=t.getDisplays();if(n.length>0){let r=n[0].getSize(),o=r.x,a=r.y,c=this.rendererManager.getRenderer(),d,u;if(this.rendererType==="webgl"){let m=c.getGridSize();d=m.cols,u=m.rows}else{let l=c;d=l.getCols(),u=l.getRows()}(d!==o||u!==a)&&(this.log(`Adjusting renderer from ${d}\xD7${u} to match display ${o}\xD7${a}`),c.resize(o,a))}}this.input&&this.input.start(),this.running=!0,this.startTime=performance.now(),this.lastTimestamp=this.startTime,this.lastRenderTimestamp=this.startTime,this.accumulatedTime=0,this.firstTickDone=!1,this.performanceMonitor.reset(),this.performanceMonitor.startTracking(this.startTime),this.log("Starting main loop"),this.mainLoop(this.lastTimestamp)}async stop(){if(!this.running)return;this.log("Stopping ClientRuntime"),this.running=!1,this.rafId&&(cancelAnimationFrame(this.rafId),this.rafId=0),this.audioManager&&this.audioManager.stopAll(),this.input&&this.input.stop(),this.mode==="connected"&&this.networkSync&&this.networkSync.disconnect();let e=this.core.getUser(this.userId);e&&this.options.application.destroyUser&&this.options.application.destroyUser(this.core,e,"Runtime stopped"),this.log("ClientRuntime stopped")}getStats(){let e=this.performanceMonitor.getStats();return{mode:this.mode,running:this.running,userCount:this.core.getUsers().length,fps:e.fps,uptime:this.running?performance.now()-this.startTime:0,totalFrames:0,latency:void 0,lastFrameTiming:e.lastFrame,avgFrameTiming:e.avgFrame}}async destroy(){await this.stop(),this.log("Destroying ClientRuntime"),this.visibilityChangeHandler&&(document.removeEventListener("visibilitychange",this.visibilityChangeHandler),this.visibilityChangeHandler=void 0),this.autoplayOverlay&&(this.autoplayOverlay.destroy(),this.autoplayOverlay=null),this.audioManager&&(this.audioManager.destroy(),this.audioManager=null),this.postProcessOverlay&&(this.postProcessOverlay.destroy(),this.postProcessOverlay=null);let e=this.rendererManager.getRenderer();e&&"clearOnResizeCallback"in e&&e.clearOnResizeCallback(),this.options.application.destroy&&this.options.application.destroy(),this.rendererManager.destroy(),this.input&&this.input.destroy(),this.networkSync&&this.networkSync.destroy(),this.log("ClientRuntime destroyed")}async createLocalUser(){let e=this.options;this.log(`Creating local user: ${this.userId}`);let t=this.core.createUser(this.userId,e.username);if(this.audioManager&&t.setAudioProcessor(this.audioManager),this.log("Calling application.initUser()"),this.options.application.initUser(this.core,t,{username:e.username}),t.hasAudioConfigCommands()&&(this.log("Applying audio config commands from initUser()"),t.applyAudioConfigCommands(t.flushAudioConfigCommands())),t.hasPendingMacroOrders()){this.log("Applying macro orders from initUser()");let n=t.flushMacroOrders();t.applyMacroOrders(n)}if(t.hasPostProcessCommands()){this.log("Applying post-process commands from initUser()");let n=t.flushPostProcessCommands(),i=this.postProcessOrderCollector.convertCommands(n);this.applyPostProcessOrders(i)}t.needsSendSounds()&&(await this.loadSoundsFromRegistry(),t.clearSendSounds()),this.log("Performing initial render"),this.core.endTick(),this.render(),this.log("Initial render complete")}async loadSoundsFromRegistry(){if(!this.audioManager){this.log("Cannot load sounds: AudioManager not initialized");return}let e=this.audioManager.getSoundBank();if(!e){this.log("Cannot load sounds: SoundBank not initialized");return}let n=this.core.getSoundRegistry().getAll();if(n.length===0){this.log("No sounds to load from registry");return}this.log(`Loading ${n.length} sounds from Core registry into AudioManager...`);for(let i of n)try{i.loadType==="file"&&i.data?(await e.loadFromData(i.soundId,i.name,i.data),this.log(`\u{1F50A} Loaded sound: ${i.name} (${i.data.length} bytes)`)):i.loadType==="external"&&i.url&&(await e.loadFromUrl(i.soundId,i.name,i.url),this.log(`\u{1F50A} Loaded external sound: ${i.name} from ${i.url}`))}catch(r){console.error(`[ClientRuntime] Failed to load sound "${i.name}":`,r)}this.log(`\u2713 ${e.size} sounds loaded into AudioManager`)}mainLoop(e){if(!this.running){this.rafId&&(cancelAnimationFrame(this.rafId),this.rafId=0);return}if(this.renderMode==="on-demand"){this.renderRequested&&(this.renderRequested=!1,this.performanceMonitor.updateFPS(e),this.render()),this.rafId=requestAnimationFrame(o=>this.mainLoop(o));return}let t=e-this.lastRenderTimestamp;if(this.lastRenderTimestamp>0&&t<this.FRAME_TIME_MIN-1){this.rafId=requestAnimationFrame(o=>this.mainLoop(o));return}this.lastRenderTimestamp=e;let n=(e-this.lastTimestamp)/1e3,i=Math.min(n,.1);this.lastTimestamp=e,this.performanceMonitor.updateFPS(e);let r=this.core.getUser(this.userId);if(!r){this.rafId=requestAnimationFrame(o=>this.mainLoop(o));return}if(this.mode==="local"){if(this.tickRate===0){let d=performance.now();this.render();let u=performance.now()-d;this.performanceMonitor.recordFrameTiming(0,u),this.rafId=requestAnimationFrame(l=>this.mainLoop(l));return}if(!this.firstTickDone){this.firstTickDone=!0,this.lastTimestamp=e,this.accumulatedTime=0,this.rafId=requestAnimationFrame(d=>this.mainLoop(d));return}this.accumulatedTime+=i;let o=1/this.tickRate;this.accumulatedTime>.5&&(this.accumulatedTime=o);let a=0,c=0;for(;this.accumulatedTime>=o&&a<5;){let d=performance.now();if(this.collectAndApplyInput(r),this.options.application.update(this.core,o),this.options.application.updateUser(this.core,r,o),r.clearTextInputs(),r.hasAudioConfigCommands()&&r.applyAudioConfigCommands(r.flushAudioConfigCommands()),r.hasSoundCommands()&&r.applyAudioCommands(r.flushSoundCommands()),r.hasPostProcessCommands()){let l=r.flushPostProcessCommands(),m=this.postProcessOrderCollector.convertCommands(l);this.applyPostProcessOrders(m)}if(r.hasPendingMacroOrders()){let l=r.flushMacroOrders();r.applyMacroOrders(l)}let u=r.updateMacros();if(r.processMacroEvents(u),r.hasBridgeMessages()){let l=r.getBridgeMessages();this.dispatchLocalBridgeMessages(l)}this.core.endTickSplit(),c+=performance.now()-d,this.accumulatedTime-=o,a++}if(a>0){let d=performance.now();this.render();let u=performance.now()-d;this.performanceMonitor.recordFrameTiming(c,u)}}else{let o=r.updateMacros();r.processMacroEvents(o),this.collectAndSendInput()}this.rafId=requestAnimationFrame(o=>this.mainLoop(o))}collectAndApplyInput(e){if(!this.input)return;let t=this.rendererManager.getCanvas();if(t){let a=e.getDisplays(),c=a.length>0?a[0].size.x:this.options.width,d=a.length>0?a[0].size.y:this.options.height,l=this.rendererManager.getRenderer()?.getOffsets?.(),m={offsetX:l?.offsetX??0,offsetY:l?.offsetY??0},f=T.InputCollector.collectMousePosition(this.input,t,c,d,m);e.setMousePosition(f.x,f.y,f.over),e.updateMacroMouse(f.x,f.y,f.isLeftDown);let g=T.InputCollector.collectTouchPositions(this.input,t,c,d,10,m);this.options.debug&&g.length>0&&console.warn("\u{1F4F1} Collected touches:",g),g.forEach(h=>{e.setTouchPosition(h.id,h.x,h.y,h.over)})}let n=T.InputCollector.collectTextInputs(this.input);n.length>0&&e.setTextInputs(n);let i=e.getInputBindingRegistry(),r=i.getAllAxes(),o=i.getAllButtons();if(r.length>0||o.length>0){let a=T.InputCollector.collectAxisSources(r,this.input),c=T.InputCollector.collectButtonSourcesWithTransitions(o,this.input);r.forEach(d=>{let u=i.evaluateAxis(d.bindingId,a);e.setAxis(d.name,u)}),o.forEach(d=>{let u=new Map,l=new Map,m=new Map;for(let[R,y]of c)u.set(R,y.pressed),l.set(R,y.justPressed),m.set(R,y.justReleased);let f=i.evaluateButton(d.bindingId,u),g=i.evaluateButton(d.bindingId,l),h=i.evaluateButton(d.bindingId,m);e.setButton(d.name,f),e.setButton(`${d.name}_justPressed`,g),e.setButton(`${d.name}_justReleased`,h)})}this.input.poll?.()}collectAndSendInput(){!this.networkSync||!this.input||this.networkSync.sendInput(this.input)}render(){let e=this.core.getUser(this.userId);if(e){let t=e.getMacroRenderOrders();for(let[n,i]of t){let r=e.getLayerById(n);r&&i.length>0&&r.addTemporaryOrders(i)}}this.rendererManager.render(this.core,this.userId)}onCorePaletteChanged(e){this.log(`Palette changed, updating renderer (${e.size} colors)`);let t=[];for(let i=0;i<256;i++){let r=e.get(i);r?t.push({r:r.r,g:r.g,b:r.b,a:r.a}):t.push({r:0,g:0,b:0,a:0})}let n=this.rendererManager.getRenderer();if(!n){console.warn("[ClientRuntime] Cannot update palette: renderer is null");return}"setPalette"in n&&typeof n.setPalette=="function"?(n.setPalette(t),this.log("\u2713 Renderer palette updated successfully")):console.warn("[ClientRuntime] Renderer does not have setPalette method")}onCoreBitmapFontChanged(e){this.log(`Bitmap font ${e} changed, updating renderer`);let t=this.core.getBitmapFont(e);if(!t){console.warn(`[ClientRuntime] Font ${e} not found in registry`);return}let n=new Map,i=0;for(let o=0;o<256;o++){let a=t.getGlyph(o);a&&(n.set(o,a),i++)}this.log(`Built bitmap font map with ${i} glyphs, dimensions: ${t.getCharWidth()}x${t.getCharHeight()} (cell: ${t.getCellWidth()}x${t.getCellHeight()})`);let r=this.rendererManager.getRenderer();if(!r){console.warn("[ClientRuntime] Cannot update font: renderer is null");return}"setBitmapFont"in r&&typeof r.setBitmapFont=="function"?(r.setBitmapFont(n,t.getCharWidth(),t.getCharHeight(),t.getCellWidth(),t.getCellHeight()),this.log("\u2713 Renderer bitmap font updated successfully"),this.postProcessOverlay&&"syncWithRenderer"in this.postProcessOverlay&&"getCellWidth"in r&&"getCellHeight"in r&&"getCurrentScale"in r&&"getGridSize"in r&&(this.postProcessOverlay.syncWithRenderer(r),this.log("\u2713 PostProcessOverlay synced with renderer dimensions"))):console.warn("[ClientRuntime] Renderer does not have setBitmapFont method")}getAudioManager(){return this.audioManager}getAudioContext(){return this.audioManager?.getContext()??null}applyAmbientEffectConfig(e){let t=this.rendererManager?.getRenderer();t&&"setAmbientEffect"in t&&(e.enabled?t.setAmbientEffect({blur:e.blur,scale:e.scale}):t.setAmbientEffect(!1),this.log(`Ambient effect ${e.enabled?"enabled":"disabled"}`))}applyPostProcessOrders(e){if(!this.postProcessOverlay){this.log("PostProcessOverlay not initialized, skipping post-process orders");return}for(let t of e)switch(t.type){case b.PostProcessOrderType.SetConfig:{let n={};t.scanlines&&(n.scanlines={enabled:t.scanlines.enabled,opacity:t.scanlines.opacity,pattern:this.patternFromType(t.scanlines.pattern),color:{r:t.scanlines.colorR,g:t.scanlines.colorG,b:t.scanlines.colorB}}),t.ambientEffect&&(n.ambientEffect={enabled:t.ambientEffect.enabled,blur:t.ambientEffect.blur,scale:t.ambientEffect.scale}),this.postProcessOverlay.setConfig(Object.keys(n).length>0?n:null),n.ambientEffect&&this.applyAmbientEffectConfig(n.ambientEffect);break}case b.PostProcessOrderType.SetScanlines:{this.postProcessOverlay.setScanlines({enabled:t.enabled,opacity:t.opacity,pattern:this.patternFromType(t.pattern),color:{r:t.colorR,g:t.colorG,b:t.colorB}});break}case b.PostProcessOrderType.SetAmbientEffect:{this.applyAmbientEffectConfig({enabled:t.enabled,blur:t.blur,scale:t.scale});break}case b.PostProcessOrderType.SetScalingMode:{this.applyScalingMode(t.mode);break}case b.PostProcessOrderType.SetGrid:{this.applyGridConfig(t);break}}}applyScalingMode(e){let t=this.rendererManager.getRenderer();if(t&&"setScalingMode"in t){let n=(0,O.valueToScalingMode)(e);t.setScalingMode(n),this.log(`Scaling mode set to ${n}`)}}applyGridConfig(e){let t=this.rendererManager.getRenderer();if(t&&"setGrid"in t){let n=e.colorA/255;t.setGrid({enabled:e.enabled,color:`rgba(${e.colorR},${e.colorG},${e.colorB},${n})`,lineWidth:e.lineWidth}),this.log(`Grid ${e.enabled?"enabled":"disabled"}`)}}patternFromType(e){switch(e){case 0:return"horizontal";case 1:return"vertical";case 2:return"grid";default:return"horizontal"}}getPostProcessOverlay(){return this.postProcessOverlay}dispatchLocalBridgeMessages(e){for(let t of e){let{channel:n,data:i}=t,r=this.localBridgeHandlers.get(n);if(r)for(let o of r)try{o(i)}catch(a){console.error(`[ClientRuntime] Bridge handler error on '${n}':`,a)}for(let o of this.localBridgeWildcardHandlers)try{o(n,i)}catch(a){console.error("[ClientRuntime] Bridge wildcard handler error:",a)}this.log(`Bridge message dispatched on channel '${n}'`)}}sendBridge(e,t){if(this.mode==="connected"&&this.networkSync)this.networkSync.sendBridge(e,t);else if(this.mode==="local"){let n=this.core.getUser(this.userId);n&&this.options.application.onBridgeMessage&&(this.options.application.onBridgeMessage(this.core,n,e,t),this.log(`Bridge message sent to application on channel '${e}'`))}}onBridge(e,t){this.mode==="connected"&&this.networkSync?this.networkSync.onBridge(e,t):this.mode==="local"&&(e==="*"?this.localBridgeWildcardHandlers.add(t):(this.localBridgeHandlers.has(e)||this.localBridgeHandlers.set(e,new Set),this.localBridgeHandlers.get(e).add(t)),this.log(`Bridge handler registered for channel '${e}'`))}offBridge(e,t){this.mode==="connected"&&this.networkSync?this.networkSync.offBridge(e,t):this.mode==="local"&&(e==="*"?this.localBridgeWildcardHandlers.delete(t):this.localBridgeHandlers.get(e)?.delete(t))}removeAllBridgeHandlers(e){this.mode==="connected"&&this.networkSync?this.networkSync.removeAllBridgeHandlers(e):this.mode==="local"&&(e===void 0?(this.localBridgeHandlers.clear(),this.localBridgeWildcardHandlers.clear()):e==="*"?this.localBridgeWildcardHandlers.clear():this.localBridgeHandlers.delete(e))}log(e){this.options.debug&&console.warn(`[ClientRuntime/${this.mode}] ${e}`)}};v($,"ClientRuntime");var A=$;var H=require("@utsp/render"),G=require("@utsp/audio");
package/dist/index.d.ts CHANGED
@@ -195,6 +195,8 @@ declare class ClientRuntime {
195
195
  private audioManager;
196
196
  private postProcessOverlay;
197
197
  private postProcessOrderCollector;
198
+ private localBridgeHandlers;
199
+ private localBridgeWildcardHandlers;
198
200
  constructor(options: ClientRuntimeOptions);
199
201
  getMode(): ClientRuntimeMode;
200
202
  getRendererType(): RendererType;
@@ -288,6 +290,123 @@ declare class ClientRuntime {
288
290
  * Returns null before start() is called
289
291
  */
290
292
  getPostProcessOverlay(): PostProcessOverlay | null;
293
+ /**
294
+ * Send a bridge message to the server
295
+ *
296
+ * Bridge messages bypass the UTSP protocol and allow direct communication
297
+ * with the server application. Use this for external UI communication
298
+ * (chat, settings, custom commands, etc.).
299
+ *
300
+ * **Note:** Only available in 'connected' mode. In 'local' mode, this is a no-op.
301
+ *
302
+ * @param channel - Channel name (e.g., 'chat', 'settings', 'ready')
303
+ * @param data - Any JSON-serializable data
304
+ *
305
+ * @example
306
+ * ```typescript
307
+ * // Send a chat message
308
+ * runtime.sendBridge('chat', { message: 'Hello everyone!' });
309
+ *
310
+ /**
311
+ * Dispatch bridge messages from application to local handlers
312
+ *
313
+ * In local mode, this simulates the server→client path by calling
314
+ * registered handlers directly. Same timing as connected mode.
315
+ *
316
+ * @param messages - Array of bridge messages from user.getBridgeMessages()
317
+ * @private
318
+ */
319
+ private dispatchLocalBridgeMessages;
320
+ /**
321
+ * Send a message to the server via the bridge channel
322
+ *
323
+ * Bridge messages are sent immediately (not queued), bypassing the UTSP
324
+ * protocol for real-time external app communication.
325
+ *
326
+ * **Note:** In 'connected' mode, sends to server. In 'local' mode, routes
327
+ * directly to application.onBridgeMessage for isomorphic behavior.
328
+ *
329
+ * @param channel - Channel name (e.g., 'chat', 'ready', 'settings')
330
+ * @param data - Data to send (will be JSON serialized)
331
+ *
332
+ * @example
333
+ * ```typescript
334
+ * // Send a chat message
335
+ * runtime.sendBridge('chat', { message: 'Hello everyone!' });
336
+ *
337
+ * // Mark player as ready
338
+ * runtime.sendBridge('ready', { ready: true });
339
+ *
340
+ * // Send custom settings
341
+ * runtime.sendBridge('settings', { volume: 0.8, theme: 'dark' });
342
+ * ```
343
+ */
344
+ sendBridge(channel: string, data: unknown): void;
345
+ /**
346
+ * Register a handler for incoming bridge messages from the application
347
+ *
348
+ * Bridge messages from the application (via user.sendBridge) are synchronized
349
+ * with tick updates, arriving at the same time as render updates for consistency.
350
+ *
351
+ * **Isomorphic:** Works in both 'connected' and 'local' modes.
352
+ * - Connected: Messages come from server via network
353
+ * - Local: Messages are dispatched directly from application
354
+ *
355
+ * @param channel - Channel name to listen for (use '*' for all channels)
356
+ * @param handler - Callback function receiving the parsed data
357
+ *
358
+ * @example
359
+ * ```typescript
360
+ * // Listen for score updates
361
+ * runtime.onBridge('score', (data) => {
362
+ * updateScoreboard(data.value, data.combo);
363
+ * });
364
+ *
365
+ * // Listen for notifications
366
+ * runtime.onBridge('notification', (data) => {
367
+ * showToast(data.title, data.message);
368
+ * });
369
+ *
370
+ * // Wildcard handler for all channels
371
+ * runtime.onBridge('*', (channel, data) => {
372
+ * console.log(`Received ${channel}:`, data);
373
+ * });
374
+ * ```
375
+ */
376
+ onBridge<T = unknown>(channel: string, handler: (data: T) => void): void;
377
+ /**
378
+ * Remove a specific bridge handler
379
+ *
380
+ * @param channel - Channel name
381
+ * @param handler - The exact handler function to remove
382
+ *
383
+ * @example
384
+ * ```typescript
385
+ * const handler = (data) => console.log(data);
386
+ * runtime.onBridge('score', handler);
387
+ *
388
+ * // Later, remove the handler
389
+ * runtime.offBridge('score', handler);
390
+ * ```
391
+ */
392
+ offBridge<T = unknown>(channel: string, handler: (data: T) => void): void;
393
+ /**
394
+ * Remove all bridge handlers for a channel (or all channels if not specified)
395
+ *
396
+ * Useful for cleanup in React useEffect or component unmount.
397
+ *
398
+ * @param channel - Optional channel name. If omitted, removes all handlers.
399
+ *
400
+ * @example
401
+ * ```typescript
402
+ * // Remove all handlers for 'score' channel
403
+ * runtime.removeAllBridgeHandlers('score');
404
+ *
405
+ * // Remove ALL bridge handlers
406
+ * runtime.removeAllBridgeHandlers();
407
+ * ```
408
+ */
409
+ removeAllBridgeHandlers(channel?: string): void;
291
410
  private log;
292
411
  }
293
412
 
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- var F=Object.defineProperty;var L=(b,e,t)=>e in b?F(b,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):b[e]=t;var y=(b,e)=>F(b,"name",{value:e,configurable:!0});var s=(b,e,t)=>(L(b,typeof e!="symbol"?e+"":e,t),t);import{Core as U,PostProcessOrderType as C,PostProcessOrderCollector as D}from"@utsp/core";import{TerminalGL as B,Terminal2D as G,AutoplayOverlay as H,PostProcessOverlay as z}from"@utsp/render";import{ScalingMode as N,valueToScalingMode as W}from"@utsp/types";import{UnifiedInputRouter as q,InputCollector as R}from"@utsp/input";import{SocketIOClient as j}from"@utsp/network-client";import{AudioManager as _}from"@utsp/audio";import{ScalingMode as K}from"@utsp/types";var E=(t=>(t.TerminalGL="webgl",t.Terminal2D="terminal2d",t))(E||{});var P=class P{constructor(e=60){s(this,"frameCount",0);s(this,"fps",0);s(this,"fpsUpdateTime",0);s(this,"lastCoreTime",0);s(this,"lastRenderTime",0);s(this,"lastTotalTime",0);s(this,"coreTimeSamples",[]);s(this,"renderTimeSamples",[]);s(this,"totalTimeSamples",[]);s(this,"maxSamples");if(e<=0)throw new Error("maxSamples must be positive");this.maxSamples=e}startTracking(e){this.fpsUpdateTime=e,this.frameCount=0,this.fps=0}updateFPS(e){this.frameCount++,e-this.fpsUpdateTime>=1e3&&(this.fps=Math.round(this.frameCount*1e3/(e-this.fpsUpdateTime)),this.frameCount=0,this.fpsUpdateTime=e)}recordFrameTiming(e,t){let n=e+t;this.lastCoreTime=e,this.lastRenderTime=t,this.lastTotalTime=n,this.coreTimeSamples.push(e),this.renderTimeSamples.push(t),this.totalTimeSamples.push(n),this.coreTimeSamples.length>this.maxSamples&&(this.coreTimeSamples.shift(),this.renderTimeSamples.shift(),this.totalTimeSamples.shift())}getFPS(){return this.fps}getLastFrameTiming(){if(!(this.lastCoreTime===0&&this.lastRenderTime===0))return{coreTime:this.lastCoreTime,renderTime:this.lastRenderTime,totalTime:this.lastTotalTime}}getAverageFrameTiming(){if(this.coreTimeSamples.length!==0)return{coreTime:this.average(this.coreTimeSamples),renderTime:this.average(this.renderTimeSamples),totalTime:this.average(this.totalTimeSamples)}}getStats(){return{fps:this.fps,lastFrame:this.getLastFrameTiming(),avgFrame:this.getAverageFrameTiming(),sampleCount:this.coreTimeSamples.length,maxSamples:this.maxSamples}}reset(){this.frameCount=0,this.fps=0,this.fpsUpdateTime=0,this.lastCoreTime=0,this.lastRenderTime=0,this.lastTotalTime=0,this.coreTimeSamples=[],this.renderTimeSamples=[],this.totalTimeSamples=[]}average(e){return e.length===0?0:e.reduce((t,n)=>t+n,0)/e.length}};y(P,"PerformanceMonitor");var I=P;import{createASCII8x8FontLoad as $}from"@utsp/core";var k=class k{constructor(e,t={}){s(this,"renderer");s(this,"options");this.renderer=e,this.options={debug:t.debug??!1,useImageDataRendering:t.useImageDataRendering??!1}}async initialize(e){await this.loadDefaultFont(e),await this.waitForReady()}async waitForReady(e=100,t=50){this.log("Waiting for renderer to be ready");let n=0;return new Promise((i,r)=>{let o=y(()=>{if(n++,this.renderer.isReady())this.log(`Renderer ready after ${n*t}ms`),i();else if(n>=e){let l=`Renderer failed to be ready after ${e*t}ms`;this.log(l),r(new Error(l))}else setTimeout(o,t)},"check");o()})}async loadDefaultFont(e){this.log("Loading ASCII 8x8 font");let t=$(1);e.hasBitmapFont(t.fontId)||e.loadBitmapFontById(t.fontId,{charWidth:t.width,charHeight:t.height,cellWidth:t.cellWidth,cellHeight:t.cellHeight,glyphs:new Map(t.characters.map(n=>[n.charCode,n.bitmap]))}),"setImageDataRendering"in this.renderer&&this.options.useImageDataRendering&&(this.renderer.setImageDataRendering(!0),this.log("ImageData rendering enabled")),this.log("Font loaded and event triggered")}render(e,t){if(!this.renderer.isReady())return console.warn("[RENDERER MANAGER] Renderer not ready"),!1;let n=e.getRenderState(t);if(!n||n.displays.length===0)return console.warn("[RENDERER MANAGER] No render state or no displays"),!1;let i=n.displays[0];return this.renderer.renderDisplayData(i),!0}getRenderer(){return this.renderer}getCanvas(){return this.renderer.getCanvas()}isReady(){return this.renderer.isReady()}destroy(){this.log("Destroying renderer"),this.renderer.destroy()}log(e){this.options.debug&&console.warn(`[RendererManager] ${e}`)}};y(k,"RendererManager");var S=k;import{InputCollector as T}from"@utsp/input";var A=class A{constructor(e,t,n,i,r){s(this,"network");s(this,"core");s(this,"rendererManager");s(this,"performanceMonitor");s(this,"options");s(this,"audioManager",null);s(this,"postProcessCallback",null);s(this,"userId","");s(this,"lastReceivedTick",-1);s(this,"connected",!1);this.network=e,this.core=t,this.rendererManager=n,this.performanceMonitor=i,this.options={serverUrl:r.serverUrl,username:r.username,token:r.token??"",debug:r.debug??!1,autoReconnect:r.autoReconnect??!0,canvasWidth:r.canvasWidth,canvasHeight:r.canvasHeight}}async connect(){this.log(`Connecting to ${this.options.serverUrl}`),await this.network.connect(),this.connected=!0,this.log("Connected to server"),this.setupNetworkHandlers()}async joinGame(e){return new Promise((t,n)=>{this.network.send("join",{username:this.options.username,token:this.options.token}),this.network.on("join_response",i=>{if(i.success){this.userId=i.userId,this.log(`Joined game as ${this.userId}`);let r=this.core.createUser(this.userId,this.options.username);this.audioManager&&r.setAudioProcessor(this.audioManager),e.initUser(this.core,r,{username:this.options.username,token:this.options.token}),t(this.userId)}else n(new Error(i.error||"Failed to join game"))}),setTimeout(()=>n(new Error("Join timeout")),5e3)})}sendInput(e){let t=this.core.getUser(this.userId);if(!t)return;let n=this.rendererManager.getCanvas();if(n){let p=this.rendererManager.getRenderer()?.getOffsets?.(),v={offsetX:p?.offsetX??0,offsetY:p?.offsetY??0},f=T.collectMousePosition(e,n,this.options.canvasWidth,this.options.canvasHeight,v);t.setMousePosition(f.x,f.y,f.over),t.updateMacroMouse(f.x,f.y,f.isLeftDown),T.collectTouchPositions(e,n,this.options.canvasWidth,this.options.canvasHeight,10,v).forEach(M=>{t.setTouchPosition(M.id,M.x,M.y,M.over)})}let i=t.getInputBindingRegistry(),r=i.getAllAxes(),o=i.getAllButtons(),d=T.collectAxisSources(r,e),l=T.collectButtonSources(o,e),a=T.collectTextInputs(e);a.length>0&&t.setTextInputs(a);let c={};r.forEach(m=>{let p=i.evaluateAxis(m.bindingId,d);c[m.name]=p,t.setAxis(m.name,p)});let u={};o.forEach(m=>{let p=i.evaluateButton(m.bindingId,l);u[m.name]=p,t.setButton(m.name,p)});let h=t.getMouseDisplayInfo(),g=h?{x:h.localX,y:h.localY}:null;this.network.send("input",{timestamp:Date.now(),axes:c,buttons:u,mousePosition:g,textInputs:a})}disconnect(){this.connected&&(this.network.send("leave",{}),this.network.disconnect(),this.connected=!1,this.log("Disconnected from server"))}getUserId(){return this.userId}isConnected(){return this.connected}destroy(){this.disconnect(),this.network.destroy(),this.log("NetworkSync destroyed")}setupNetworkHandlers(){this.network.on("update",e=>{try{let t=this.convertToUint8Array(e,"update");if(!t)return;let n=this.core.applyUpdatePacketBuffer(this.userId,t);n?(this.postProcessCallback&&n.postProcessOrders&&n.postProcessOrders.length>0&&this.postProcessCallback(n.postProcessOrders),this.rendererManager.render(this.core,this.userId)):this.log("Failed to apply update packet")}catch(t){this.log(`Error applying update packet: ${t}`)}}),this.network.on("update-static",e=>{this.handleUpdatePacket(e,"update-static")}),this.network.on("update-dynamic",e=>{this.handleUpdatePacket(e,"update-dynamic")}),this.network.on("load",e=>{try{let t=this.convertToUint8Array(e,"load");if(!t)return;this.core.applyLoadPacket(t)?this.log("Load packet applied successfully"):this.log("Failed to apply load packet")}catch(t){this.log(`Error applying load packet: ${t}`)}}),this.network.on("input-bindings",e=>{this.core.applyInputBindingsLoadPacket(this.userId,e)?this.log("Input bindings configured"):this.log("Failed to apply input bindings")}),this.audioManager&&this.setupSoundHandlers(),this.network.on("disconnect",()=>{this.connected=!1,this.log("Disconnected from server")})}handleUpdatePacket(e,t){try{let n=this.convertToUint8Array(e,t);if(!n)return;let i=new DataView(n.buffer,n.byteOffset,n.byteLength),r=Number(i.getBigUint64(0,!1));if(t==="update-dynamic"&&r<this.lastReceivedTick)return;r>this.lastReceivedTick&&(this.lastReceivedTick=r);let o=this.core.applyUpdatePacketBuffer(this.userId,n);if(o){console.warn(`[CLIENT] ${t}: ${n.length}B (tick ${r}) - APPLIED`),this.postProcessCallback&&o.postProcessOrders&&o.postProcessOrders.length>0&&this.postProcessCallback(o.postProcessOrders),this.options.debug&&this.debugRenderState();let d=performance.now();this.rendererManager.render(this.core,this.userId);let l=performance.now()-d;this.performanceMonitor.recordFrameTiming(0,l),this.options.debug&&console.warn(`[CLIENT] render() completed for ${t} (${l.toFixed(2)}ms)`)}else console.warn(`[CLIENT] Failed to apply ${t} packet (tick ${r})`)}catch(n){this.log(`Error applying ${t} update packet: ${n}`),console.error(n)}}convertToUint8Array(e,t){return e instanceof Uint8Array?e:e instanceof ArrayBuffer?new Uint8Array(e):Array.isArray(e)?new Uint8Array(e):e.data&&Array.isArray(e.data)?new Uint8Array(e.data):typeof e=="object"&&e.type==="Buffer"?new Uint8Array(e.data):(this.log(`Unknown data type for ${t} packet: ${typeof e}`),null)}debugRenderState(){let e=this.core.getUser(this.userId);if(e){let n=e.getLayers();console.warn(`[CLIENT] User has ${n.length} layers`),n.forEach((i,r)=>{let o=i.getOrders(),d=i.getStatic();(o.length>0||d)&&console.warn(` Layer ${r}: ${o.length} orders, static=${d}, z=${i.getZOrder()}`)})}let t=this.rendererManager.isReady();console.warn(`[CLIENT] Renderer ready: ${t}`)}log(e){this.options.debug&&console.warn(`[NetworkSync] ${e}`)}setAudioManager(e){if(this.audioManager=e,e&&this.userId){let t=this.core.getUser(this.userId);t&&t.setAudioProcessor(e)}this.connected&&e&&this.setupSoundHandlers()}setPostProcessCallback(e){this.postProcessCallback=e}setupSoundHandlers(){this.audioManager&&(this.network.on("sound-load",async e=>{e.mode==="file"?await this.handleSoundLoad(e):e.mode==="external"&&await this.handleSoundExternalLoad(e)}),this.log("Sound handlers registered"))}async handleSoundLoad(e){if(!this.audioManager){this.log("Cannot load sounds: AudioManager not initialized");return}let t=this.audioManager.getSoundBank();if(!t){this.log("Cannot load sounds: SoundBank not initialized");return}for(let n of e.sounds)try{await t.loadFromData(n.soundId,n.name,n.data),this.log(`Loaded sound: ${n.name} (${n.data.length} bytes)`),this.sendAudioAck({type:"sound-loaded",soundId:n.soundId,name:n.name})}catch(i){console.error(`[NetworkSync] Failed to load sound "${n.name}":`,i),this.sendAudioAck({type:"sound-error",soundId:n.soundId,name:n.name,error:String(i)})}}async handleSoundExternalLoad(e){if(!this.audioManager){this.log("Cannot load external sounds: AudioManager not initialized");return}let t=this.audioManager.getSoundBank();if(!t){this.log("Cannot load external sounds: SoundBank not initialized");return}for(let n of e.sounds)try{await t.loadFromUrl(n.soundId,n.name,n.url),this.log(`Loaded external sound: ${n.name} from ${n.url}`),this.sendAudioAck({type:"sound-loaded",soundId:n.soundId,name:n.name})}catch(i){console.error(`[NetworkSync] Failed to load external sound "${n.name}":`,i),this.sendAudioAck({type:"sound-error",soundId:n.soundId,name:n.name,error:String(i)})}}sendAudioAck(e){this.network.send("audio-ack",e),this.log(`Sent audio-ack: ${e.type}`)}};y(A,"NetworkSync");var w=A;var x=class x{constructor(e){s(this,"core");s(this,"rendererManager");s(this,"rendererType");s(this,"input",null);s(this,"networkSync",null);s(this,"options");s(this,"running",!1);s(this,"startTime",0);s(this,"lastTimestamp",0);s(this,"userId","");s(this,"mode");s(this,"visibilityChangeHandler");s(this,"tickRate",30);s(this,"accumulatedTime",0);s(this,"FRAME_TIME_MIN",1e3/60);s(this,"lastRenderTimestamp",0);s(this,"rafId",0);s(this,"firstTickDone",!1);s(this,"performanceMonitor");s(this,"renderMode","continuous");s(this,"renderRequested",!1);s(this,"autoplayOverlay",null);s(this,"autoplay",!0);s(this,"audioManager",null);s(this,"postProcessOverlay",null);s(this,"postProcessOrderCollector",new D);this.mode=e.mode,e.mode==="local"?(this.options={mode:"local",application:e.application,container:e.container,debug:e.debug??!1,width:e.width??80,height:e.height??25,userId:e.userId??"local",username:e.username??"User",renderer:e.renderer??"webgl",useImageDataRendering:e.useImageDataRendering??!0,mobileInputConfig:e.mobileInputConfig,captureInput:e.captureInput??!1,inputEnabled:e.inputEnabled??!0,autoplay:e.autoplay??!0,autoplayOptions:e.autoplayOptions},this.userId=e.userId??"local",this.autoplay=e.autoplay??!0):(this.options={mode:"connected",application:e.application,container:e.container,serverUrl:e.serverUrl,username:e.username??"Player",debug:e.debug??!1,width:e.width??80,height:e.height??25,autoReconnect:e.autoReconnect??!0,token:e.token,renderer:e.renderer??"webgl",useImageDataRendering:e.useImageDataRendering??!0,mobileInputConfig:e.mobileInputConfig,captureInput:e.captureInput??!1,inputEnabled:e.inputEnabled??!0,autoplay:e.autoplay??!0,autoplayOptions:e.autoplayOptions},this.autoplay=e.autoplay??!0),this.log(`Initializing ClientRuntime (${this.mode} mode)`),this.core=new U({mode:"client",maxUsers:this.mode==="local"?1:100}),this.core.onPaletteChanged(n=>{this.onCorePaletteChanged(n)}),this.core.onBitmapFontChanged(n=>{this.onCoreBitmapFontChanged(n)}),this.visibilityChangeHandler=()=>{!document.hidden&&this.running&&(this.lastTimestamp=performance.now(),this.accumulatedTime=0,this.log("Tab visible: Reset timing"))},document.addEventListener("visibilitychange",this.visibilityChangeHandler);let t=this.createRenderer();if(this.rendererType=this.options.renderer??"webgl",this.rendererManager=new S(t,{debug:this.options.debug,useImageDataRendering:this.options.useImageDataRendering}),this.options.inputEnabled?this.input=new q({enableKeyboardMouse:!0,enableGamepad:!0,enableMobile:!0,targetElement:window,mobileTargetElement:void 0,debug:this.options.debug,keyboardConfig:{preventDefault:this.options.captureInput??!1,stopPropagation:this.options.captureInput??!1},mouseConfig:{preventDefault:this.options.captureInput??!1,stopPropagation:this.options.captureInput??!1},mobileConfig:{preventDefault:this.options.mobileInputConfig?.preventDefault??!0,passive:this.options.mobileInputConfig?.passive??!1,maxTouches:this.options.mobileInputConfig?.maxTouches??10}}):this.log("Input disabled (inputEnabled: false)"),this.performanceMonitor=new I(60),this.mode==="connected"){let n=this.options,i=new j({url:n.serverUrl,autoReconnect:n.autoReconnect,auth:{username:n.username,token:n.token},debug:this.options.debug});this.networkSync=new w(i,this.core,this.rendererManager,this.performanceMonitor,{serverUrl:n.serverUrl,username:n.username,token:n.token,debug:this.options.debug,autoReconnect:n.autoReconnect,canvasWidth:this.options.width,canvasHeight:this.options.height})}this.tickRate=e.tickRate??30,this.tickRate===0&&!e.renderMode?(this.renderMode="on-demand",this.log("tickRate is 0: automatically enabling on-demand render mode")):this.renderMode=e.renderMode??"continuous",this.log(`Configured: tickRate=${this.tickRate}, renderMode=${this.renderMode}`),this.log("ClientRuntime initialized")}getMode(){return this.mode}getRendererType(){return this.rendererType}isRunning(){return this.running}createRenderer(){if((this.options.renderer??"webgl")==="terminal2d"){this.log("Creating Terminal 2D renderer");let r=new G(this.options.container,{fixedCols:this.options.width,fixedRows:this.options.height,cellAspectRatio:1});return this.options.useImageDataRendering&&(this.log("Enabling ImageData rendering (pixel-perfect, optimized)"),r.setImageDataRendering(!0)),this.log("Canvas 2D renderer created successfully"),r}this.log("Creating TerminalGL renderer");let n=document.createElement("canvas").getContext("webgl");if(!n)throw new Error("WebGL not supported. TerminalGL requires WebGL 1.0.");if(!n.getExtension("OES_element_index_uint"))throw new Error("OES_element_index_uint extension not supported. TerminalGL requires this extension for large terminals (256x256). Supported on 97% of devices (Android 4.3+, iOS 8+, Desktop).");if(!(this.options.container instanceof HTMLDivElement))throw new Error(`TerminalGL requires container to be an HTMLDivElement. Received: ${this.options.container.tagName}`);let i=new B(this.options.container,{cols:this.options.width,rows:this.options.height,charWidth:8,charHeight:8,scalingMode:N.None});return this.log("TerminalGL created successfully"),i}setTickRate(e){if(e<0||e>1e3)throw new Error(`Invalid tick rate: ${e}. Must be between 0 and 1000.`);if(this.mode==="connected"){this.log("setTickRate() has no effect in connected mode");return}this.tickRate=e,e===0&&this.renderMode==="continuous"?(this.renderMode="on-demand",this.log(`Tick rate set to ${e} TPS (update loop disabled, automatically switched to on-demand render mode)`)):this.log(`Tick rate set to ${e} TPS ${e===0?"(update loop disabled)":""}`)}setRenderMode(e){this.renderMode=e,this.log(`Render mode set to ${e}`)}setMaxFPS(e){if(e<=0||e>240)throw new Error(`Invalid FPS: ${e}. Must be between 1 and 240.`);this.FRAME_TIME_MIN=1e3/e,this.log(`Max FPS set to ${e}`)}getTickRate(){return this.tickRate}requestRender(){this.renderMode==="on-demand"?(this.renderRequested=!0,this.log("Render requested")):this.log("requestRender() has no effect in continuous mode")}async start(){if(this.running){this.log("Already running");return}this.log("Starting ClientRuntime"),this.audioManager=new _({debug:this.options.debug}),this.autoplay?(this.log("Autoplay enabled, initializing AudioManager..."),this.audioManager.initialize()):(this.log("Autoplay disabled, showing overlay..."),await new Promise(n=>{this.autoplayOverlay=new H(this.options.container,{...this.options.autoplayOptions,onStart:()=>{this.log("User clicked start button"),this.audioManager&&(this.audioManager.initialize(),this.audioManager.playStartSound()),n()}})}),this.log("Autoplay overlay dismissed, continuing startup...")),await this.rendererManager.initialize(this.core),this.log("Renderer is ready"),this.postProcessOverlay=new z(this.options.container),this.log("PostProcessOverlay created"),this.onCorePaletteChanged(this.core.getPalette()),this.log("Initial palette sent to renderer"),this.log("Calling application.init()"),await this.options.application.init(this.core,this);let e=this.rendererManager.getCanvas();e&&this.input&&this.input.setMobileTarget(e),this.mode==="connected"&&this.networkSync?(this.log("Connecting to server"),this.audioManager&&this.networkSync.setAudioManager(this.audioManager),this.networkSync.setPostProcessCallback(n=>{this.applyPostProcessOrders(n)}),await this.networkSync.connect(),this.userId=await this.networkSync.joinGame(this.options.application)):await this.createLocalUser();let t=this.core.getUser(this.userId);if(t){let n=t.getDisplays();if(n.length>0){let r=n[0].getSize(),o=r.x,d=r.y,l=this.rendererManager.getRenderer(),a,c;if(this.rendererType==="webgl"){let h=l.getGridSize();a=h.cols,c=h.rows}else{let u=l;a=u.getCols(),c=u.getRows()}(a!==o||c!==d)&&(this.log(`Adjusting renderer from ${a}\xD7${c} to match display ${o}\xD7${d}`),l.resize(o,d))}}this.input&&this.input.start(),this.running=!0,this.startTime=performance.now(),this.lastTimestamp=this.startTime,this.lastRenderTimestamp=this.startTime,this.accumulatedTime=0,this.firstTickDone=!1,this.performanceMonitor.reset(),this.performanceMonitor.startTracking(this.startTime),this.log("Starting main loop"),this.mainLoop(this.lastTimestamp)}async stop(){if(!this.running)return;this.log("Stopping ClientRuntime"),this.running=!1,this.rafId&&(cancelAnimationFrame(this.rafId),this.rafId=0),this.audioManager&&this.audioManager.stopAll(),this.input&&this.input.stop(),this.mode==="connected"&&this.networkSync&&this.networkSync.disconnect();let e=this.core.getUser(this.userId);e&&this.options.application.destroyUser&&this.options.application.destroyUser(this.core,e,"Runtime stopped"),this.log("ClientRuntime stopped")}getStats(){let e=this.performanceMonitor.getStats();return{mode:this.mode,running:this.running,userCount:this.core.getUsers().length,fps:e.fps,uptime:this.running?performance.now()-this.startTime:0,totalFrames:0,latency:void 0,lastFrameTiming:e.lastFrame,avgFrameTiming:e.avgFrame}}async destroy(){await this.stop(),this.log("Destroying ClientRuntime"),this.visibilityChangeHandler&&(document.removeEventListener("visibilitychange",this.visibilityChangeHandler),this.visibilityChangeHandler=void 0),this.autoplayOverlay&&(this.autoplayOverlay.destroy(),this.autoplayOverlay=null),this.audioManager&&(this.audioManager.destroy(),this.audioManager=null),this.postProcessOverlay&&(this.postProcessOverlay.destroy(),this.postProcessOverlay=null);let e=this.rendererManager.getRenderer();e&&"clearOnResizeCallback"in e&&e.clearOnResizeCallback(),this.options.application.destroy&&this.options.application.destroy(),this.rendererManager.destroy(),this.input&&this.input.destroy(),this.networkSync&&this.networkSync.destroy(),this.log("ClientRuntime destroyed")}async createLocalUser(){let e=this.options;this.log(`Creating local user: ${this.userId}`);let t=this.core.createUser(this.userId,e.username);if(this.audioManager&&t.setAudioProcessor(this.audioManager),this.log("Calling application.initUser()"),this.options.application.initUser(this.core,t,{username:e.username}),t.hasAudioConfigCommands()&&(this.log("Applying audio config commands from initUser()"),t.applyAudioConfigCommands(t.flushAudioConfigCommands())),t.hasPendingMacroOrders()){this.log("Applying macro orders from initUser()");let n=t.flushMacroOrders();t.applyMacroOrders(n)}if(t.hasPostProcessCommands()){this.log("Applying post-process commands from initUser()");let n=t.flushPostProcessCommands(),i=this.postProcessOrderCollector.convertCommands(n);this.applyPostProcessOrders(i)}t.needsSendSounds()&&(await this.loadSoundsFromRegistry(),t.clearSendSounds()),this.log("Performing initial render"),this.core.endTick(),this.render(),this.log("Initial render complete")}async loadSoundsFromRegistry(){if(!this.audioManager){this.log("Cannot load sounds: AudioManager not initialized");return}let e=this.audioManager.getSoundBank();if(!e){this.log("Cannot load sounds: SoundBank not initialized");return}let n=this.core.getSoundRegistry().getAll();if(n.length===0){this.log("No sounds to load from registry");return}this.log(`Loading ${n.length} sounds from Core registry into AudioManager...`);for(let i of n)try{i.loadType==="file"&&i.data?(await e.loadFromData(i.soundId,i.name,i.data),this.log(`\u{1F50A} Loaded sound: ${i.name} (${i.data.length} bytes)`)):i.loadType==="external"&&i.url&&(await e.loadFromUrl(i.soundId,i.name,i.url),this.log(`\u{1F50A} Loaded external sound: ${i.name} from ${i.url}`))}catch(r){console.error(`[ClientRuntime] Failed to load sound "${i.name}":`,r)}this.log(`\u2713 ${e.size} sounds loaded into AudioManager`)}mainLoop(e){if(!this.running){this.rafId&&(cancelAnimationFrame(this.rafId),this.rafId=0);return}if(this.renderMode==="on-demand"){this.renderRequested&&(this.renderRequested=!1,this.performanceMonitor.updateFPS(e),this.render()),this.rafId=requestAnimationFrame(o=>this.mainLoop(o));return}let t=e-this.lastRenderTimestamp;if(this.lastRenderTimestamp>0&&t<this.FRAME_TIME_MIN-1){this.rafId=requestAnimationFrame(o=>this.mainLoop(o));return}this.lastRenderTimestamp=e;let n=(e-this.lastTimestamp)/1e3,i=Math.min(n,.1);this.lastTimestamp=e,this.performanceMonitor.updateFPS(e);let r=this.core.getUser(this.userId);if(!r){this.rafId=requestAnimationFrame(o=>this.mainLoop(o));return}if(this.mode==="local"){if(this.tickRate===0){let a=performance.now();this.render();let c=performance.now()-a;this.performanceMonitor.recordFrameTiming(0,c),this.rafId=requestAnimationFrame(u=>this.mainLoop(u));return}if(!this.firstTickDone){this.firstTickDone=!0,this.lastTimestamp=e,this.accumulatedTime=0,this.rafId=requestAnimationFrame(a=>this.mainLoop(a));return}this.accumulatedTime+=i;let o=1/this.tickRate;this.accumulatedTime>.5&&(this.accumulatedTime=o);let d=0,l=0;for(;this.accumulatedTime>=o&&d<5;){let a=performance.now();if(this.collectAndApplyInput(r),this.options.application.update(this.core,o),this.options.application.updateUser(this.core,r,o),r.clearTextInputs(),r.hasAudioConfigCommands()&&r.applyAudioConfigCommands(r.flushAudioConfigCommands()),r.hasSoundCommands()&&r.applyAudioCommands(r.flushSoundCommands()),r.hasPostProcessCommands()){let u=r.flushPostProcessCommands(),h=this.postProcessOrderCollector.convertCommands(u);this.applyPostProcessOrders(h)}if(r.hasPendingMacroOrders()){let u=r.flushMacroOrders();r.applyMacroOrders(u)}let c=r.updateMacros();r.processMacroEvents(c),this.core.endTickSplit(),l+=performance.now()-a,this.accumulatedTime-=o,d++}if(d>0){let a=performance.now();this.render();let c=performance.now()-a;this.performanceMonitor.recordFrameTiming(l,c)}}else{let o=r.updateMacros();r.processMacroEvents(o),this.collectAndSendInput()}this.rafId=requestAnimationFrame(o=>this.mainLoop(o))}collectAndApplyInput(e){if(!this.input)return;let t=this.rendererManager.getCanvas();if(t){let d=e.getDisplays(),l=d.length>0?d[0].size.x:this.options.width,a=d.length>0?d[0].size.y:this.options.height,u=this.rendererManager.getRenderer()?.getOffsets?.(),h={offsetX:u?.offsetX??0,offsetY:u?.offsetY??0},g=R.collectMousePosition(this.input,t,l,a,h);e.setMousePosition(g.x,g.y,g.over),e.updateMacroMouse(g.x,g.y,g.isLeftDown);let m=R.collectTouchPositions(this.input,t,l,a,10,h);this.options.debug&&m.length>0&&console.warn("\u{1F4F1} Collected touches:",m),m.forEach(p=>{e.setTouchPosition(p.id,p.x,p.y,p.over)})}let n=R.collectTextInputs(this.input);n.length>0&&e.setTextInputs(n);let i=e.getInputBindingRegistry(),r=i.getAllAxes(),o=i.getAllButtons();if(r.length>0||o.length>0){let d=R.collectAxisSources(r,this.input),l=R.collectButtonSourcesWithTransitions(o,this.input);r.forEach(a=>{let c=i.evaluateAxis(a.bindingId,d);e.setAxis(a.name,c)}),o.forEach(a=>{let c=new Map,u=new Map,h=new Map;for(let[v,f]of l)c.set(v,f.pressed),u.set(v,f.justPressed),h.set(v,f.justReleased);let g=i.evaluateButton(a.bindingId,c),m=i.evaluateButton(a.bindingId,u),p=i.evaluateButton(a.bindingId,h);e.setButton(a.name,g),e.setButton(`${a.name}_justPressed`,m),e.setButton(`${a.name}_justReleased`,p)})}this.input.poll?.()}collectAndSendInput(){!this.networkSync||!this.input||this.networkSync.sendInput(this.input)}render(){let e=this.core.getUser(this.userId);if(e){let t=e.getMacroRenderOrders();for(let[n,i]of t){let r=e.getLayerById(n);r&&i.length>0&&r.addTemporaryOrders(i)}}this.rendererManager.render(this.core,this.userId)}onCorePaletteChanged(e){this.log(`Palette changed, updating renderer (${e.size} colors)`);let t=[];for(let i=0;i<256;i++){let r=e.get(i);r?t.push({r:r.r,g:r.g,b:r.b,a:r.a}):t.push({r:0,g:0,b:0,a:0})}let n=this.rendererManager.getRenderer();if(!n){console.warn("[ClientRuntime] Cannot update palette: renderer is null");return}"setPalette"in n&&typeof n.setPalette=="function"?(n.setPalette(t),this.log("\u2713 Renderer palette updated successfully")):console.warn("[ClientRuntime] Renderer does not have setPalette method")}onCoreBitmapFontChanged(e){this.log(`Bitmap font ${e} changed, updating renderer`);let t=this.core.getBitmapFont(e);if(!t){console.warn(`[ClientRuntime] Font ${e} not found in registry`);return}let n=new Map,i=0;for(let o=0;o<256;o++){let d=t.getGlyph(o);d&&(n.set(o,d),i++)}this.log(`Built bitmap font map with ${i} glyphs, dimensions: ${t.getCharWidth()}x${t.getCharHeight()} (cell: ${t.getCellWidth()}x${t.getCellHeight()})`);let r=this.rendererManager.getRenderer();if(!r){console.warn("[ClientRuntime] Cannot update font: renderer is null");return}"setBitmapFont"in r&&typeof r.setBitmapFont=="function"?(r.setBitmapFont(n,t.getCharWidth(),t.getCharHeight(),t.getCellWidth(),t.getCellHeight()),this.log("\u2713 Renderer bitmap font updated successfully"),this.postProcessOverlay&&"syncWithRenderer"in this.postProcessOverlay&&"getCellWidth"in r&&"getCellHeight"in r&&"getCurrentScale"in r&&"getGridSize"in r&&(this.postProcessOverlay.syncWithRenderer(r),this.log("\u2713 PostProcessOverlay synced with renderer dimensions"))):console.warn("[ClientRuntime] Renderer does not have setBitmapFont method")}getAudioManager(){return this.audioManager}getAudioContext(){return this.audioManager?.getContext()??null}applyAmbientEffectConfig(e){let t=this.rendererManager?.getRenderer();t&&"setAmbientEffect"in t&&(e.enabled?t.setAmbientEffect({blur:e.blur,scale:e.scale}):t.setAmbientEffect(!1),this.log(`Ambient effect ${e.enabled?"enabled":"disabled"}`))}applyPostProcessOrders(e){if(!this.postProcessOverlay){this.log("PostProcessOverlay not initialized, skipping post-process orders");return}for(let t of e)switch(t.type){case C.SetConfig:{let n={};t.scanlines&&(n.scanlines={enabled:t.scanlines.enabled,opacity:t.scanlines.opacity,pattern:this.patternFromType(t.scanlines.pattern),color:{r:t.scanlines.colorR,g:t.scanlines.colorG,b:t.scanlines.colorB}}),t.ambientEffect&&(n.ambientEffect={enabled:t.ambientEffect.enabled,blur:t.ambientEffect.blur,scale:t.ambientEffect.scale}),this.postProcessOverlay.setConfig(Object.keys(n).length>0?n:null),n.ambientEffect&&this.applyAmbientEffectConfig(n.ambientEffect);break}case C.SetScanlines:{this.postProcessOverlay.setScanlines({enabled:t.enabled,opacity:t.opacity,pattern:this.patternFromType(t.pattern),color:{r:t.colorR,g:t.colorG,b:t.colorB}});break}case C.SetAmbientEffect:{this.applyAmbientEffectConfig({enabled:t.enabled,blur:t.blur,scale:t.scale});break}case C.SetScalingMode:{this.applyScalingMode(t.mode);break}case C.SetGrid:{this.applyGridConfig(t);break}}}applyScalingMode(e){let t=this.rendererManager.getRenderer();if(t&&"setScalingMode"in t){let n=W(e);t.setScalingMode(n),this.log(`Scaling mode set to ${n}`)}}applyGridConfig(e){let t=this.rendererManager.getRenderer();if(t&&"setGrid"in t){let n=e.colorA/255;t.setGrid({enabled:e.enabled,color:`rgba(${e.colorR},${e.colorG},${e.colorB},${n})`,lineWidth:e.lineWidth}),this.log(`Grid ${e.enabled?"enabled":"disabled"}`)}}patternFromType(e){switch(e){case 0:return"horizontal";case 1:return"vertical";case 2:return"grid";default:return"horizontal"}}getPostProcessOverlay(){return this.postProcessOverlay}log(e){this.options.debug&&console.warn(`[ClientRuntime/${this.mode}] ${e}`)}};y(x,"ClientRuntime");var O=x;import{ScalingMode as Re}from"@utsp/render";import{AudioManager as Ie}from"@utsp/audio";export{Ie as AudioManager,O as ClientRuntime,E as RendererType,Re as ScalingMode};
1
+ var B=Object.defineProperty;var E=(b,e,t)=>e in b?B(b,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):b[e]=t;var y=(b,e)=>B(b,"name",{value:e,configurable:!0});var s=(b,e,t)=>(E(b,typeof e!="symbol"?e+"":e,t),t);import{Core as L,PostProcessOrderType as C,PostProcessOrderCollector as U}from"@utsp/core";import{TerminalGL as D,Terminal2D as H,AutoplayOverlay as G,PostProcessOverlay as z}from"@utsp/render";import{ScalingMode as W,valueToScalingMode as N}from"@utsp/types";import{UnifiedInputRouter as q,InputCollector as R}from"@utsp/input";import{SocketIOClient as j}from"@utsp/network-client";import{AudioManager as _}from"@utsp/audio";import{ScalingMode as K}from"@utsp/types";var F=(t=>(t.TerminalGL="webgl",t.Terminal2D="terminal2d",t))(F||{});var I=class I{constructor(e=60){s(this,"frameCount",0);s(this,"fps",0);s(this,"fpsUpdateTime",0);s(this,"lastCoreTime",0);s(this,"lastRenderTime",0);s(this,"lastTotalTime",0);s(this,"coreTimeSamples",[]);s(this,"renderTimeSamples",[]);s(this,"totalTimeSamples",[]);s(this,"maxSamples");if(e<=0)throw new Error("maxSamples must be positive");this.maxSamples=e}startTracking(e){this.fpsUpdateTime=e,this.frameCount=0,this.fps=0}updateFPS(e){this.frameCount++,e-this.fpsUpdateTime>=1e3&&(this.fps=Math.round(this.frameCount*1e3/(e-this.fpsUpdateTime)),this.frameCount=0,this.fpsUpdateTime=e)}recordFrameTiming(e,t){let n=e+t;this.lastCoreTime=e,this.lastRenderTime=t,this.lastTotalTime=n,this.coreTimeSamples.push(e),this.renderTimeSamples.push(t),this.totalTimeSamples.push(n),this.coreTimeSamples.length>this.maxSamples&&(this.coreTimeSamples.shift(),this.renderTimeSamples.shift(),this.totalTimeSamples.shift())}getFPS(){return this.fps}getLastFrameTiming(){if(!(this.lastCoreTime===0&&this.lastRenderTime===0))return{coreTime:this.lastCoreTime,renderTime:this.lastRenderTime,totalTime:this.lastTotalTime}}getAverageFrameTiming(){if(this.coreTimeSamples.length!==0)return{coreTime:this.average(this.coreTimeSamples),renderTime:this.average(this.renderTimeSamples),totalTime:this.average(this.totalTimeSamples)}}getStats(){return{fps:this.fps,lastFrame:this.getLastFrameTiming(),avgFrame:this.getAverageFrameTiming(),sampleCount:this.coreTimeSamples.length,maxSamples:this.maxSamples}}reset(){this.frameCount=0,this.fps=0,this.fpsUpdateTime=0,this.lastCoreTime=0,this.lastRenderTime=0,this.lastTotalTime=0,this.coreTimeSamples=[],this.renderTimeSamples=[],this.totalTimeSamples=[]}average(e){return e.length===0?0:e.reduce((t,n)=>t+n,0)/e.length}};y(I,"PerformanceMonitor");var M=I;import{createASCII8x8FontLoad as $}from"@utsp/core";var P=class P{constructor(e,t={}){s(this,"renderer");s(this,"options");this.renderer=e,this.options={debug:t.debug??!1,useImageDataRendering:t.useImageDataRendering??!1}}async initialize(e){await this.loadDefaultFont(e),await this.waitForReady()}async waitForReady(e=100,t=50){this.log("Waiting for renderer to be ready");let n=0;return new Promise((i,r)=>{let o=y(()=>{if(n++,this.renderer.isReady())this.log(`Renderer ready after ${n*t}ms`),i();else if(n>=e){let c=`Renderer failed to be ready after ${e*t}ms`;this.log(c),r(new Error(c))}else setTimeout(o,t)},"check");o()})}async loadDefaultFont(e){this.log("Loading ASCII 8x8 font");let t=$(1);e.hasBitmapFont(t.fontId)||e.loadBitmapFontById(t.fontId,{charWidth:t.width,charHeight:t.height,cellWidth:t.cellWidth,cellHeight:t.cellHeight,glyphs:new Map(t.characters.map(n=>[n.charCode,n.bitmap]))}),"setImageDataRendering"in this.renderer&&this.options.useImageDataRendering&&(this.renderer.setImageDataRendering(!0),this.log("ImageData rendering enabled")),this.log("Font loaded and event triggered")}render(e,t){if(!this.renderer.isReady())return console.warn("[RENDERER MANAGER] Renderer not ready"),!1;let n=e.getRenderState(t);if(!n||n.displays.length===0)return console.warn("[RENDERER MANAGER] No render state or no displays"),!1;let i=n.displays[0];return this.renderer.renderDisplayData(i),!0}getRenderer(){return this.renderer}getCanvas(){return this.renderer.getCanvas()}isReady(){return this.renderer.isReady()}destroy(){this.log("Destroying renderer"),this.renderer.destroy()}log(e){this.options.debug&&console.warn(`[RendererManager] ${e}`)}};y(P,"RendererManager");var k=P;import{InputCollector as T}from"@utsp/input";var A=class A{constructor(e,t,n,i,r){s(this,"network");s(this,"core");s(this,"rendererManager");s(this,"performanceMonitor");s(this,"options");s(this,"audioManager",null);s(this,"postProcessCallback",null);s(this,"userId","");s(this,"lastReceivedTick",-1);s(this,"connected",!1);this.network=e,this.core=t,this.rendererManager=n,this.performanceMonitor=i,this.options={serverUrl:r.serverUrl,username:r.username,token:r.token??"",debug:r.debug??!1,autoReconnect:r.autoReconnect??!0,canvasWidth:r.canvasWidth,canvasHeight:r.canvasHeight}}async connect(){this.log(`Connecting to ${this.options.serverUrl}`),await this.network.connect(),this.connected=!0,this.log("Connected to server"),this.setupNetworkHandlers()}async joinGame(e){return new Promise((t,n)=>{this.network.send("join",{username:this.options.username,token:this.options.token}),this.network.on("join_response",i=>{if(i.success){this.userId=i.userId,this.log(`Joined game as ${this.userId}`);let r=this.core.createUser(this.userId,this.options.username);this.audioManager&&r.setAudioProcessor(this.audioManager),e.initUser(this.core,r,{username:this.options.username,token:this.options.token}),t(this.userId)}else n(new Error(i.error||"Failed to join game"))}),setTimeout(()=>n(new Error("Join timeout")),5e3)})}sendInput(e){let t=this.core.getUser(this.userId);if(!t)return;let n=this.rendererManager.getCanvas();if(n){let p=this.rendererManager.getRenderer()?.getOffsets?.(),v={offsetX:p?.offsetX??0,offsetY:p?.offsetY??0},f=T.collectMousePosition(e,n,this.options.canvasWidth,this.options.canvasHeight,v);t.setMousePosition(f.x,f.y,f.over),t.updateMacroMouse(f.x,f.y,f.isLeftDown),T.collectTouchPositions(e,n,this.options.canvasWidth,this.options.canvasHeight,10,v).forEach(w=>{t.setTouchPosition(w.id,w.x,w.y,w.over)})}let i=t.getInputBindingRegistry(),r=i.getAllAxes(),o=i.getAllButtons(),a=T.collectAxisSources(r,e),c=T.collectButtonSources(o,e),d=T.collectTextInputs(e);d.length>0&&t.setTextInputs(d);let u={};r.forEach(m=>{let p=i.evaluateAxis(m.bindingId,a);u[m.name]=p,t.setAxis(m.name,p)});let l={};o.forEach(m=>{let p=i.evaluateButton(m.bindingId,c);l[m.name]=p,t.setButton(m.name,p)});let h=t.getMouseDisplayInfo(),g=h?{x:h.localX,y:h.localY}:null;this.network.send("input",{timestamp:Date.now(),axes:u,buttons:l,mousePosition:g,textInputs:d})}disconnect(){this.connected&&(this.network.send("leave",{}),this.network.disconnect(),this.connected=!1,this.log("Disconnected from server"))}getUserId(){return this.userId}isConnected(){return this.connected}destroy(){this.disconnect(),this.network.destroy(),this.log("NetworkSync destroyed")}setupNetworkHandlers(){this.network.on("update",e=>{try{let t=this.convertToUint8Array(e,"update");if(!t)return;let n=this.core.applyUpdatePacketBuffer(this.userId,t);n?(this.postProcessCallback&&n.postProcessOrders&&n.postProcessOrders.length>0&&this.postProcessCallback(n.postProcessOrders),this.rendererManager.render(this.core,this.userId)):this.log("Failed to apply update packet")}catch(t){this.log(`Error applying update packet: ${t}`)}}),this.network.on("update-static",e=>{this.handleUpdatePacket(e,"update-static")}),this.network.on("update-dynamic",e=>{this.handleUpdatePacket(e,"update-dynamic")}),this.network.on("load",e=>{try{let t=this.convertToUint8Array(e,"load");if(!t)return;this.core.applyLoadPacket(t)?this.log("Load packet applied successfully"):this.log("Failed to apply load packet")}catch(t){this.log(`Error applying load packet: ${t}`)}}),this.network.on("input-bindings",e=>{this.core.applyInputBindingsLoadPacket(this.userId,e)?this.log("Input bindings configured"):this.log("Failed to apply input bindings")}),this.audioManager&&this.setupSoundHandlers(),this.network.on("disconnect",()=>{this.connected=!1,this.log("Disconnected from server")})}handleUpdatePacket(e,t){try{let n=this.convertToUint8Array(e,t);if(!n)return;let i=new DataView(n.buffer,n.byteOffset,n.byteLength),r=Number(i.getBigUint64(0,!1));if(t==="update-dynamic"&&r<this.lastReceivedTick)return;r>this.lastReceivedTick&&(this.lastReceivedTick=r);let o=this.core.applyUpdatePacketBuffer(this.userId,n);if(o){console.warn(`[CLIENT] ${t}: ${n.length}B (tick ${r}) - APPLIED`),this.postProcessCallback&&o.postProcessOrders&&o.postProcessOrders.length>0&&this.postProcessCallback(o.postProcessOrders),this.options.debug&&this.debugRenderState();let a=performance.now();this.rendererManager.render(this.core,this.userId);let c=performance.now()-a;this.performanceMonitor.recordFrameTiming(0,c),this.options.debug&&console.warn(`[CLIENT] render() completed for ${t} (${c.toFixed(2)}ms)`)}else console.warn(`[CLIENT] Failed to apply ${t} packet (tick ${r})`)}catch(n){this.log(`Error applying ${t} update packet: ${n}`),console.error(n)}}convertToUint8Array(e,t){return e instanceof Uint8Array?e:e instanceof ArrayBuffer?new Uint8Array(e):Array.isArray(e)?new Uint8Array(e):e.data&&Array.isArray(e.data)?new Uint8Array(e.data):typeof e=="object"&&e.type==="Buffer"?new Uint8Array(e.data):(this.log(`Unknown data type for ${t} packet: ${typeof e}`),null)}debugRenderState(){let e=this.core.getUser(this.userId);if(e){let n=e.getLayers();console.warn(`[CLIENT] User has ${n.length} layers`),n.forEach((i,r)=>{let o=i.getOrders(),a=i.getStatic();(o.length>0||a)&&console.warn(` Layer ${r}: ${o.length} orders, static=${a}, z=${i.getZOrder()}`)})}let t=this.rendererManager.isReady();console.warn(`[CLIENT] Renderer ready: ${t}`)}log(e){this.options.debug&&console.warn(`[NetworkSync] ${e}`)}setAudioManager(e){if(this.audioManager=e,e&&this.userId){let t=this.core.getUser(this.userId);t&&t.setAudioProcessor(e)}this.connected&&e&&this.setupSoundHandlers()}setPostProcessCallback(e){this.postProcessCallback=e}setupSoundHandlers(){this.audioManager&&(this.network.on("sound-load",async e=>{e.mode==="file"?await this.handleSoundLoad(e):e.mode==="external"&&await this.handleSoundExternalLoad(e)}),this.log("Sound handlers registered"))}async handleSoundLoad(e){if(!this.audioManager){this.log("Cannot load sounds: AudioManager not initialized");return}let t=this.audioManager.getSoundBank();if(!t){this.log("Cannot load sounds: SoundBank not initialized");return}for(let n of e.sounds)try{await t.loadFromData(n.soundId,n.name,n.data),this.log(`Loaded sound: ${n.name} (${n.data.length} bytes)`),this.sendAudioAck({type:"sound-loaded",soundId:n.soundId,name:n.name})}catch(i){console.error(`[NetworkSync] Failed to load sound "${n.name}":`,i),this.sendAudioAck({type:"sound-error",soundId:n.soundId,name:n.name,error:String(i)})}}async handleSoundExternalLoad(e){if(!this.audioManager){this.log("Cannot load external sounds: AudioManager not initialized");return}let t=this.audioManager.getSoundBank();if(!t){this.log("Cannot load external sounds: SoundBank not initialized");return}for(let n of e.sounds)try{await t.loadFromUrl(n.soundId,n.name,n.url),this.log(`Loaded external sound: ${n.name} from ${n.url}`),this.sendAudioAck({type:"sound-loaded",soundId:n.soundId,name:n.name})}catch(i){console.error(`[NetworkSync] Failed to load external sound "${n.name}":`,i),this.sendAudioAck({type:"sound-error",soundId:n.soundId,name:n.name,error:String(i)})}}sendAudioAck(e){this.network.send("audio-ack",e),this.log(`Sent audio-ack: ${e.type}`)}sendBridge(e,t){this.network.sendBridge(e,t),this.log(`Bridge sent on channel '${e}'`)}onBridge(e,t){this.network.onBridge(e,t),this.log(`Bridge handler registered for channel '${e}'`)}offBridge(e,t){this.network.offBridge(e,t),this.log(`Bridge handler removed for channel '${e}'`)}removeAllBridgeHandlers(e){this.network.removeAllBridgeHandlers(e),this.log(e?`All bridge handlers removed for '${e}'`:"All bridge handlers removed")}};y(A,"NetworkSync");var S=A;var x=class x{constructor(e){s(this,"core");s(this,"rendererManager");s(this,"rendererType");s(this,"input",null);s(this,"networkSync",null);s(this,"options");s(this,"running",!1);s(this,"startTime",0);s(this,"lastTimestamp",0);s(this,"userId","");s(this,"mode");s(this,"visibilityChangeHandler");s(this,"tickRate",30);s(this,"accumulatedTime",0);s(this,"FRAME_TIME_MIN",1e3/60);s(this,"lastRenderTimestamp",0);s(this,"rafId",0);s(this,"firstTickDone",!1);s(this,"performanceMonitor");s(this,"renderMode","continuous");s(this,"renderRequested",!1);s(this,"autoplayOverlay",null);s(this,"autoplay",!0);s(this,"audioManager",null);s(this,"postProcessOverlay",null);s(this,"postProcessOrderCollector",new U);s(this,"localBridgeHandlers",new Map);s(this,"localBridgeWildcardHandlers",new Set);this.mode=e.mode,e.mode==="local"?(this.options={mode:"local",application:e.application,container:e.container,debug:e.debug??!1,width:e.width??80,height:e.height??25,userId:e.userId??"local",username:e.username??"User",renderer:e.renderer??"webgl",useImageDataRendering:e.useImageDataRendering??!0,mobileInputConfig:e.mobileInputConfig,captureInput:e.captureInput??!1,inputEnabled:e.inputEnabled??!0,autoplay:e.autoplay??!0,autoplayOptions:e.autoplayOptions},this.userId=e.userId??"local",this.autoplay=e.autoplay??!0):(this.options={mode:"connected",application:e.application,container:e.container,serverUrl:e.serverUrl,username:e.username??"Player",debug:e.debug??!1,width:e.width??80,height:e.height??25,autoReconnect:e.autoReconnect??!0,token:e.token,renderer:e.renderer??"webgl",useImageDataRendering:e.useImageDataRendering??!0,mobileInputConfig:e.mobileInputConfig,captureInput:e.captureInput??!1,inputEnabled:e.inputEnabled??!0,autoplay:e.autoplay??!0,autoplayOptions:e.autoplayOptions},this.autoplay=e.autoplay??!0),this.log(`Initializing ClientRuntime (${this.mode} mode)`),this.core=new L({mode:"client",maxUsers:this.mode==="local"?1:100}),this.core.onPaletteChanged(n=>{this.onCorePaletteChanged(n)}),this.core.onBitmapFontChanged(n=>{this.onCoreBitmapFontChanged(n)}),this.visibilityChangeHandler=()=>{!document.hidden&&this.running&&(this.lastTimestamp=performance.now(),this.accumulatedTime=0,this.log("Tab visible: Reset timing"))},document.addEventListener("visibilitychange",this.visibilityChangeHandler);let t=this.createRenderer();if(this.rendererType=this.options.renderer??"webgl",this.rendererManager=new k(t,{debug:this.options.debug,useImageDataRendering:this.options.useImageDataRendering}),this.options.inputEnabled?this.input=new q({enableKeyboardMouse:!0,enableGamepad:!0,enableMobile:!0,targetElement:window,mobileTargetElement:void 0,debug:this.options.debug,keyboardConfig:{preventDefault:this.options.captureInput??!1,stopPropagation:this.options.captureInput??!1},mouseConfig:{preventDefault:this.options.captureInput??!1,stopPropagation:this.options.captureInput??!1},mobileConfig:{preventDefault:this.options.mobileInputConfig?.preventDefault??!0,passive:this.options.mobileInputConfig?.passive??!1,maxTouches:this.options.mobileInputConfig?.maxTouches??10}}):this.log("Input disabled (inputEnabled: false)"),this.performanceMonitor=new M(60),this.mode==="connected"){let n=this.options,i=new j({url:n.serverUrl,autoReconnect:n.autoReconnect,auth:{username:n.username,token:n.token},debug:this.options.debug});this.networkSync=new S(i,this.core,this.rendererManager,this.performanceMonitor,{serverUrl:n.serverUrl,username:n.username,token:n.token,debug:this.options.debug,autoReconnect:n.autoReconnect,canvasWidth:this.options.width,canvasHeight:this.options.height})}this.tickRate=e.tickRate??30,this.tickRate===0&&!e.renderMode?(this.renderMode="on-demand",this.log("tickRate is 0: automatically enabling on-demand render mode")):this.renderMode=e.renderMode??"continuous",this.log(`Configured: tickRate=${this.tickRate}, renderMode=${this.renderMode}`),this.log("ClientRuntime initialized")}getMode(){return this.mode}getRendererType(){return this.rendererType}isRunning(){return this.running}createRenderer(){if((this.options.renderer??"webgl")==="terminal2d"){this.log("Creating Terminal 2D renderer");let r=new H(this.options.container,{fixedCols:this.options.width,fixedRows:this.options.height,cellAspectRatio:1});return this.options.useImageDataRendering&&(this.log("Enabling ImageData rendering (pixel-perfect, optimized)"),r.setImageDataRendering(!0)),this.log("Canvas 2D renderer created successfully"),r}this.log("Creating TerminalGL renderer");let n=document.createElement("canvas").getContext("webgl");if(!n)throw new Error("WebGL not supported. TerminalGL requires WebGL 1.0.");if(!n.getExtension("OES_element_index_uint"))throw new Error("OES_element_index_uint extension not supported. TerminalGL requires this extension for large terminals (256x256). Supported on 97% of devices (Android 4.3+, iOS 8+, Desktop).");if(!(this.options.container instanceof HTMLDivElement))throw new Error(`TerminalGL requires container to be an HTMLDivElement. Received: ${this.options.container.tagName}`);let i=new D(this.options.container,{cols:this.options.width,rows:this.options.height,charWidth:8,charHeight:8,scalingMode:W.None});return this.log("TerminalGL created successfully"),i}setTickRate(e){if(e<0||e>1e3)throw new Error(`Invalid tick rate: ${e}. Must be between 0 and 1000.`);if(this.mode==="connected"){this.log("setTickRate() has no effect in connected mode");return}this.tickRate=e,e===0&&this.renderMode==="continuous"?(this.renderMode="on-demand",this.log(`Tick rate set to ${e} TPS (update loop disabled, automatically switched to on-demand render mode)`)):this.log(`Tick rate set to ${e} TPS ${e===0?"(update loop disabled)":""}`)}setRenderMode(e){this.renderMode=e,this.log(`Render mode set to ${e}`)}setMaxFPS(e){if(e<=0||e>240)throw new Error(`Invalid FPS: ${e}. Must be between 1 and 240.`);this.FRAME_TIME_MIN=1e3/e,this.log(`Max FPS set to ${e}`)}getTickRate(){return this.tickRate}requestRender(){this.renderMode==="on-demand"?(this.renderRequested=!0,this.log("Render requested")):this.log("requestRender() has no effect in continuous mode")}async start(){if(this.running){this.log("Already running");return}this.log("Starting ClientRuntime"),this.audioManager=new _({debug:this.options.debug}),this.autoplay?(this.log("Autoplay enabled, initializing AudioManager..."),this.audioManager.initialize()):(this.log("Autoplay disabled, showing overlay..."),await new Promise(n=>{this.autoplayOverlay=new G(this.options.container,{...this.options.autoplayOptions,onStart:()=>{this.log("User clicked start button"),this.audioManager&&(this.audioManager.initialize(),this.audioManager.playStartSound()),n()}})}),this.log("Autoplay overlay dismissed, continuing startup...")),await this.rendererManager.initialize(this.core),this.log("Renderer is ready"),this.postProcessOverlay=new z(this.options.container),this.log("PostProcessOverlay created"),this.onCorePaletteChanged(this.core.getPalette()),this.log("Initial palette sent to renderer"),this.log("Calling application.init()"),await this.options.application.init(this.core,this);let e=this.rendererManager.getCanvas();e&&this.input&&this.input.setMobileTarget(e),this.mode==="connected"&&this.networkSync?(this.log("Connecting to server"),this.audioManager&&this.networkSync.setAudioManager(this.audioManager),this.networkSync.setPostProcessCallback(n=>{this.applyPostProcessOrders(n)}),await this.networkSync.connect(),this.userId=await this.networkSync.joinGame(this.options.application)):await this.createLocalUser();let t=this.core.getUser(this.userId);if(t){let n=t.getDisplays();if(n.length>0){let r=n[0].getSize(),o=r.x,a=r.y,c=this.rendererManager.getRenderer(),d,u;if(this.rendererType==="webgl"){let h=c.getGridSize();d=h.cols,u=h.rows}else{let l=c;d=l.getCols(),u=l.getRows()}(d!==o||u!==a)&&(this.log(`Adjusting renderer from ${d}\xD7${u} to match display ${o}\xD7${a}`),c.resize(o,a))}}this.input&&this.input.start(),this.running=!0,this.startTime=performance.now(),this.lastTimestamp=this.startTime,this.lastRenderTimestamp=this.startTime,this.accumulatedTime=0,this.firstTickDone=!1,this.performanceMonitor.reset(),this.performanceMonitor.startTracking(this.startTime),this.log("Starting main loop"),this.mainLoop(this.lastTimestamp)}async stop(){if(!this.running)return;this.log("Stopping ClientRuntime"),this.running=!1,this.rafId&&(cancelAnimationFrame(this.rafId),this.rafId=0),this.audioManager&&this.audioManager.stopAll(),this.input&&this.input.stop(),this.mode==="connected"&&this.networkSync&&this.networkSync.disconnect();let e=this.core.getUser(this.userId);e&&this.options.application.destroyUser&&this.options.application.destroyUser(this.core,e,"Runtime stopped"),this.log("ClientRuntime stopped")}getStats(){let e=this.performanceMonitor.getStats();return{mode:this.mode,running:this.running,userCount:this.core.getUsers().length,fps:e.fps,uptime:this.running?performance.now()-this.startTime:0,totalFrames:0,latency:void 0,lastFrameTiming:e.lastFrame,avgFrameTiming:e.avgFrame}}async destroy(){await this.stop(),this.log("Destroying ClientRuntime"),this.visibilityChangeHandler&&(document.removeEventListener("visibilitychange",this.visibilityChangeHandler),this.visibilityChangeHandler=void 0),this.autoplayOverlay&&(this.autoplayOverlay.destroy(),this.autoplayOverlay=null),this.audioManager&&(this.audioManager.destroy(),this.audioManager=null),this.postProcessOverlay&&(this.postProcessOverlay.destroy(),this.postProcessOverlay=null);let e=this.rendererManager.getRenderer();e&&"clearOnResizeCallback"in e&&e.clearOnResizeCallback(),this.options.application.destroy&&this.options.application.destroy(),this.rendererManager.destroy(),this.input&&this.input.destroy(),this.networkSync&&this.networkSync.destroy(),this.log("ClientRuntime destroyed")}async createLocalUser(){let e=this.options;this.log(`Creating local user: ${this.userId}`);let t=this.core.createUser(this.userId,e.username);if(this.audioManager&&t.setAudioProcessor(this.audioManager),this.log("Calling application.initUser()"),this.options.application.initUser(this.core,t,{username:e.username}),t.hasAudioConfigCommands()&&(this.log("Applying audio config commands from initUser()"),t.applyAudioConfigCommands(t.flushAudioConfigCommands())),t.hasPendingMacroOrders()){this.log("Applying macro orders from initUser()");let n=t.flushMacroOrders();t.applyMacroOrders(n)}if(t.hasPostProcessCommands()){this.log("Applying post-process commands from initUser()");let n=t.flushPostProcessCommands(),i=this.postProcessOrderCollector.convertCommands(n);this.applyPostProcessOrders(i)}t.needsSendSounds()&&(await this.loadSoundsFromRegistry(),t.clearSendSounds()),this.log("Performing initial render"),this.core.endTick(),this.render(),this.log("Initial render complete")}async loadSoundsFromRegistry(){if(!this.audioManager){this.log("Cannot load sounds: AudioManager not initialized");return}let e=this.audioManager.getSoundBank();if(!e){this.log("Cannot load sounds: SoundBank not initialized");return}let n=this.core.getSoundRegistry().getAll();if(n.length===0){this.log("No sounds to load from registry");return}this.log(`Loading ${n.length} sounds from Core registry into AudioManager...`);for(let i of n)try{i.loadType==="file"&&i.data?(await e.loadFromData(i.soundId,i.name,i.data),this.log(`\u{1F50A} Loaded sound: ${i.name} (${i.data.length} bytes)`)):i.loadType==="external"&&i.url&&(await e.loadFromUrl(i.soundId,i.name,i.url),this.log(`\u{1F50A} Loaded external sound: ${i.name} from ${i.url}`))}catch(r){console.error(`[ClientRuntime] Failed to load sound "${i.name}":`,r)}this.log(`\u2713 ${e.size} sounds loaded into AudioManager`)}mainLoop(e){if(!this.running){this.rafId&&(cancelAnimationFrame(this.rafId),this.rafId=0);return}if(this.renderMode==="on-demand"){this.renderRequested&&(this.renderRequested=!1,this.performanceMonitor.updateFPS(e),this.render()),this.rafId=requestAnimationFrame(o=>this.mainLoop(o));return}let t=e-this.lastRenderTimestamp;if(this.lastRenderTimestamp>0&&t<this.FRAME_TIME_MIN-1){this.rafId=requestAnimationFrame(o=>this.mainLoop(o));return}this.lastRenderTimestamp=e;let n=(e-this.lastTimestamp)/1e3,i=Math.min(n,.1);this.lastTimestamp=e,this.performanceMonitor.updateFPS(e);let r=this.core.getUser(this.userId);if(!r){this.rafId=requestAnimationFrame(o=>this.mainLoop(o));return}if(this.mode==="local"){if(this.tickRate===0){let d=performance.now();this.render();let u=performance.now()-d;this.performanceMonitor.recordFrameTiming(0,u),this.rafId=requestAnimationFrame(l=>this.mainLoop(l));return}if(!this.firstTickDone){this.firstTickDone=!0,this.lastTimestamp=e,this.accumulatedTime=0,this.rafId=requestAnimationFrame(d=>this.mainLoop(d));return}this.accumulatedTime+=i;let o=1/this.tickRate;this.accumulatedTime>.5&&(this.accumulatedTime=o);let a=0,c=0;for(;this.accumulatedTime>=o&&a<5;){let d=performance.now();if(this.collectAndApplyInput(r),this.options.application.update(this.core,o),this.options.application.updateUser(this.core,r,o),r.clearTextInputs(),r.hasAudioConfigCommands()&&r.applyAudioConfigCommands(r.flushAudioConfigCommands()),r.hasSoundCommands()&&r.applyAudioCommands(r.flushSoundCommands()),r.hasPostProcessCommands()){let l=r.flushPostProcessCommands(),h=this.postProcessOrderCollector.convertCommands(l);this.applyPostProcessOrders(h)}if(r.hasPendingMacroOrders()){let l=r.flushMacroOrders();r.applyMacroOrders(l)}let u=r.updateMacros();if(r.processMacroEvents(u),r.hasBridgeMessages()){let l=r.getBridgeMessages();this.dispatchLocalBridgeMessages(l)}this.core.endTickSplit(),c+=performance.now()-d,this.accumulatedTime-=o,a++}if(a>0){let d=performance.now();this.render();let u=performance.now()-d;this.performanceMonitor.recordFrameTiming(c,u)}}else{let o=r.updateMacros();r.processMacroEvents(o),this.collectAndSendInput()}this.rafId=requestAnimationFrame(o=>this.mainLoop(o))}collectAndApplyInput(e){if(!this.input)return;let t=this.rendererManager.getCanvas();if(t){let a=e.getDisplays(),c=a.length>0?a[0].size.x:this.options.width,d=a.length>0?a[0].size.y:this.options.height,l=this.rendererManager.getRenderer()?.getOffsets?.(),h={offsetX:l?.offsetX??0,offsetY:l?.offsetY??0},g=R.collectMousePosition(this.input,t,c,d,h);e.setMousePosition(g.x,g.y,g.over),e.updateMacroMouse(g.x,g.y,g.isLeftDown);let m=R.collectTouchPositions(this.input,t,c,d,10,h);this.options.debug&&m.length>0&&console.warn("\u{1F4F1} Collected touches:",m),m.forEach(p=>{e.setTouchPosition(p.id,p.x,p.y,p.over)})}let n=R.collectTextInputs(this.input);n.length>0&&e.setTextInputs(n);let i=e.getInputBindingRegistry(),r=i.getAllAxes(),o=i.getAllButtons();if(r.length>0||o.length>0){let a=R.collectAxisSources(r,this.input),c=R.collectButtonSourcesWithTransitions(o,this.input);r.forEach(d=>{let u=i.evaluateAxis(d.bindingId,a);e.setAxis(d.name,u)}),o.forEach(d=>{let u=new Map,l=new Map,h=new Map;for(let[v,f]of c)u.set(v,f.pressed),l.set(v,f.justPressed),h.set(v,f.justReleased);let g=i.evaluateButton(d.bindingId,u),m=i.evaluateButton(d.bindingId,l),p=i.evaluateButton(d.bindingId,h);e.setButton(d.name,g),e.setButton(`${d.name}_justPressed`,m),e.setButton(`${d.name}_justReleased`,p)})}this.input.poll?.()}collectAndSendInput(){!this.networkSync||!this.input||this.networkSync.sendInput(this.input)}render(){let e=this.core.getUser(this.userId);if(e){let t=e.getMacroRenderOrders();for(let[n,i]of t){let r=e.getLayerById(n);r&&i.length>0&&r.addTemporaryOrders(i)}}this.rendererManager.render(this.core,this.userId)}onCorePaletteChanged(e){this.log(`Palette changed, updating renderer (${e.size} colors)`);let t=[];for(let i=0;i<256;i++){let r=e.get(i);r?t.push({r:r.r,g:r.g,b:r.b,a:r.a}):t.push({r:0,g:0,b:0,a:0})}let n=this.rendererManager.getRenderer();if(!n){console.warn("[ClientRuntime] Cannot update palette: renderer is null");return}"setPalette"in n&&typeof n.setPalette=="function"?(n.setPalette(t),this.log("\u2713 Renderer palette updated successfully")):console.warn("[ClientRuntime] Renderer does not have setPalette method")}onCoreBitmapFontChanged(e){this.log(`Bitmap font ${e} changed, updating renderer`);let t=this.core.getBitmapFont(e);if(!t){console.warn(`[ClientRuntime] Font ${e} not found in registry`);return}let n=new Map,i=0;for(let o=0;o<256;o++){let a=t.getGlyph(o);a&&(n.set(o,a),i++)}this.log(`Built bitmap font map with ${i} glyphs, dimensions: ${t.getCharWidth()}x${t.getCharHeight()} (cell: ${t.getCellWidth()}x${t.getCellHeight()})`);let r=this.rendererManager.getRenderer();if(!r){console.warn("[ClientRuntime] Cannot update font: renderer is null");return}"setBitmapFont"in r&&typeof r.setBitmapFont=="function"?(r.setBitmapFont(n,t.getCharWidth(),t.getCharHeight(),t.getCellWidth(),t.getCellHeight()),this.log("\u2713 Renderer bitmap font updated successfully"),this.postProcessOverlay&&"syncWithRenderer"in this.postProcessOverlay&&"getCellWidth"in r&&"getCellHeight"in r&&"getCurrentScale"in r&&"getGridSize"in r&&(this.postProcessOverlay.syncWithRenderer(r),this.log("\u2713 PostProcessOverlay synced with renderer dimensions"))):console.warn("[ClientRuntime] Renderer does not have setBitmapFont method")}getAudioManager(){return this.audioManager}getAudioContext(){return this.audioManager?.getContext()??null}applyAmbientEffectConfig(e){let t=this.rendererManager?.getRenderer();t&&"setAmbientEffect"in t&&(e.enabled?t.setAmbientEffect({blur:e.blur,scale:e.scale}):t.setAmbientEffect(!1),this.log(`Ambient effect ${e.enabled?"enabled":"disabled"}`))}applyPostProcessOrders(e){if(!this.postProcessOverlay){this.log("PostProcessOverlay not initialized, skipping post-process orders");return}for(let t of e)switch(t.type){case C.SetConfig:{let n={};t.scanlines&&(n.scanlines={enabled:t.scanlines.enabled,opacity:t.scanlines.opacity,pattern:this.patternFromType(t.scanlines.pattern),color:{r:t.scanlines.colorR,g:t.scanlines.colorG,b:t.scanlines.colorB}}),t.ambientEffect&&(n.ambientEffect={enabled:t.ambientEffect.enabled,blur:t.ambientEffect.blur,scale:t.ambientEffect.scale}),this.postProcessOverlay.setConfig(Object.keys(n).length>0?n:null),n.ambientEffect&&this.applyAmbientEffectConfig(n.ambientEffect);break}case C.SetScanlines:{this.postProcessOverlay.setScanlines({enabled:t.enabled,opacity:t.opacity,pattern:this.patternFromType(t.pattern),color:{r:t.colorR,g:t.colorG,b:t.colorB}});break}case C.SetAmbientEffect:{this.applyAmbientEffectConfig({enabled:t.enabled,blur:t.blur,scale:t.scale});break}case C.SetScalingMode:{this.applyScalingMode(t.mode);break}case C.SetGrid:{this.applyGridConfig(t);break}}}applyScalingMode(e){let t=this.rendererManager.getRenderer();if(t&&"setScalingMode"in t){let n=N(e);t.setScalingMode(n),this.log(`Scaling mode set to ${n}`)}}applyGridConfig(e){let t=this.rendererManager.getRenderer();if(t&&"setGrid"in t){let n=e.colorA/255;t.setGrid({enabled:e.enabled,color:`rgba(${e.colorR},${e.colorG},${e.colorB},${n})`,lineWidth:e.lineWidth}),this.log(`Grid ${e.enabled?"enabled":"disabled"}`)}}patternFromType(e){switch(e){case 0:return"horizontal";case 1:return"vertical";case 2:return"grid";default:return"horizontal"}}getPostProcessOverlay(){return this.postProcessOverlay}dispatchLocalBridgeMessages(e){for(let t of e){let{channel:n,data:i}=t,r=this.localBridgeHandlers.get(n);if(r)for(let o of r)try{o(i)}catch(a){console.error(`[ClientRuntime] Bridge handler error on '${n}':`,a)}for(let o of this.localBridgeWildcardHandlers)try{o(n,i)}catch(a){console.error("[ClientRuntime] Bridge wildcard handler error:",a)}this.log(`Bridge message dispatched on channel '${n}'`)}}sendBridge(e,t){if(this.mode==="connected"&&this.networkSync)this.networkSync.sendBridge(e,t);else if(this.mode==="local"){let n=this.core.getUser(this.userId);n&&this.options.application.onBridgeMessage&&(this.options.application.onBridgeMessage(this.core,n,e,t),this.log(`Bridge message sent to application on channel '${e}'`))}}onBridge(e,t){this.mode==="connected"&&this.networkSync?this.networkSync.onBridge(e,t):this.mode==="local"&&(e==="*"?this.localBridgeWildcardHandlers.add(t):(this.localBridgeHandlers.has(e)||this.localBridgeHandlers.set(e,new Set),this.localBridgeHandlers.get(e).add(t)),this.log(`Bridge handler registered for channel '${e}'`))}offBridge(e,t){this.mode==="connected"&&this.networkSync?this.networkSync.offBridge(e,t):this.mode==="local"&&(e==="*"?this.localBridgeWildcardHandlers.delete(t):this.localBridgeHandlers.get(e)?.delete(t))}removeAllBridgeHandlers(e){this.mode==="connected"&&this.networkSync?this.networkSync.removeAllBridgeHandlers(e):this.mode==="local"&&(e===void 0?(this.localBridgeHandlers.clear(),this.localBridgeWildcardHandlers.clear()):e==="*"?this.localBridgeWildcardHandlers.clear():this.localBridgeHandlers.delete(e))}log(e){this.options.debug&&console.warn(`[ClientRuntime/${this.mode}] ${e}`)}};y(x,"ClientRuntime");var O=x;import{ScalingMode as Re}from"@utsp/render";import{AudioManager as Me}from"@utsp/audio";export{Me as AudioManager,O as ClientRuntime,F as RendererType,Re as ScalingMode};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@utsp/runtime-client",
3
- "version": "0.8.0-nightly.20251209024647.83e7069",
3
+ "version": "0.8.0",
4
4
  "description": "UTSP Runtime Client - Local and multi-user client runtime",
5
5
  "author": "THP Software",
6
6
  "license": "MIT",
@@ -48,12 +48,12 @@
48
48
  "access": "public"
49
49
  },
50
50
  "dependencies": {
51
- "@utsp/audio": "0.8.0-nightly.20251209024647.83e7069",
52
- "@utsp/core": "0.8.0-nightly.20251209024647.83e7069",
53
- "@utsp/input": "0.8.0-nightly.20251209024647.83e7069",
54
- "@utsp/network-client": "0.8.0-nightly.20251209024647.83e7069",
55
- "@utsp/render": "0.8.0-nightly.20251209024647.83e7069",
56
- "@utsp/types": "0.8.0-nightly.20251209024647.83e7069"
51
+ "@utsp/audio": "0.8.0",
52
+ "@utsp/core": "0.8.0",
53
+ "@utsp/input": "0.8.0",
54
+ "@utsp/network-client": "0.8.0",
55
+ "@utsp/render": "0.8.0",
56
+ "@utsp/types": "0.8.0"
57
57
  },
58
58
  "devDependencies": {
59
59
  "typescript": "^5.6.3"