@skewedaspect/sage 0.6.1 → 0.6.2

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/sage.es.js CHANGED
@@ -3,7 +3,7 @@ var I = (r, e, t) => e in r ? A(r, e, { enumerable: !0, configurable: !0, writab
3
3
  var n = (r, e, t) => I(r, typeof e != "symbol" ? e + "" : e, t);
4
4
  import { FreeCamera as G, Vector3 as b, HemisphericLight as V, MeshBuilder as E, PhysicsAggregate as C, PhysicsShapeType as D, Scene as B, NullEngine as L, WebGPUEngine as K, Engine as T, HavokPlugin as U } from "@babylonjs/core";
5
5
  import R from "@babylonjs/havok";
6
- const m = "0.6.0";
6
+ const m = "0.6.1";
7
7
  class z {
8
8
  /**
9
9
  * Creates an instance of SkewedAspectGameEngine.
package/dist/sage.umd.js CHANGED
@@ -1,2 +1,2 @@
1
- (function(d,h){typeof exports=="object"&&typeof module<"u"?h(exports,require("@babylonjs/core"),require("@babylonjs/havok")):typeof define=="function"&&define.amd?define(["exports","@babylonjs/core","@babylonjs/havok"],h):(d=typeof globalThis<"u"?globalThis:d||self,h(d.SAGameEngine={},d.BABYLON,d.HavokPhysics))})(this,function(d,h,y){"use strict";var ee=Object.defineProperty;var te=(d,h,y)=>h in d?ee(d,h,{enumerable:!0,configurable:!0,writable:!0,value:y}):d[h]=y;var s=(d,h,y)=>te(d,typeof h!="symbol"?h+"":h,y);const b="0.6.0";class D{constructor(e,t,i,n,o,r,a){s(this,"canvas");s(this,"renderEngine");s(this,"physics");s(this,"managers");s(this,"engines");s(this,"eventBus");s(this,"logger");s(this,"started",!1);s(this,"_log");s(this,"_beforeStartHook",null);s(this,"_onStartHook",null);s(this,"_onTeardownHook",null);this.canvas=e,this.renderEngine=t,this.physics=i,this.eventBus=n,this.logger=o,this.engines=r,this.managers=a,this._log=o.getLogger("GameEngine")}onBeforeStart(e){if(this._beforeStartHook!==null)throw new Error("A beforeStart hook is already registered. Only one hook is allowed per lifecycle event.");this._beforeStartHook=e}onStart(e){if(this._onStartHook!==null)throw new Error("An onStart hook is already registered. Only one hook is allowed per lifecycle event.");this._onStartHook=e}onTeardown(e){if(this._onTeardownHook!==null)throw new Error("An onTeardown hook is already registered. Only one hook is allowed per lifecycle event.");this._onTeardownHook=e}async start(){this.started?this._log.warn("Game engine is already started. Skipping start."):(this._log.info(`Starting SkewedAspect Game Engine (Version: ${b})...`),this._beforeStartHook&&(this._log.debug("Executing beforeStart hook..."),await this._beforeStartHook(this)),await this.managers.gameManager.start(this.canvas),this._log.info("Game engine started successfully"),this._onStartHook&&(this._log.debug("Executing onStart hook..."),await this._onStartHook(this)),this.started=!0)}async stop(){if(this.started){this._log.info(`Stopping SkewedAspect Game Engine (Version: ${b})...`);let e=null;if(this._onTeardownHook)try{this._log.debug("Executing onTeardown hook..."),await this._onTeardownHook(this)}catch(t){this._log.error(`Error in onTeardown hook: ${t}`),e=e||t}for(const t of Object.keys(this.managers)){const i=this.managers[t];if(typeof i.$teardown=="function")try{this._log.debug(`Tearing down manager: ${t}`),await i.$teardown()}catch(n){this._log.error(`Error tearing down manager ${t}: ${n}`),e=e||n}}for(const t of Object.keys(this.engines)){const i=this.engines[t];if(typeof i.$teardown=="function")try{this._log.debug(`Tearing down engine: ${t}`),await i.$teardown()}catch(n){this._log.error(`Error tearing down engine ${t}: ${n}`),e=e||n}}try{await this.managers.gameManager.stop()}catch(t){this._log.error(`Error stopping game manager: ${t}`),e=e||t}if(this._log.info("Game engine stopped successfully"),e)throw e}else this._log.warn("Game engine is not started. Skipping stop.")}}class k{constructor(){s(this,"timers");this.timers=new Map}getStyleForLevel(e){const t={trace:"color: #999999",debug:"color: #00AAAA",info:"color: #00AA00",warn:"color: #AAAA00",error:"color: #AA0000",timer:"color: #AA00AA",none:"color: inherit"};return t[e]||t.none}formatMessage(e,t){const i=new Date,n=i.getHours()%12||12,o=i.getMinutes().toString().padStart(2,"0"),r=i.getSeconds().toString().padStart(2,"0"),a=i.getHours()>=12?"PM":"AM",u=`[${n}:${o}:${r} ${a}]`,l=t.toUpperCase();return[`${u} %c${l}%c (${e}): `,this.getStyleForLevel(t),"",""]}trace(e,t,...i){const[n,o,r,a]=this.formatMessage(e,"trace");console.debug(n,o,r,a,t,...i)}debug(e,t,...i){const[n,o,r,a]=this.formatMessage(e,"debug");console.debug(n,o,r,a,t,...i)}info(e,t,...i){const[n,o,r,a]=this.formatMessage(e,"info");console.info(n,o,r,a,t,...i)}warn(e,t,...i){const[n,o,r,a]=this.formatMessage(e,"warn");console.warn(n,o,r,a,t,...i)}error(e,t,...i){const[n,o,r,a]=this.formatMessage(e,"error");console.error(n,o,r,a,t,...i)}time(e,t){const i=`${e}:${t}`;this.timers.set(i,performance.now())}timeEnd(e,t){const i=`${e}:${t}`,n=this.timers.get(i);if(n!==void 0){const o=performance.now()-n;this.timers.delete(i);const[r,a,u,l]=this.formatMessage(e,"timer");console.info(`${r} Timer '${t}' completed in ${o.toFixed(2)}ms`,a,u,l)}else console.warn(`[${e}]: Timer '${t}' does not exist`)}}class M{trace(e,t,...i){}debug(e,t,...i){}info(e,t,...i){}warn(e,t,...i){}error(e,t,...i){}time(e,t){}timeEnd(e,t){}}class _{constructor(e,t="none",i=new M){s(this,"category");s(this,"backend");s(this,"minLevel");this.category=e,this.backend=i,this.minLevel=t}updateSettings(e,t){this.backend=e,this.minLevel=t}trace(e,...t){this.shouldLog("trace")&&this.backend.trace(this.category,e,...t)}debug(e,...t){this.shouldLog("debug")&&this.backend.debug(this.category,e,...t)}info(e,...t){this.shouldLog("info")&&this.backend.info(this.category,e,...t)}warn(e,...t){this.shouldLog("warn")&&this.backend.warn(this.category,e,...t)}error(e,...t){this.shouldLog("error")&&this.backend.error(this.category,e,...t)}time(e){this.minLevel!=="none"&&this.backend.time(this.category,e)}timeEnd(e){this.minLevel!=="none"&&this.backend.timeEnd(this.category,e)}shouldLog(e){if(this.minLevel==="none")return!1;switch(this.minLevel){case"trace":return!0;case"debug":return e!=="trace";case"info":return e==="info"||e==="warn"||e==="error";case"warn":return e==="warn"||e==="error";case"error":return e==="error";default:return!1}}}class A{constructor(e="debug",t=new k){s(this,"backend");s(this,"level");s(this,"loggers");this.backend=t,this.level=e,this.loggers=new Map}setBackend(e){this.backend=e,this.loggers.forEach(t=>{t instanceof _&&t.updateSettings(this.backend,this.level)})}setLevel(e){this.level=e,this.loggers.forEach(t=>{t instanceof _&&t.updateSettings(this.backend,this.level)})}getLevel(){return this.level}getLogger(e){this.loggers.has(e)||this.loggers.set(e,new _(e,this.level,this.backend));const t=this.loggers.get(e);if(t===void 0)throw new Error(`Failed to create logger for category: ${e}`);return t}}class I{constructor(e){s(this,"directMap",new Map);s(this,"patternSubs",new Set);s(this,"_log");this._log=(e==null?void 0:e.getLogger("EventBus"))||new _("EventBus")}subscribe(e,t){return this._log.trace(`Subscribe request for: ${e.toString()}`),e instanceof RegExp||typeof e=="string"&&e.includes("*")?this.subscribePattern(e,t):this.subscribeExact(e,t)}subscribeExact(e,t){this._log.debug(`Adding exact subscription for: ${e}`);let i=this.directMap.get(e);i||(i=new Set,this.directMap.set(e,i));const n={callback:o=>t(o)};return i.add(n),()=>{this._log.debug(`Removing exact subscription for: ${e}`),i.delete(n),i.size===0&&this.directMap.delete(e)}}subscribePattern(e,t){let i;if(typeof e=="string"){const o=e.replace(/[-/\\^$+?.()|[\]{}]/g,"\\$&").replace(/\*/g,".*");i=new RegExp(`^${o}$`),this._log.debug(`Adding pattern subscription for string: ${e}, regex: ${i.toString()}`)}else i=e,this._log.debug(`Adding pattern subscription for regex: ${i.toString()}`);const n={pattern:i,callback:o=>t(o)};return this.patternSubs.add(n),()=>{this._log.debug(`Removing pattern subscription: ${i.toString()}`),this.patternSubs.delete(n)}}publish(e){this._log.trace(`Publishing event: ${e.type}`,e);let t=0;const i=this.directMap.get(e.type);if(i){t+=i.size;for(const n of i)Promise.resolve().then(()=>n.callback(e))}for(const n of this.patternSubs)n.pattern&&n.pattern.test(e.type)&&(t++,Promise.resolve().then(()=>n.callback(e)));t===0?this._log.debug(`No subscribers found for event: ${e.type}`):this._log.trace(`Event ${e.type} dispatched to ${t} subscribers`)}}class T{constructor(e,t,i){s(this,"_engine");s(this,"_physics");s(this,"_log");this._engine=e,this._physics=t,this._log=(i==null?void 0:i.getLogger("SceneEngine"))||new _("SceneEngine")}async _buildDemoScene(e,t){this._log.debug("Building demo scene..."),this._log.trace("Creating camera...");const i=new h.FreeCamera("camera1",new h.Vector3(0,5,-10),e);i.setTarget(h.Vector3.Zero()),i.attachControl(t,!0),this._log.trace("Creating light...");const n=new h.HemisphericLight("light",new h.Vector3(0,1,0),e);n.intensity=.7,this._log.trace("Creating sphere...");const o=h.MeshBuilder.CreateSphere("sphere",{diameter:2,segments:32},e);o.position.y=4,this._log.trace("Creating ground...");const r=h.MeshBuilder.CreateGround("ground",{width:10,height:10},e);this._log.trace("Adding physics to sphere..."),new h.PhysicsAggregate(o,h.PhysicsShapeType.SPHERE,{mass:1,restitution:.75},e),this._log.trace("Adding physics to ground..."),new h.PhysicsAggregate(r,h.PhysicsShapeType.BOX,{mass:0},e),this._log.debug("Demo scene built successfully")}async loadScene(e){this._log.info("Loading scene..."),this._log.time("sceneLoad"),this._log.debug("Creating new scene...");const t=new h.Scene(this._engine);return this._log.debug("Enabling physics with gravity (0, -9.8, 0)..."),t.enablePhysics(new h.Vector3(0,-9.8,0),this._physics),await this._buildDemoScene(t,e),this._log.timeEnd("sceneLoad"),this._log.info("Scene loaded successfully"),t}async $teardown(){return this._log.info("Tearing down SceneEngine"),this._log.info("SceneEngine torn down successfully"),Promise.resolve()}}const G=["trigger","toggle","value"];class K{constructor(e,t,i,n={}){s(this,"type","trigger");s(this,"action");s(this,"context");s(this,"deviceID");s(this,"reader");s(this,"_edgeMode");s(this,"_threshold");s(this,"_lastDigitalState",!1);this.action=e,this.deviceID=t,this.reader=i,this.context=n.context,this._edgeMode=n.edgeMode??"rising",this._threshold=n.threshold??.5}get options(){return{edgeMode:this._edgeMode,threshold:this._threshold}}process(e,t){const i=this.reader.getValue(e)??!1,n=typeof i=="boolean"?i:i>=this._threshold;let o=!1;switch(this._edgeMode){case"rising":o=n&&!this._lastDigitalState;break;case"falling":o=!n&&this._lastDigitalState;break;case"both":o=n!==this._lastDigitalState;break}if(this._lastDigitalState=n,o){let r;if(this.action.type==="analog"){let u=typeof i=="number"?i:i?1:0;if(this.action.minValue!==void 0||this.action.maxValue!==void 0){const l=this.action.minValue??0,f=this.action.maxValue??1;u=l+u*(f-l)}r=u}else r=!0;const a={type:`action:${this.action.name}`,payload:{value:r,deviceId:this.deviceID,context:this.context}};t.publish(a)}}toJSON(){return{type:this.type,action:this.action.name,input:{deviceID:this.deviceID,...this.reader.toJSON()},context:this.context,options:this.options}}}class U{constructor(e,t,i,n={}){s(this,"type","toggle");s(this,"action");s(this,"context");s(this,"deviceID");s(this,"reader");s(this,"_invert");s(this,"_threshold");s(this,"_initialState");s(this,"_onValue");s(this,"_offValue");s(this,"_lastDigitalState",!1);s(this,"_toggleState");this.action=e,this.deviceID=t,this.reader=i,this.context=n.context,this._invert=n.invert??!1,this._threshold=n.threshold??.5,this._initialState=n.initialState??!1,this._toggleState=this._initialState,this._onValue=n.onValue??!0,this._offValue=n.offValue??!1}get state(){return this._toggleState}set state(e){this._toggleState=e}get onValue(){return this._onValue}get offValue(){return this._offValue}get value(){return this.action.type==="analog"?this._toggleState?this.action.maxValue??1:this.action.minValue??0:this._toggleState?this._onValue:this._offValue}get options(){return{invert:this._invert,threshold:this._threshold,initialState:this._initialState,onValue:this._onValue,offValue:this._offValue}}process(e,t){const i=this.reader.getValue(e)??!1,n=typeof i=="boolean"?i:i>=this._threshold,o=this._invert?!n&&this._lastDigitalState:n&&!this._lastDigitalState;this._lastDigitalState=n,o&&(this._toggleState=!this._toggleState,t.publish({type:`action:${this.action.name}`,payload:{value:this.value,deviceId:this.deviceID,context:this.context}}))}reset(){this._toggleState=this._initialState}toJSON(){return{type:this.type,action:this.action.name,input:{deviceID:this.deviceID,...this.reader.toJSON()},state:this._toggleState,context:this.context,options:this.options}}}class R{constructor(e,t,i,n={}){s(this,"type","value");s(this,"action");s(this,"context");s(this,"deviceID");s(this,"reader");s(this,"_scale");s(this,"_offset");s(this,"_invert");s(this,"_emitOnChange");s(this,"_deadzone");s(this,"_onValue");s(this,"_offValue");s(this,"_min");s(this,"_max");s(this,"_lastValue");this.action=e,this.deviceID=t,this.reader=i,this.context=n.context,this._scale=n.scale??1,this._offset=n.offset??0,this._invert=n.invert??!1,this._emitOnChange=n.emitOnChange??!0,this._deadzone=n.deadzone??0,this._onValue=n.onValue??!0,this._offValue=n.offValue??!1;const o=this.action.type==="analog"?this.action.minValue??Number.NEGATIVE_INFINITY:Number.NEGATIVE_INFINITY;this._min=n.min??o;const r=this.action.type==="analog"?this.action.maxValue??Number.POSITIVE_INFINITY:Number.POSITIVE_INFINITY;this._max=n.max??r}get options(){return{scale:this._scale,offset:this._offset,invert:this._invert,emitOnChange:this._emitOnChange,deadzone:this._deadzone,onValue:this._onValue,offValue:this._offValue,min:this._min,max:this._max}}process(e,t){const i=this.reader.getValue(e);if(i===void 0)return;const n=typeof i=="boolean"?i?1:0:i;if(n===void 0)return;let o=this._deadzone>0&&Math.abs(n)<this._deadzone?0:n;o=(this._invert?-o:o)*this._scale+this._offset,o=Math.max(this._min,Math.min(this._max,o)),!(this._emitOnChange&&this._lastValue===o)&&(this._lastValue=o,t.publish({type:`action:${this.action.name}`,payload:{value:o,deviceId:this.deviceID,context:this.context}}))}toJSON(){return{type:this.type,action:this.action.name,input:{deviceID:this.deviceID,...this.reader.toJSON()},context:this.context,options:this.options}}}class S{constructor(e,t={}){s(this,"sourceType","key");s(this,"sourceKey");s(this,"useDelta");this.sourceKey=e,this.useDelta=t.useDelta||!1}getValue(e){if(e.type!=="keyboard")return;const t=e;return this.useDelta?t.delta[this.sourceKey]:t.keys[this.sourceKey]}static fromString(e,t={}){return new S(e,t)}toJSON(){return{type:"keyboard",sourceType:this.sourceType,sourceKey:this.sourceKey,options:{useDelta:this.useDelta}}}}class x{constructor(e,t){s(this,"sourceType");s(this,"sourceKey");this.sourceType=e,this.sourceKey=t}getValue(e){if(e.type==="mouse")switch(this.sourceType){case"button":{const t=e.buttons[this.sourceKey];return t?t.pressed:void 0}case"position":{const[t,i]=this.sourceKey.split(":");return!t||!i||t!=="absolute"&&t!=="relative"||i!=="x"&&i!=="y"?void 0:e.position[t][i]}case"wheel":{const t=this.sourceKey==="deltaX"||this.sourceKey==="deltaY"||this.sourceKey==="deltaZ";return e.wheel&&t?e.wheel[this.sourceKey]:void 0}default:return}}static fromString(e){const[t,...i]=e.split(":"),n=i.join(":");if(!t||!n)throw new Error(`Invalid mouse source format: ${e}`);return new x(t,n)}toJSON(){return{type:"mouse",sourceType:this.sourceType,sourceKey:this.sourceKey}}}class E{constructor(e,t,i={}){s(this,"sourceType");s(this,"sourceKey");s(this,"useAnalogValue");s(this,"deadzone");s(this,"invert");this.sourceType=e,this.sourceKey=t,this.useAnalogValue=i.useAnalogValue||!1,this.deadzone=i.deadzone||.1,this.invert=i.invert||!1}getValue(e){if(e.type==="gamepad")switch(this.sourceType){case"button":{const t=e.buttons[this.sourceKey];return t?this.useAnalogValue?t.value:t.pressed:void 0}case"axis":{let t=e.axes[this.sourceKey];return t===void 0?void 0:(Math.abs(t)<this.deadzone&&(t=0),this.invert?-t:t)}default:return}}static fromString(e,t={}){const[i,n]=e.split(":");if(!i||!n)throw new Error(`Invalid gamepad source format: ${e}`);return new E(i,n,t)}toJSON(){return{type:"gamepad",sourceType:this.sourceType,sourceKey:this.sourceKey,options:{useAnalogValue:this.useAnalogValue,deadzone:this.deadzone,invert:this.invert}}}}class z{constructor(e,t){s(this,"_bindings",new Map);s(this,"_actions",new Map);s(this,"_contexts",new Map);s(this,"_activeContexts",new Set);s(this,"_eventBus");s(this,"_log");this._eventBus=e,this._eventBus.subscribe("input:changed",i=>{i.payload&&this.$handleInput(i.payload.device,i.payload.state)}),this._log=(t==null?void 0:t.getLogger("BindingManager"))||new _("BindingManager"),this._log.debug("BindingManager initialized")}_isBindingContextActive(e){return e.context?this._activeContexts.has(e.context):!0}_getOrCreateContext(e,t=!0){let i=this._contexts.get(e);return i||(i={name:e,exclusive:t},this._contexts.set(e,i),this._log.debug(`Auto-created context "${e}" (exclusive: ${t})`)),i}_deactivateExclusiveContexts(e){const t=[];for(const i of this._activeContexts){if(i===e)continue;const n=this._contexts.get(i);n!=null&&n.exclusive&&(this._activeContexts.delete(i),t.push(i))}return t}_createBindingFromDefinition(e){const t=this._actions.get(e.action);if(!t)return this._log.warn(`Cannot create binding: Action "${e.action}" not found.`),null;const{deviceID:i,...n}=e.input;switch(e.type){case"trigger":return new K(t,i,this._createInputSourceFromDefinition(n),{...e.options||{},context:e.context});case"toggle":return new U(t,i,this._createInputSourceFromDefinition(n),{...e.options||{},context:e.context});case"value":return new R(t,i,this._createInputSourceFromDefinition(n),{...e.options||{},context:e.context});default:return this._log.error(`Binding type not implemented: ${e.type}`),null}}_createInputSourceFromDefinition(e){switch(e.type){case"keyboard":return new S(e.sourceKey,e.options);case"mouse":{const t=e.sourceType;if(!(t==="button"||t==="position"||t==="wheel"))throw new Error(`Invalid mouse source type: ${t}`);return new x(t,e.sourceKey)}case"gamepad":{const t=e.sourceType;if(!(t==="button"||t==="axis"))throw new Error(`Invalid gamepad source type: ${t}`);return new E(t,e.sourceKey,e.options)}default:throw new Error(`Unsupported input source type: ${e.type}`)}}$handleInput(e,t){const i=this._bindings.get(e.id);if(!(!i||i.length===0)&&!(this._activeContexts.size===0&&i.some(n=>n.context)))for(const n of i)this._isBindingContextActive(n)&&n.process(t,this._eventBus)}registerAction(e){if(this._log.debug(`Registering action "${e.name}"`),this._actions.has(e.name)){const t=`Action "${e.name}" already registered.`;throw this._log.error(t),new Error(t)}this._actions.set(e.name,e),this._log.debug(`Action "${e.name}" registered successfully`)}getAction(e){this._log.trace(`Getting action "${e}"`);const t=this._actions.get(e)??null;return t||this._log.debug(`Action "${e}" not found`),t}registerContext(e,t=!0){const i=this._getOrCreateContext(e,t);return i.exclusive!==t&&(i.exclusive=t,this._log.info(`Updated context "${e}" exclusivity: ${t}`)),i}activateContext(e){const i=this._getOrCreateContext(e).exclusive;this._log.debug(`Activating context "${e}" (exclusive: ${i})`);const n=this._activeContexts.has(e);if(i){const o=this._deactivateExclusiveContexts(e);o.length>0&&this._log.info(`Deactivated exclusive contexts: ${o.join(", ")}`)}n||(this._activeContexts.add(e),this._log.info(`Context "${e}" activated${i?" as exclusive":""}`))}deactivateContext(e){this._log.debug(`Deactivating context "${e}"`),this._activeContexts.has(e)?(this._activeContexts.delete(e),this._log.info(`Context "${e}" deactivated`)):this._log.debug(`Context "${e}" was not active`)}getActiveContexts(){return[...this._activeContexts]}isContextActive(e){return this._activeContexts.has(e)}getContext(e){return this._contexts.get(e)||null}$registerBinding(e){var t;if(!G.includes(e.type))throw new Error(`Invalid binding type: ${e.type}`);e.context&&!this._contexts.has(e.context)&&this.registerContext(e.context),this._bindings.has(e.deviceID)||this._bindings.set(e.deviceID,[]),(t=this._bindings.get(e.deviceID))==null||t.push(e),this._log.debug(`Registered ${e.type} binding for "${e.action.name}" in context "${e.context||null}"`)}registerBinding(e){const t=this._createBindingFromDefinition(e);t?this.$registerBinding(t):this._log.error(`Failed to create binding for action "${e.action}" with type "${e.type}"`)}unregisterBindings(e,t=null){this._log.debug(`Unregistering all bindings for action "${e}" in context "${t}"`);let i=0;for(const[n,o]of this._bindings.entries()){const r=o.filter(a=>{const u=a.context||null,l=a.action.name!==e||u!==t;return l||i++,l});r.length!==o.length&&this._bindings.set(n,r)}this._log.info(`Removed ${i} bindings for action "${e}" in context "${t}"`)}getBindingsForAction(e,t){const i=[];for(const n of this._bindings.values())for(const o of n){const r=o.context||null;o.action.name===e&&(!t||r===t)&&i.push(o)}return i}exportConfiguration(){this._log.debug("Exporting binding configuration");const e=[...this._actions.values()].map(n=>n.type==="analog"?{name:n.name,type:n.type,minValue:n.minValue??0,maxValue:n.maxValue??1}:n),t=[];for(const n of this._bindings.values())for(const o of n)t.push(o.toJSON());const i=[...this._contexts.values()].map(n=>({name:n.name,exclusive:n.exclusive,active:this._activeContexts.has(n.name)}));return this._log.info(`Configuration exported: ${e.length} actions, ${t.length} bindings, ${i.length} contexts`),{actions:e,bindings:t,contexts:i}}importConfiguration(e){this._log.debug("Importing binding configuration"),this._bindings.clear(),this._actions.clear(),this._contexts.clear(),this._activeContexts.clear();for(const t of e.actions)this.registerAction(t);for(const t of e.contexts)this.registerContext(t.name,t.exclusive),t.active&&this.activateContext(t.name);for(const t of e.bindings)try{this.registerBinding(t)}catch(i){i instanceof Error&&this._log.error(`Failed to import binding for action "${t.action}": ${i.message}`)}this._log.info(`Configuration imported: ${e.actions.length} actions, ${e.bindings.length} bindings, ${e.contexts.length} contexts`)}async $teardown(){return this._log.info("Tearing down BindingManager"),this._bindings.clear(),this._actions.clear(),this._contexts.clear(),this._activeContexts.clear(),this._log.info("BindingManager torn down successfully"),Promise.resolve()}}function $(){return typeof window<"u"&&typeof window.document<"u"}function V(){return $()&&!!window.navigator.gpu}class P{constructor(e,t,i,n,o){s(this,"_engine");s(this,"_entityManager");s(this,"_inputManager");s(this,"_sceneEngine");s(this,"_currentScene",null);s(this,"_log");s(this,"_boundResizeHandler");s(this,"started",!1);this._engine=e,this._sceneEngine=t,this._entityManager=i,this._inputManager=n,this._log=(o==null?void 0:o.getLogger("GameManager"))||new _("GameManager"),this._boundResizeHandler=this._resizeHandler.bind(this),$()&&window.addEventListener("resize",this._boundResizeHandler)}_renderLoop(){const e=this._engine.getDeltaTime();this._entityManager.$frameUpdate(e),this._inputManager&&this._inputManager.pollGamepads(),this._currentScene&&this._currentScene.render()}_resizeHandler(){this.started&&this._engine.resize()}async start(e){this._currentScene=await this._sceneEngine.loadScene(e),this._engine.runRenderLoop(this._renderLoop.bind(this)),this.started=!0,this._log.info("SkewedAspect Game Engine started successfully")}async stop(){this.started=!1,this._engine.stopRenderLoop(),this._log.info("SkewedAspect Game Engine stopped successfully")}async $teardown(){this._log.info("Tearing down GameManager"),this.started&&await this.stop(),$()&&window.removeEventListener("resize",this._boundResizeHandler),this._log.info("GameManager torn down successfully")}}class O{constructor(){s(this,"entity",null)}$emit(e){if(!this.entity)throw new Error("Entity is not set for this behavior.");e.senderID=this.entity.id,this.entity.eventBus.publish(e)}$setEntity(e){this.entity=e}}class B{constructor(e,t,i,n){s(this,"id");s(this,"type");s(this,"state");s(this,"behaviors",new Map);s(this,"eventBus");s(this,"subscriptions",new Map);this.id=crypto.randomUUID(),this.type=e,this.state=i,this.eventBus=t;for(const o of n)this.attachBehavior(new o)}async $processEvent(e){for(const t of this.behaviors.values())if(await t.processEvent(e,this.state))break}$update(e){var t;for(const i of this.behaviors.values())(t=i.update)==null||t.call(i,e,this.state)}async $destroy(){var e;for(const t of this.behaviors.values())(e=t.destroy)==null||e.call(t);for(const t of this.subscriptions.values())t.unsubscribe();this.behaviors.clear(),this.subscriptions.clear()}attachBehavior(e){if(this.behaviors.has(e.name))throw new Error(`Behavior ${e.name} is already attached to this entity.`);for(const t of e.eventSubscriptions){const i=this.subscriptions.get(t);if(i)i.count++;else{const n=this.eventBus.subscribe(t,this.$processEvent.bind(this));this.subscriptions.set(t,{count:1,unsubscribe:n})}}this.behaviors.set(e.name,e),e.$setEntity(this)}detachBehavior(e){const t=this.behaviors.get(e);if(t){for(const i of t.eventSubscriptions){const n=this.subscriptions.get(i);n&&(n.count--,n.count<=0&&(n.unsubscribe(),this.subscriptions.delete(i)))}this.behaviors.delete(t.name),t.$setEntity(null)}}}class H{constructor(e,t,i){s(this,"eventBus");s(this,"entities",new Map);s(this,"entityDefinitions",new Map);s(this,"bindingManager");s(this,"_log");this.eventBus=e,this.bindingManager=i,this._log=(t==null?void 0:t.getLogger("EntityManager"))||new _("EntityManager"),this._log.info("EntityManager initialized")}_areActionsCompatible(e,t){return!(e.type!==t.type||e.type==="analog"&&t.type==="analog"&&(t.minValue!==void 0&&e.minValue!==t.minValue||t.maxValue!==void 0&&e.maxValue!==t.maxValue))}_registerEntityActions(e){if(e.actions)for(const t of e.actions)try{const i=this.bindingManager.getAction(t.name);i?this._areActionsCompatible(i,t)?this._log.trace(`Action "${t.name}" already registered with compatible options, skipping registration`):this._log.warn(`Action "${t.name}" already registered with different options. Entity "${e.type}" requires: ${JSON.stringify(t)}, but found: ${JSON.stringify(i)}`):(this._log.debug(`Registering action "${t.name}" from entity type "${e.type}"`),this.bindingManager.registerAction(t))}catch(i){this._log.debug(`Failed to register action "${t.name}": ${i instanceof Error?i.message:String(i)}`)}}$frameUpdate(e){this._log.trace(`Updating ${this.entities.size} entities with dt=${e}`);for(const t of this.entities.values())t.$update(e)}registerEntityDefinition(e){this._log.debug(`Registering entity definition: ${e.type}`),this._registerEntityActions(e),this.entityDefinitions.set(e.type,e)}async createEntity(e,t={}){var r;this._log.debug(`Creating entity of type: ${e}`);const i=this.entityDefinitions.get(e);if(!i){const a=`Entity type ${e} is not registered.`;throw this._log.error(a),new Error(a)}this._log.trace(`Using entity definition with ${((r=i.behaviors)==null?void 0:r.length)||0} behaviors`);let n={...i.defaultState,...t};if(i.onBeforeCreate){this._log.trace(`Calling onBeforeCreate hook for entity type: ${e}`);const a=await i.onBeforeCreate(n);a!==void 0&&(n=a)}const o=new B(i.type,this.eventBus,n,i.behaviors);if(this.entities.set(o.id,o),this._log.debug(`Entity created with ID: ${o.id}`),i.onCreate){this._log.trace(`Calling onCreate hook for entity type: ${e}`);const a=await i.onCreate(o.state);a!==void 0&&(o.state=a)}return o}async destroyEntity(e){this._log.debug(`Destroying entity: ${e}`);const t=this.entities.get(e);if(t){const i=this.entityDefinitions.get(t.type);if(i!=null&&i.onBeforeDestroy){this._log.trace(`Calling onBeforeDestroy hook for entity: ${e}`);const n=await i.onBeforeDestroy(t.state);n!==void 0&&(t.state=n)}await t.$destroy(),i!=null&&i.onDestroy&&(this._log.trace(`Calling onDestroy hook for entity: ${e}`),await i.onDestroy(t.state)),this.entities.delete(e),this._log.debug(`Entity ${e} destroyed`)}else this._log.warn(`Attempted to destroy non-existent entity: ${e}`)}getEntity(e){return this._log.trace(`Getting entity: ${e}`),this.entities.get(e)??null}addEntity(e){this._log.debug(`Adding existing entity: ${e.id} (type: ${e.type})`),this.entities.set(e.id,e)}removeEntity(e){this._log.debug(`Removing entity: ${e}`),this.entities.get(e)?(this.entities.delete(e),this._log.debug(`Entity ${e} removed`)):this._log.warn(`Attempted to remove non-existent entity: ${e}`)}async $teardown(){this._log.info(`Tearing down EntityManager with ${this.entities.size} entities`);const e=[...this.entities.keys()];for(const t of e)try{await this.destroyEntity(t)}catch(i){this._log.error(`Error destroying entity ${t}: ${i}`)}this.entityDefinitions.clear(),this._log.info("EntityManager torn down successfully")}}class W{constructor(){s(this,"_keyboardDevice");s(this,"_keysState",{});s(this,"_onDeviceConnected");s(this,"_onInputChanged");this._keyboardDevice={id:"keyboard-0",name:"Keyboard",type:"keyboard",connected:!0},this._setupKeyboardEvents(),setTimeout(()=>this._notifyDeviceConnected(),0)}onDeviceConnected(e){this._onDeviceConnected=e}onInputChanged(e){this._onInputChanged=e}getState(){return{type:"keyboard",keys:{...this._keysState},delta:{}}}getDevice(){return{...this._keyboardDevice}}$teardown(){return window.removeEventListener("keydown",this._handleKeyDown),window.removeEventListener("keyup",this._handleKeyUp),Promise.resolve()}_setupKeyboardEvents(){this._handleKeyDown=this._handleKeyDown.bind(this),this._handleKeyUp=this._handleKeyUp.bind(this),window.addEventListener("keydown",this._handleKeyDown),window.addEventListener("keyup",this._handleKeyUp)}_handleKeyDown(e){this._keysState[e.code]=!0;const t={};t[e.code]=!0;const i={type:"keyboard",keys:{...this._keysState},delta:t,event:e};this._notifyInputChanged(i)}_handleKeyUp(e){this._keysState[e.code]=!1;const t={};t[e.code]=!1;const i={type:"keyboard",keys:{...this._keysState},delta:t,event:e};this._notifyInputChanged(i)}_notifyDeviceConnected(){this._onDeviceConnected&&this._onDeviceConnected(this._keyboardDevice)}_notifyInputChanged(e){this._onInputChanged&&this._onInputChanged(this._keyboardDevice,e)}}class F{constructor(e=document.body){s(this,"_targetElement");s(this,"_mouseDevice");s(this,"_buttonState",{});s(this,"_axesState",{});s(this,"_position",{absolute:{x:0,y:0},relative:{x:0,y:0}});s(this,"_wheelState",{deltaX:0,deltaY:0,deltaZ:0,deltaMode:0});s(this,"_onDeviceConnected");s(this,"_onInputChanged");this._targetElement=e,this._mouseDevice={id:"mouse-0",name:"Mouse",type:"mouse",connected:!0},this._setupMouseEvents(),this._axesState["axis-x"]=0,this._axesState["axis-y"]=0,setTimeout(()=>this._notifyDeviceConnected(),0)}onDeviceConnected(e){this._onDeviceConnected=e}onInputChanged(e){this._onInputChanged=e}getState(){return{type:"mouse",buttons:{...this._buttonState},axes:{...this._axesState},position:{absolute:{...this._position.absolute},relative:{...this._position.relative}},wheel:{...this._wheelState}}}getDevice(){return{...this._mouseDevice}}$teardown(){return this._targetElement.removeEventListener("mousedown",this._handleMouseDown),this._targetElement.removeEventListener("mouseup",this._handleMouseUp),this._targetElement.removeEventListener("mousemove",this._handleMouseMove),this._targetElement.removeEventListener("wheel",this._handleMouseWheel),Promise.resolve()}_setupMouseEvents(){this._handleMouseDown=this._handleMouseDown.bind(this),this._handleMouseUp=this._handleMouseUp.bind(this),this._handleMouseMove=this._handleMouseMove.bind(this),this._handleMouseWheel=this._handleMouseWheel.bind(this),this._targetElement.addEventListener("mousedown",this._handleMouseDown),this._targetElement.addEventListener("mouseup",this._handleMouseUp),this._targetElement.addEventListener("mousemove",this._handleMouseMove),this._targetElement.addEventListener("wheel",this._handleMouseWheel)}_handleMouseDown(e){const t=`button-${e.button}`,i={pressed:!0};this._buttonState[t]=i,this._notifyInputChanged({type:"mouse",event:e,buttons:{...this._buttonState},axes:{...this._axesState},position:{...this._position}})}_handleMouseUp(e){const t=`button-${e.button}`,i={pressed:!1};this._buttonState[t]=i,this._notifyInputChanged({type:"mouse",event:e,buttons:{...this._buttonState},axes:{...this._axesState},position:{...this._position}})}_handleMouseMove(e){this._position={absolute:{x:e.clientX,y:e.clientY},relative:{x:e.movementX,y:e.movementY}},this._axesState["axis-x"]=e.clientX,this._axesState["axis-y"]=e.clientY,this._notifyInputChanged({type:"mouse",event:e,buttons:{...this._buttonState},axes:{...this._axesState},position:{...this._position}})}_handleMouseWheel(e){this._wheelState={deltaX:e.deltaX,deltaY:e.deltaY,deltaZ:e.deltaZ,deltaMode:e.deltaMode},this._notifyInputChanged({type:"mouse",event:e,buttons:{...this._buttonState},axes:{...this._axesState},position:{...this._position},wheel:{...this._wheelState}})}_notifyDeviceConnected(){this._onDeviceConnected&&this._onDeviceConnected(this._mouseDevice)}_notifyInputChanged(e){this._onInputChanged&&this._onInputChanged(this._mouseDevice,e)}}class N{constructor(){s(this,"_gamepadDevices",{});s(this,"_buttonStates",{});s(this,"_axesStates",{});s(this,"_onDeviceConnected");s(this,"_onDeviceDisconnected");s(this,"_onInputChanged");this._setupGamepadEvents()}onDeviceConnected(e){this._onDeviceConnected=e}onDeviceDisconnected(e){this._onDeviceDisconnected=e}onInputChanged(e){this._onInputChanged=e}getDevices(){return Object.values(this._gamepadDevices).map(e=>({...e}))}getStates(){const e={};return Object.keys(this._buttonStates).forEach(t=>{const i=Number(t),n=this._buttonStates[i]||{},o=this._axesStates[i]||{};e[i]={type:"gamepad",buttons:{...n},axes:{...o}}}),e}getDevice(e){const t=this._gamepadDevices[e];return t?{...t}:null}getState(e){const t=this._buttonStates[e],i=this._axesStates[e];return!t&&!i?null:{type:"gamepad",buttons:t?{...t}:{},axes:i?{...i}:{}}}pollGamepads(){if(!navigator.getGamepads)return;const e=navigator.getGamepads();for(const t of e){if(!t)continue;const i=t.index;if(!this._gamepadDevices[i]){this._handleGamepadConnected(t);continue}const n=this._gamepadDevices[i];if(!n)continue;const o=this._buttonStates[i]||{},r=this._axesStates[i]||{},a={};let u=!1;t.buttons.forEach((g,m)=>{const p=`button-${m}`,v={pressed:g.pressed,touched:g.touched,value:g.value};a[p]=v;const w=o[p];(!w||w.pressed!==v.pressed||w.touched!==v.touched||w.value!==v.value)&&(u=!0)});const l={};let f=!1;if(t.axes.forEach((g,m)=>{const p=`axis-${m}`;l[p]=g,r[p]!==g&&(f=!0)}),this._buttonStates[i]=a,this._axesStates[i]=l,u||f){const g={type:"gamepad",buttons:{...a},axes:{...l}};this._notifyInputChanged(n,g)}}}$teardown(){return window.removeEventListener("gamepadconnected",this._handleGamepadConnected),window.removeEventListener("gamepaddisconnected",this._handleGamepadDisconnected),Promise.resolve()}_setupGamepadEvents(){if(this._handleGamepadConnected=this._handleGamepadConnected.bind(this),this._handleGamepadDisconnected=this._handleGamepadDisconnected.bind(this),window.addEventListener("gamepadconnected",this._handleGamepadConnected),window.addEventListener("gamepaddisconnected",this._handleGamepadDisconnected),navigator.getGamepads){const e=navigator.getGamepads();for(const t of e)t&&this._handleGamepadConnected(t)}}_handleGamepadConnected(e){const t=e instanceof GamepadEvent?e.gamepad:e,i=t.index,n={id:`gamepad-${i}`,name:t.id,type:"gamepad",connected:!0,index:i,mapping:t.mapping,axes:Array.from(t.axes),buttons:Array.from(t.buttons)};this._gamepadDevices[i]=n;const o={};Array.from(t.buttons).forEach((a,u)=>{o[`button-${u}`]={pressed:a.pressed,touched:a.touched,value:a.value}}),this._buttonStates[i]=o;const r={};Array.from(t.axes).forEach((a,u)=>{r[`axis-${u}`]=a}),this._axesStates[i]=r,this._notifyDeviceConnected(n),this._notifyInputChanged(n,{type:"gamepad",buttons:{...o},axes:{...r},event:e instanceof GamepadEvent?e:void 0})}_handleGamepadDisconnected(e){const i=e.gamepad.index,n=this._gamepadDevices[i];n&&(n.connected=!1,this._notifyDeviceDisconnected(n),this._gamepadDevices[i]=void 0,this._buttonStates[i]=void 0,this._axesStates[i]=void 0)}_notifyDeviceConnected(e){this._onDeviceConnected&&this._onDeviceConnected(e)}_notifyDeviceDisconnected(e){this._onDeviceDisconnected&&this._onDeviceDisconnected(e)}_notifyInputChanged(e,t){this._onInputChanged&&this._onInputChanged(e,t)}}class j{constructor(e,t,i){s(this,"_eventBus");s(this,"_keyboardRA");s(this,"_mouseRA");s(this,"_gamepadRA");s(this,"_log");this._eventBus=e,this._log=(i==null?void 0:i.getLogger("UserInputManager"))||new _("UserInputManager"),this._log.info("Initializing UserInputManager"),this._log.debug("Initializing input resource access classes"),this._keyboardRA=new W,this._mouseRA=new F(t),this._gamepadRA=new N,this._log.debug("Registering input event callbacks"),this._keyboardRA.onDeviceConnected(this._publishDeviceConnected.bind(this)),this._keyboardRA.onInputChanged(this._publishInputChanged.bind(this)),this._mouseRA.onDeviceConnected(this._publishDeviceConnected.bind(this)),this._mouseRA.onInputChanged(this._publishInputChanged.bind(this)),this._gamepadRA.onDeviceConnected(this._publishDeviceConnected.bind(this)),this._gamepadRA.onDeviceDisconnected(this._publishDeviceDisconnected.bind(this)),this._gamepadRA.onInputChanged(this._publishInputChanged.bind(this)),this._log.info("UserInputManager initialized successfully")}_publishDeviceConnected(e){this._log.debug(`Device connected: ${e.id} (${e.name})`);const t={type:"input:device:connected",payload:{device:e}};this._eventBus.publish(t)}_publishDeviceDisconnected(e){this._log.debug(`Device disconnected: ${e.id} (${e.name})`);const t={type:"input:device:disconnected",payload:{device:e}};this._eventBus.publish(t)}_publishInputChanged(e,t){this._log.trace(`Input changed: ${e.id}`,t);const i={type:"input:changed",payload:{deviceId:e.id,device:e,state:t}};this._eventBus.publish(i)}$teardown(){return this._log.info("Tearing down UserInputManager"),this._log.debug("Cleaning up input resource access instances"),Promise.all([this._keyboardRA.$teardown(),this._mouseRA.$teardown(),this._gamepadRA.$teardown()]).then(()=>{this._log.info("UserInputManager torn down successfully")})}listDevices(){this._log.debug("Getting all connected input devices");const e=[];return e.push(this._keyboardRA.getDevice()),e.push(this._mouseRA.getDevice()),e.push(...this._gamepadRA.getDevices()),this._log.debug(`Found ${e.length} connected devices`),e}getDevice(e){if(this._log.debug(`Getting device: ${e}`),e.startsWith("keyboard-"))return this._keyboardRA.getDevice();if(e.startsWith("mouse-"))return this._mouseRA.getDevice();if(e.startsWith("gamepad-")){const t=parseInt(e.split("-")[1],10);return this._gamepadRA.getDevice(t)}else{const t=this.listDevices();for(const i of t)if(i.id===e)return i}return this._log.warn(`Device not found: ${e}`),null}pollGamepads(){this._gamepadRA.pollGamepads()}}async function L(c,e){const t=new h.WebGPUEngine(c,e);return await t.initAsync(),t}function C(c,e){return new h.Engine(c,e.antialias,e.options,e.adaptToDeviceRatio)}function J(c){return new h.NullEngine(c)}async function Y(c,e){if(c===null)return console.debug("Using Null Engine"),J(e);const t=e.engine||"auto";if(t==="webgpu")if(V())try{return console.debug("Using forced WebGPU engine"),await L(c,e)}catch(i){throw console.error("Forced WebGPU initialization failed:",i),new Error("Forced WebGPU failed to initialize. If WebGPU is required, check browser compatibility.")}else throw new Error("WebGPU was forced but is not available in this browser.");if(t==="webgl")return console.debug("Using forced WebGL engine"),C(c,e);if(V())try{return console.debug("Using WebGPU"),L(c,e).catch(i=>(console.warn("WebGPU initialization failed, falling back to WebGL:",i),C(c,e)))}catch(i){console.warn("WebGPU initialization failed, falling back to WebGL:",i)}else console.warn("WebGPU not supported, falling back to WebGL.");return console.debug("Using WebGL"),C(c,e)}async function X(){const c=await y();return new h.HavokPlugin(!0,c)}const q=["keyboard","mouse","gamepad"],Z=["trace","debug","info","warn","error","none"];async function Q(c,e,t={}){const i=new A(t.logLevel||"debug"),n=i.getLogger("SAGE");n.info(`Initializing SAGE Game Engine v${b}...`),n.debug("Creating rendering engine...");const o=await Y(c,t.renderOptions||{});n.debug("Creating physics engine...");const r=await X();n.debug("Creating event bus...");const a=new I(i);n.debug("Creating scene engine...");const u=new T(o,r,i);n.debug("Creating managers...");const l=new j(a,c,i),f=new z(a,i),g=new H(a,i,f),m=new P(o,u,g,l,i);n.info(`SAGE Game Engine v${b} initialized successfully.`),n.debug("Loading entities...");for(const p of e)g.registerEntityDefinition(p);if(n.debug("Registering default input bindings..."),t.bindings)for(const p of t.bindings)f.registerBinding(p);return new D(c,o,r,a,i,{sceneEngine:u},{bindingManager:f,entityManager:g,gameManager:m,inputManager:l})}d.ConsoleBackend=k,d.GameEntity=B,d.GameEntityBehavior=O,d.GameEventBus=I,d.LogLevels=Z,d.LoggingUtility=A,d.NullBackend=M,d.SAGELogger=_,d.SkewedAspectGameEngine=D,d.VERSION=b,d.bindingTypes=G,d.createGameEngine=Q,d.validDeviceTypes=q,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"})});
1
+ (function(d,h){typeof exports=="object"&&typeof module<"u"?h(exports,require("@babylonjs/core"),require("@babylonjs/havok")):typeof define=="function"&&define.amd?define(["exports","@babylonjs/core","@babylonjs/havok"],h):(d=typeof globalThis<"u"?globalThis:d||self,h(d.SAGameEngine={},d.BABYLON,d.HavokPhysics))})(this,function(d,h,y){"use strict";var ee=Object.defineProperty;var te=(d,h,y)=>h in d?ee(d,h,{enumerable:!0,configurable:!0,writable:!0,value:y}):d[h]=y;var s=(d,h,y)=>te(d,typeof h!="symbol"?h+"":h,y);const b="0.6.1";class D{constructor(e,t,i,n,o,r,a){s(this,"canvas");s(this,"renderEngine");s(this,"physics");s(this,"managers");s(this,"engines");s(this,"eventBus");s(this,"logger");s(this,"started",!1);s(this,"_log");s(this,"_beforeStartHook",null);s(this,"_onStartHook",null);s(this,"_onTeardownHook",null);this.canvas=e,this.renderEngine=t,this.physics=i,this.eventBus=n,this.logger=o,this.engines=r,this.managers=a,this._log=o.getLogger("GameEngine")}onBeforeStart(e){if(this._beforeStartHook!==null)throw new Error("A beforeStart hook is already registered. Only one hook is allowed per lifecycle event.");this._beforeStartHook=e}onStart(e){if(this._onStartHook!==null)throw new Error("An onStart hook is already registered. Only one hook is allowed per lifecycle event.");this._onStartHook=e}onTeardown(e){if(this._onTeardownHook!==null)throw new Error("An onTeardown hook is already registered. Only one hook is allowed per lifecycle event.");this._onTeardownHook=e}async start(){this.started?this._log.warn("Game engine is already started. Skipping start."):(this._log.info(`Starting SkewedAspect Game Engine (Version: ${b})...`),this._beforeStartHook&&(this._log.debug("Executing beforeStart hook..."),await this._beforeStartHook(this)),await this.managers.gameManager.start(this.canvas),this._log.info("Game engine started successfully"),this._onStartHook&&(this._log.debug("Executing onStart hook..."),await this._onStartHook(this)),this.started=!0)}async stop(){if(this.started){this._log.info(`Stopping SkewedAspect Game Engine (Version: ${b})...`);let e=null;if(this._onTeardownHook)try{this._log.debug("Executing onTeardown hook..."),await this._onTeardownHook(this)}catch(t){this._log.error(`Error in onTeardown hook: ${t}`),e=e||t}for(const t of Object.keys(this.managers)){const i=this.managers[t];if(typeof i.$teardown=="function")try{this._log.debug(`Tearing down manager: ${t}`),await i.$teardown()}catch(n){this._log.error(`Error tearing down manager ${t}: ${n}`),e=e||n}}for(const t of Object.keys(this.engines)){const i=this.engines[t];if(typeof i.$teardown=="function")try{this._log.debug(`Tearing down engine: ${t}`),await i.$teardown()}catch(n){this._log.error(`Error tearing down engine ${t}: ${n}`),e=e||n}}try{await this.managers.gameManager.stop()}catch(t){this._log.error(`Error stopping game manager: ${t}`),e=e||t}if(this._log.info("Game engine stopped successfully"),e)throw e}else this._log.warn("Game engine is not started. Skipping stop.")}}class k{constructor(){s(this,"timers");this.timers=new Map}getStyleForLevel(e){const t={trace:"color: #999999",debug:"color: #00AAAA",info:"color: #00AA00",warn:"color: #AAAA00",error:"color: #AA0000",timer:"color: #AA00AA",none:"color: inherit"};return t[e]||t.none}formatMessage(e,t){const i=new Date,n=i.getHours()%12||12,o=i.getMinutes().toString().padStart(2,"0"),r=i.getSeconds().toString().padStart(2,"0"),a=i.getHours()>=12?"PM":"AM",u=`[${n}:${o}:${r} ${a}]`,l=t.toUpperCase();return[`${u} %c${l}%c (${e}): `,this.getStyleForLevel(t),"",""]}trace(e,t,...i){const[n,o,r,a]=this.formatMessage(e,"trace");console.debug(n,o,r,a,t,...i)}debug(e,t,...i){const[n,o,r,a]=this.formatMessage(e,"debug");console.debug(n,o,r,a,t,...i)}info(e,t,...i){const[n,o,r,a]=this.formatMessage(e,"info");console.info(n,o,r,a,t,...i)}warn(e,t,...i){const[n,o,r,a]=this.formatMessage(e,"warn");console.warn(n,o,r,a,t,...i)}error(e,t,...i){const[n,o,r,a]=this.formatMessage(e,"error");console.error(n,o,r,a,t,...i)}time(e,t){const i=`${e}:${t}`;this.timers.set(i,performance.now())}timeEnd(e,t){const i=`${e}:${t}`,n=this.timers.get(i);if(n!==void 0){const o=performance.now()-n;this.timers.delete(i);const[r,a,u,l]=this.formatMessage(e,"timer");console.info(`${r} Timer '${t}' completed in ${o.toFixed(2)}ms`,a,u,l)}else console.warn(`[${e}]: Timer '${t}' does not exist`)}}class M{trace(e,t,...i){}debug(e,t,...i){}info(e,t,...i){}warn(e,t,...i){}error(e,t,...i){}time(e,t){}timeEnd(e,t){}}class _{constructor(e,t="none",i=new M){s(this,"category");s(this,"backend");s(this,"minLevel");this.category=e,this.backend=i,this.minLevel=t}updateSettings(e,t){this.backend=e,this.minLevel=t}trace(e,...t){this.shouldLog("trace")&&this.backend.trace(this.category,e,...t)}debug(e,...t){this.shouldLog("debug")&&this.backend.debug(this.category,e,...t)}info(e,...t){this.shouldLog("info")&&this.backend.info(this.category,e,...t)}warn(e,...t){this.shouldLog("warn")&&this.backend.warn(this.category,e,...t)}error(e,...t){this.shouldLog("error")&&this.backend.error(this.category,e,...t)}time(e){this.minLevel!=="none"&&this.backend.time(this.category,e)}timeEnd(e){this.minLevel!=="none"&&this.backend.timeEnd(this.category,e)}shouldLog(e){if(this.minLevel==="none")return!1;switch(this.minLevel){case"trace":return!0;case"debug":return e!=="trace";case"info":return e==="info"||e==="warn"||e==="error";case"warn":return e==="warn"||e==="error";case"error":return e==="error";default:return!1}}}class A{constructor(e="debug",t=new k){s(this,"backend");s(this,"level");s(this,"loggers");this.backend=t,this.level=e,this.loggers=new Map}setBackend(e){this.backend=e,this.loggers.forEach(t=>{t instanceof _&&t.updateSettings(this.backend,this.level)})}setLevel(e){this.level=e,this.loggers.forEach(t=>{t instanceof _&&t.updateSettings(this.backend,this.level)})}getLevel(){return this.level}getLogger(e){this.loggers.has(e)||this.loggers.set(e,new _(e,this.level,this.backend));const t=this.loggers.get(e);if(t===void 0)throw new Error(`Failed to create logger for category: ${e}`);return t}}class I{constructor(e){s(this,"directMap",new Map);s(this,"patternSubs",new Set);s(this,"_log");this._log=(e==null?void 0:e.getLogger("EventBus"))||new _("EventBus")}subscribe(e,t){return this._log.trace(`Subscribe request for: ${e.toString()}`),e instanceof RegExp||typeof e=="string"&&e.includes("*")?this.subscribePattern(e,t):this.subscribeExact(e,t)}subscribeExact(e,t){this._log.debug(`Adding exact subscription for: ${e}`);let i=this.directMap.get(e);i||(i=new Set,this.directMap.set(e,i));const n={callback:o=>t(o)};return i.add(n),()=>{this._log.debug(`Removing exact subscription for: ${e}`),i.delete(n),i.size===0&&this.directMap.delete(e)}}subscribePattern(e,t){let i;if(typeof e=="string"){const o=e.replace(/[-/\\^$+?.()|[\]{}]/g,"\\$&").replace(/\*/g,".*");i=new RegExp(`^${o}$`),this._log.debug(`Adding pattern subscription for string: ${e}, regex: ${i.toString()}`)}else i=e,this._log.debug(`Adding pattern subscription for regex: ${i.toString()}`);const n={pattern:i,callback:o=>t(o)};return this.patternSubs.add(n),()=>{this._log.debug(`Removing pattern subscription: ${i.toString()}`),this.patternSubs.delete(n)}}publish(e){this._log.trace(`Publishing event: ${e.type}`,e);let t=0;const i=this.directMap.get(e.type);if(i){t+=i.size;for(const n of i)Promise.resolve().then(()=>n.callback(e))}for(const n of this.patternSubs)n.pattern&&n.pattern.test(e.type)&&(t++,Promise.resolve().then(()=>n.callback(e)));t===0?this._log.debug(`No subscribers found for event: ${e.type}`):this._log.trace(`Event ${e.type} dispatched to ${t} subscribers`)}}class T{constructor(e,t,i){s(this,"_engine");s(this,"_physics");s(this,"_log");this._engine=e,this._physics=t,this._log=(i==null?void 0:i.getLogger("SceneEngine"))||new _("SceneEngine")}async _buildDemoScene(e,t){this._log.debug("Building demo scene..."),this._log.trace("Creating camera...");const i=new h.FreeCamera("camera1",new h.Vector3(0,5,-10),e);i.setTarget(h.Vector3.Zero()),i.attachControl(t,!0),this._log.trace("Creating light...");const n=new h.HemisphericLight("light",new h.Vector3(0,1,0),e);n.intensity=.7,this._log.trace("Creating sphere...");const o=h.MeshBuilder.CreateSphere("sphere",{diameter:2,segments:32},e);o.position.y=4,this._log.trace("Creating ground...");const r=h.MeshBuilder.CreateGround("ground",{width:10,height:10},e);this._log.trace("Adding physics to sphere..."),new h.PhysicsAggregate(o,h.PhysicsShapeType.SPHERE,{mass:1,restitution:.75},e),this._log.trace("Adding physics to ground..."),new h.PhysicsAggregate(r,h.PhysicsShapeType.BOX,{mass:0},e),this._log.debug("Demo scene built successfully")}async loadScene(e){this._log.info("Loading scene..."),this._log.time("sceneLoad"),this._log.debug("Creating new scene...");const t=new h.Scene(this._engine);return this._log.debug("Enabling physics with gravity (0, -9.8, 0)..."),t.enablePhysics(new h.Vector3(0,-9.8,0),this._physics),await this._buildDemoScene(t,e),this._log.timeEnd("sceneLoad"),this._log.info("Scene loaded successfully"),t}async $teardown(){return this._log.info("Tearing down SceneEngine"),this._log.info("SceneEngine torn down successfully"),Promise.resolve()}}const G=["trigger","toggle","value"];class K{constructor(e,t,i,n={}){s(this,"type","trigger");s(this,"action");s(this,"context");s(this,"deviceID");s(this,"reader");s(this,"_edgeMode");s(this,"_threshold");s(this,"_lastDigitalState",!1);this.action=e,this.deviceID=t,this.reader=i,this.context=n.context,this._edgeMode=n.edgeMode??"rising",this._threshold=n.threshold??.5}get options(){return{edgeMode:this._edgeMode,threshold:this._threshold}}process(e,t){const i=this.reader.getValue(e)??!1,n=typeof i=="boolean"?i:i>=this._threshold;let o=!1;switch(this._edgeMode){case"rising":o=n&&!this._lastDigitalState;break;case"falling":o=!n&&this._lastDigitalState;break;case"both":o=n!==this._lastDigitalState;break}if(this._lastDigitalState=n,o){let r;if(this.action.type==="analog"){let u=typeof i=="number"?i:i?1:0;if(this.action.minValue!==void 0||this.action.maxValue!==void 0){const l=this.action.minValue??0,f=this.action.maxValue??1;u=l+u*(f-l)}r=u}else r=!0;const a={type:`action:${this.action.name}`,payload:{value:r,deviceId:this.deviceID,context:this.context}};t.publish(a)}}toJSON(){return{type:this.type,action:this.action.name,input:{deviceID:this.deviceID,...this.reader.toJSON()},context:this.context,options:this.options}}}class U{constructor(e,t,i,n={}){s(this,"type","toggle");s(this,"action");s(this,"context");s(this,"deviceID");s(this,"reader");s(this,"_invert");s(this,"_threshold");s(this,"_initialState");s(this,"_onValue");s(this,"_offValue");s(this,"_lastDigitalState",!1);s(this,"_toggleState");this.action=e,this.deviceID=t,this.reader=i,this.context=n.context,this._invert=n.invert??!1,this._threshold=n.threshold??.5,this._initialState=n.initialState??!1,this._toggleState=this._initialState,this._onValue=n.onValue??!0,this._offValue=n.offValue??!1}get state(){return this._toggleState}set state(e){this._toggleState=e}get onValue(){return this._onValue}get offValue(){return this._offValue}get value(){return this.action.type==="analog"?this._toggleState?this.action.maxValue??1:this.action.minValue??0:this._toggleState?this._onValue:this._offValue}get options(){return{invert:this._invert,threshold:this._threshold,initialState:this._initialState,onValue:this._onValue,offValue:this._offValue}}process(e,t){const i=this.reader.getValue(e)??!1,n=typeof i=="boolean"?i:i>=this._threshold,o=this._invert?!n&&this._lastDigitalState:n&&!this._lastDigitalState;this._lastDigitalState=n,o&&(this._toggleState=!this._toggleState,t.publish({type:`action:${this.action.name}`,payload:{value:this.value,deviceId:this.deviceID,context:this.context}}))}reset(){this._toggleState=this._initialState}toJSON(){return{type:this.type,action:this.action.name,input:{deviceID:this.deviceID,...this.reader.toJSON()},state:this._toggleState,context:this.context,options:this.options}}}class R{constructor(e,t,i,n={}){s(this,"type","value");s(this,"action");s(this,"context");s(this,"deviceID");s(this,"reader");s(this,"_scale");s(this,"_offset");s(this,"_invert");s(this,"_emitOnChange");s(this,"_deadzone");s(this,"_onValue");s(this,"_offValue");s(this,"_min");s(this,"_max");s(this,"_lastValue");this.action=e,this.deviceID=t,this.reader=i,this.context=n.context,this._scale=n.scale??1,this._offset=n.offset??0,this._invert=n.invert??!1,this._emitOnChange=n.emitOnChange??!0,this._deadzone=n.deadzone??0,this._onValue=n.onValue??!0,this._offValue=n.offValue??!1;const o=this.action.type==="analog"?this.action.minValue??Number.NEGATIVE_INFINITY:Number.NEGATIVE_INFINITY;this._min=n.min??o;const r=this.action.type==="analog"?this.action.maxValue??Number.POSITIVE_INFINITY:Number.POSITIVE_INFINITY;this._max=n.max??r}get options(){return{scale:this._scale,offset:this._offset,invert:this._invert,emitOnChange:this._emitOnChange,deadzone:this._deadzone,onValue:this._onValue,offValue:this._offValue,min:this._min,max:this._max}}process(e,t){const i=this.reader.getValue(e);if(i===void 0)return;const n=typeof i=="boolean"?i?1:0:i;if(n===void 0)return;let o=this._deadzone>0&&Math.abs(n)<this._deadzone?0:n;o=(this._invert?-o:o)*this._scale+this._offset,o=Math.max(this._min,Math.min(this._max,o)),!(this._emitOnChange&&this._lastValue===o)&&(this._lastValue=o,t.publish({type:`action:${this.action.name}`,payload:{value:o,deviceId:this.deviceID,context:this.context}}))}toJSON(){return{type:this.type,action:this.action.name,input:{deviceID:this.deviceID,...this.reader.toJSON()},context:this.context,options:this.options}}}class S{constructor(e,t={}){s(this,"sourceType","key");s(this,"sourceKey");s(this,"useDelta");this.sourceKey=e,this.useDelta=t.useDelta||!1}getValue(e){if(e.type!=="keyboard")return;const t=e;return this.useDelta?t.delta[this.sourceKey]:t.keys[this.sourceKey]}static fromString(e,t={}){return new S(e,t)}toJSON(){return{type:"keyboard",sourceType:this.sourceType,sourceKey:this.sourceKey,options:{useDelta:this.useDelta}}}}class x{constructor(e,t){s(this,"sourceType");s(this,"sourceKey");this.sourceType=e,this.sourceKey=t}getValue(e){if(e.type==="mouse")switch(this.sourceType){case"button":{const t=e.buttons[this.sourceKey];return t?t.pressed:void 0}case"position":{const[t,i]=this.sourceKey.split(":");return!t||!i||t!=="absolute"&&t!=="relative"||i!=="x"&&i!=="y"?void 0:e.position[t][i]}case"wheel":{const t=this.sourceKey==="deltaX"||this.sourceKey==="deltaY"||this.sourceKey==="deltaZ";return e.wheel&&t?e.wheel[this.sourceKey]:void 0}default:return}}static fromString(e){const[t,...i]=e.split(":"),n=i.join(":");if(!t||!n)throw new Error(`Invalid mouse source format: ${e}`);return new x(t,n)}toJSON(){return{type:"mouse",sourceType:this.sourceType,sourceKey:this.sourceKey}}}class E{constructor(e,t,i={}){s(this,"sourceType");s(this,"sourceKey");s(this,"useAnalogValue");s(this,"deadzone");s(this,"invert");this.sourceType=e,this.sourceKey=t,this.useAnalogValue=i.useAnalogValue||!1,this.deadzone=i.deadzone||.1,this.invert=i.invert||!1}getValue(e){if(e.type==="gamepad")switch(this.sourceType){case"button":{const t=e.buttons[this.sourceKey];return t?this.useAnalogValue?t.value:t.pressed:void 0}case"axis":{let t=e.axes[this.sourceKey];return t===void 0?void 0:(Math.abs(t)<this.deadzone&&(t=0),this.invert?-t:t)}default:return}}static fromString(e,t={}){const[i,n]=e.split(":");if(!i||!n)throw new Error(`Invalid gamepad source format: ${e}`);return new E(i,n,t)}toJSON(){return{type:"gamepad",sourceType:this.sourceType,sourceKey:this.sourceKey,options:{useAnalogValue:this.useAnalogValue,deadzone:this.deadzone,invert:this.invert}}}}class z{constructor(e,t){s(this,"_bindings",new Map);s(this,"_actions",new Map);s(this,"_contexts",new Map);s(this,"_activeContexts",new Set);s(this,"_eventBus");s(this,"_log");this._eventBus=e,this._eventBus.subscribe("input:changed",i=>{i.payload&&this.$handleInput(i.payload.device,i.payload.state)}),this._log=(t==null?void 0:t.getLogger("BindingManager"))||new _("BindingManager"),this._log.debug("BindingManager initialized")}_isBindingContextActive(e){return e.context?this._activeContexts.has(e.context):!0}_getOrCreateContext(e,t=!0){let i=this._contexts.get(e);return i||(i={name:e,exclusive:t},this._contexts.set(e,i),this._log.debug(`Auto-created context "${e}" (exclusive: ${t})`)),i}_deactivateExclusiveContexts(e){const t=[];for(const i of this._activeContexts){if(i===e)continue;const n=this._contexts.get(i);n!=null&&n.exclusive&&(this._activeContexts.delete(i),t.push(i))}return t}_createBindingFromDefinition(e){const t=this._actions.get(e.action);if(!t)return this._log.warn(`Cannot create binding: Action "${e.action}" not found.`),null;const{deviceID:i,...n}=e.input;switch(e.type){case"trigger":return new K(t,i,this._createInputSourceFromDefinition(n),{...e.options||{},context:e.context});case"toggle":return new U(t,i,this._createInputSourceFromDefinition(n),{...e.options||{},context:e.context});case"value":return new R(t,i,this._createInputSourceFromDefinition(n),{...e.options||{},context:e.context});default:return this._log.error(`Binding type not implemented: ${e.type}`),null}}_createInputSourceFromDefinition(e){switch(e.type){case"keyboard":return new S(e.sourceKey,e.options);case"mouse":{const t=e.sourceType;if(!(t==="button"||t==="position"||t==="wheel"))throw new Error(`Invalid mouse source type: ${t}`);return new x(t,e.sourceKey)}case"gamepad":{const t=e.sourceType;if(!(t==="button"||t==="axis"))throw new Error(`Invalid gamepad source type: ${t}`);return new E(t,e.sourceKey,e.options)}default:throw new Error(`Unsupported input source type: ${e.type}`)}}$handleInput(e,t){const i=this._bindings.get(e.id);if(!(!i||i.length===0)&&!(this._activeContexts.size===0&&i.some(n=>n.context)))for(const n of i)this._isBindingContextActive(n)&&n.process(t,this._eventBus)}registerAction(e){if(this._log.debug(`Registering action "${e.name}"`),this._actions.has(e.name)){const t=`Action "${e.name}" already registered.`;throw this._log.error(t),new Error(t)}this._actions.set(e.name,e),this._log.debug(`Action "${e.name}" registered successfully`)}getAction(e){this._log.trace(`Getting action "${e}"`);const t=this._actions.get(e)??null;return t||this._log.debug(`Action "${e}" not found`),t}registerContext(e,t=!0){const i=this._getOrCreateContext(e,t);return i.exclusive!==t&&(i.exclusive=t,this._log.info(`Updated context "${e}" exclusivity: ${t}`)),i}activateContext(e){const i=this._getOrCreateContext(e).exclusive;this._log.debug(`Activating context "${e}" (exclusive: ${i})`);const n=this._activeContexts.has(e);if(i){const o=this._deactivateExclusiveContexts(e);o.length>0&&this._log.info(`Deactivated exclusive contexts: ${o.join(", ")}`)}n||(this._activeContexts.add(e),this._log.info(`Context "${e}" activated${i?" as exclusive":""}`))}deactivateContext(e){this._log.debug(`Deactivating context "${e}"`),this._activeContexts.has(e)?(this._activeContexts.delete(e),this._log.info(`Context "${e}" deactivated`)):this._log.debug(`Context "${e}" was not active`)}getActiveContexts(){return[...this._activeContexts]}isContextActive(e){return this._activeContexts.has(e)}getContext(e){return this._contexts.get(e)||null}$registerBinding(e){var t;if(!G.includes(e.type))throw new Error(`Invalid binding type: ${e.type}`);e.context&&!this._contexts.has(e.context)&&this.registerContext(e.context),this._bindings.has(e.deviceID)||this._bindings.set(e.deviceID,[]),(t=this._bindings.get(e.deviceID))==null||t.push(e),this._log.debug(`Registered ${e.type} binding for "${e.action.name}" in context "${e.context||null}"`)}registerBinding(e){const t=this._createBindingFromDefinition(e);t?this.$registerBinding(t):this._log.error(`Failed to create binding for action "${e.action}" with type "${e.type}"`)}unregisterBindings(e,t=null){this._log.debug(`Unregistering all bindings for action "${e}" in context "${t}"`);let i=0;for(const[n,o]of this._bindings.entries()){const r=o.filter(a=>{const u=a.context||null,l=a.action.name!==e||u!==t;return l||i++,l});r.length!==o.length&&this._bindings.set(n,r)}this._log.info(`Removed ${i} bindings for action "${e}" in context "${t}"`)}getBindingsForAction(e,t){const i=[];for(const n of this._bindings.values())for(const o of n){const r=o.context||null;o.action.name===e&&(!t||r===t)&&i.push(o)}return i}exportConfiguration(){this._log.debug("Exporting binding configuration");const e=[...this._actions.values()].map(n=>n.type==="analog"?{name:n.name,type:n.type,minValue:n.minValue??0,maxValue:n.maxValue??1}:n),t=[];for(const n of this._bindings.values())for(const o of n)t.push(o.toJSON());const i=[...this._contexts.values()].map(n=>({name:n.name,exclusive:n.exclusive,active:this._activeContexts.has(n.name)}));return this._log.info(`Configuration exported: ${e.length} actions, ${t.length} bindings, ${i.length} contexts`),{actions:e,bindings:t,contexts:i}}importConfiguration(e){this._log.debug("Importing binding configuration"),this._bindings.clear(),this._actions.clear(),this._contexts.clear(),this._activeContexts.clear();for(const t of e.actions)this.registerAction(t);for(const t of e.contexts)this.registerContext(t.name,t.exclusive),t.active&&this.activateContext(t.name);for(const t of e.bindings)try{this.registerBinding(t)}catch(i){i instanceof Error&&this._log.error(`Failed to import binding for action "${t.action}": ${i.message}`)}this._log.info(`Configuration imported: ${e.actions.length} actions, ${e.bindings.length} bindings, ${e.contexts.length} contexts`)}async $teardown(){return this._log.info("Tearing down BindingManager"),this._bindings.clear(),this._actions.clear(),this._contexts.clear(),this._activeContexts.clear(),this._log.info("BindingManager torn down successfully"),Promise.resolve()}}function $(){return typeof window<"u"&&typeof window.document<"u"}function V(){return $()&&!!window.navigator.gpu}class P{constructor(e,t,i,n,o){s(this,"_engine");s(this,"_entityManager");s(this,"_inputManager");s(this,"_sceneEngine");s(this,"_currentScene",null);s(this,"_log");s(this,"_boundResizeHandler");s(this,"started",!1);this._engine=e,this._sceneEngine=t,this._entityManager=i,this._inputManager=n,this._log=(o==null?void 0:o.getLogger("GameManager"))||new _("GameManager"),this._boundResizeHandler=this._resizeHandler.bind(this),$()&&window.addEventListener("resize",this._boundResizeHandler)}_renderLoop(){const e=this._engine.getDeltaTime();this._entityManager.$frameUpdate(e),this._inputManager&&this._inputManager.pollGamepads(),this._currentScene&&this._currentScene.render()}_resizeHandler(){this.started&&this._engine.resize()}async start(e){this._currentScene=await this._sceneEngine.loadScene(e),this._engine.runRenderLoop(this._renderLoop.bind(this)),this.started=!0,this._log.info("SkewedAspect Game Engine started successfully")}async stop(){this.started=!1,this._engine.stopRenderLoop(),this._log.info("SkewedAspect Game Engine stopped successfully")}async $teardown(){this._log.info("Tearing down GameManager"),this.started&&await this.stop(),$()&&window.removeEventListener("resize",this._boundResizeHandler),this._log.info("GameManager torn down successfully")}}class O{constructor(){s(this,"entity",null)}$emit(e){if(!this.entity)throw new Error("Entity is not set for this behavior.");e.senderID=this.entity.id,this.entity.eventBus.publish(e)}$setEntity(e){this.entity=e}}class B{constructor(e,t,i,n){s(this,"id");s(this,"type");s(this,"state");s(this,"behaviors",new Map);s(this,"eventBus");s(this,"subscriptions",new Map);this.id=crypto.randomUUID(),this.type=e,this.state=i,this.eventBus=t;for(const o of n)this.attachBehavior(new o)}async $processEvent(e){for(const t of this.behaviors.values())if(await t.processEvent(e,this.state))break}$update(e){var t;for(const i of this.behaviors.values())(t=i.update)==null||t.call(i,e,this.state)}async $destroy(){var e;for(const t of this.behaviors.values())(e=t.destroy)==null||e.call(t);for(const t of this.subscriptions.values())t.unsubscribe();this.behaviors.clear(),this.subscriptions.clear()}attachBehavior(e){if(this.behaviors.has(e.name))throw new Error(`Behavior ${e.name} is already attached to this entity.`);for(const t of e.eventSubscriptions){const i=this.subscriptions.get(t);if(i)i.count++;else{const n=this.eventBus.subscribe(t,this.$processEvent.bind(this));this.subscriptions.set(t,{count:1,unsubscribe:n})}}this.behaviors.set(e.name,e),e.$setEntity(this)}detachBehavior(e){const t=this.behaviors.get(e);if(t){for(const i of t.eventSubscriptions){const n=this.subscriptions.get(i);n&&(n.count--,n.count<=0&&(n.unsubscribe(),this.subscriptions.delete(i)))}this.behaviors.delete(t.name),t.$setEntity(null)}}}class H{constructor(e,t,i){s(this,"eventBus");s(this,"entities",new Map);s(this,"entityDefinitions",new Map);s(this,"bindingManager");s(this,"_log");this.eventBus=e,this.bindingManager=i,this._log=(t==null?void 0:t.getLogger("EntityManager"))||new _("EntityManager"),this._log.info("EntityManager initialized")}_areActionsCompatible(e,t){return!(e.type!==t.type||e.type==="analog"&&t.type==="analog"&&(t.minValue!==void 0&&e.minValue!==t.minValue||t.maxValue!==void 0&&e.maxValue!==t.maxValue))}_registerEntityActions(e){if(e.actions)for(const t of e.actions)try{const i=this.bindingManager.getAction(t.name);i?this._areActionsCompatible(i,t)?this._log.trace(`Action "${t.name}" already registered with compatible options, skipping registration`):this._log.warn(`Action "${t.name}" already registered with different options. Entity "${e.type}" requires: ${JSON.stringify(t)}, but found: ${JSON.stringify(i)}`):(this._log.debug(`Registering action "${t.name}" from entity type "${e.type}"`),this.bindingManager.registerAction(t))}catch(i){this._log.debug(`Failed to register action "${t.name}": ${i instanceof Error?i.message:String(i)}`)}}$frameUpdate(e){this._log.trace(`Updating ${this.entities.size} entities with dt=${e}`);for(const t of this.entities.values())t.$update(e)}registerEntityDefinition(e){this._log.debug(`Registering entity definition: ${e.type}`),this._registerEntityActions(e),this.entityDefinitions.set(e.type,e)}async createEntity(e,t={}){var r;this._log.debug(`Creating entity of type: ${e}`);const i=this.entityDefinitions.get(e);if(!i){const a=`Entity type ${e} is not registered.`;throw this._log.error(a),new Error(a)}this._log.trace(`Using entity definition with ${((r=i.behaviors)==null?void 0:r.length)||0} behaviors`);let n={...i.defaultState,...t};if(i.onBeforeCreate){this._log.trace(`Calling onBeforeCreate hook for entity type: ${e}`);const a=await i.onBeforeCreate(n);a!==void 0&&(n=a)}const o=new B(i.type,this.eventBus,n,i.behaviors);if(this.entities.set(o.id,o),this._log.debug(`Entity created with ID: ${o.id}`),i.onCreate){this._log.trace(`Calling onCreate hook for entity type: ${e}`);const a=await i.onCreate(o.state);a!==void 0&&(o.state=a)}return o}async destroyEntity(e){this._log.debug(`Destroying entity: ${e}`);const t=this.entities.get(e);if(t){const i=this.entityDefinitions.get(t.type);if(i!=null&&i.onBeforeDestroy){this._log.trace(`Calling onBeforeDestroy hook for entity: ${e}`);const n=await i.onBeforeDestroy(t.state);n!==void 0&&(t.state=n)}await t.$destroy(),i!=null&&i.onDestroy&&(this._log.trace(`Calling onDestroy hook for entity: ${e}`),await i.onDestroy(t.state)),this.entities.delete(e),this._log.debug(`Entity ${e} destroyed`)}else this._log.warn(`Attempted to destroy non-existent entity: ${e}`)}getEntity(e){return this._log.trace(`Getting entity: ${e}`),this.entities.get(e)??null}addEntity(e){this._log.debug(`Adding existing entity: ${e.id} (type: ${e.type})`),this.entities.set(e.id,e)}removeEntity(e){this._log.debug(`Removing entity: ${e}`),this.entities.get(e)?(this.entities.delete(e),this._log.debug(`Entity ${e} removed`)):this._log.warn(`Attempted to remove non-existent entity: ${e}`)}async $teardown(){this._log.info(`Tearing down EntityManager with ${this.entities.size} entities`);const e=[...this.entities.keys()];for(const t of e)try{await this.destroyEntity(t)}catch(i){this._log.error(`Error destroying entity ${t}: ${i}`)}this.entityDefinitions.clear(),this._log.info("EntityManager torn down successfully")}}class W{constructor(){s(this,"_keyboardDevice");s(this,"_keysState",{});s(this,"_onDeviceConnected");s(this,"_onInputChanged");this._keyboardDevice={id:"keyboard-0",name:"Keyboard",type:"keyboard",connected:!0},this._setupKeyboardEvents(),setTimeout(()=>this._notifyDeviceConnected(),0)}onDeviceConnected(e){this._onDeviceConnected=e}onInputChanged(e){this._onInputChanged=e}getState(){return{type:"keyboard",keys:{...this._keysState},delta:{}}}getDevice(){return{...this._keyboardDevice}}$teardown(){return window.removeEventListener("keydown",this._handleKeyDown),window.removeEventListener("keyup",this._handleKeyUp),Promise.resolve()}_setupKeyboardEvents(){this._handleKeyDown=this._handleKeyDown.bind(this),this._handleKeyUp=this._handleKeyUp.bind(this),window.addEventListener("keydown",this._handleKeyDown),window.addEventListener("keyup",this._handleKeyUp)}_handleKeyDown(e){this._keysState[e.code]=!0;const t={};t[e.code]=!0;const i={type:"keyboard",keys:{...this._keysState},delta:t,event:e};this._notifyInputChanged(i)}_handleKeyUp(e){this._keysState[e.code]=!1;const t={};t[e.code]=!1;const i={type:"keyboard",keys:{...this._keysState},delta:t,event:e};this._notifyInputChanged(i)}_notifyDeviceConnected(){this._onDeviceConnected&&this._onDeviceConnected(this._keyboardDevice)}_notifyInputChanged(e){this._onInputChanged&&this._onInputChanged(this._keyboardDevice,e)}}class F{constructor(e=document.body){s(this,"_targetElement");s(this,"_mouseDevice");s(this,"_buttonState",{});s(this,"_axesState",{});s(this,"_position",{absolute:{x:0,y:0},relative:{x:0,y:0}});s(this,"_wheelState",{deltaX:0,deltaY:0,deltaZ:0,deltaMode:0});s(this,"_onDeviceConnected");s(this,"_onInputChanged");this._targetElement=e,this._mouseDevice={id:"mouse-0",name:"Mouse",type:"mouse",connected:!0},this._setupMouseEvents(),this._axesState["axis-x"]=0,this._axesState["axis-y"]=0,setTimeout(()=>this._notifyDeviceConnected(),0)}onDeviceConnected(e){this._onDeviceConnected=e}onInputChanged(e){this._onInputChanged=e}getState(){return{type:"mouse",buttons:{...this._buttonState},axes:{...this._axesState},position:{absolute:{...this._position.absolute},relative:{...this._position.relative}},wheel:{...this._wheelState}}}getDevice(){return{...this._mouseDevice}}$teardown(){return this._targetElement.removeEventListener("mousedown",this._handleMouseDown),this._targetElement.removeEventListener("mouseup",this._handleMouseUp),this._targetElement.removeEventListener("mousemove",this._handleMouseMove),this._targetElement.removeEventListener("wheel",this._handleMouseWheel),Promise.resolve()}_setupMouseEvents(){this._handleMouseDown=this._handleMouseDown.bind(this),this._handleMouseUp=this._handleMouseUp.bind(this),this._handleMouseMove=this._handleMouseMove.bind(this),this._handleMouseWheel=this._handleMouseWheel.bind(this),this._targetElement.addEventListener("mousedown",this._handleMouseDown),this._targetElement.addEventListener("mouseup",this._handleMouseUp),this._targetElement.addEventListener("mousemove",this._handleMouseMove),this._targetElement.addEventListener("wheel",this._handleMouseWheel)}_handleMouseDown(e){const t=`button-${e.button}`,i={pressed:!0};this._buttonState[t]=i,this._notifyInputChanged({type:"mouse",event:e,buttons:{...this._buttonState},axes:{...this._axesState},position:{...this._position}})}_handleMouseUp(e){const t=`button-${e.button}`,i={pressed:!1};this._buttonState[t]=i,this._notifyInputChanged({type:"mouse",event:e,buttons:{...this._buttonState},axes:{...this._axesState},position:{...this._position}})}_handleMouseMove(e){this._position={absolute:{x:e.clientX,y:e.clientY},relative:{x:e.movementX,y:e.movementY}},this._axesState["axis-x"]=e.clientX,this._axesState["axis-y"]=e.clientY,this._notifyInputChanged({type:"mouse",event:e,buttons:{...this._buttonState},axes:{...this._axesState},position:{...this._position}})}_handleMouseWheel(e){this._wheelState={deltaX:e.deltaX,deltaY:e.deltaY,deltaZ:e.deltaZ,deltaMode:e.deltaMode},this._notifyInputChanged({type:"mouse",event:e,buttons:{...this._buttonState},axes:{...this._axesState},position:{...this._position},wheel:{...this._wheelState}})}_notifyDeviceConnected(){this._onDeviceConnected&&this._onDeviceConnected(this._mouseDevice)}_notifyInputChanged(e){this._onInputChanged&&this._onInputChanged(this._mouseDevice,e)}}class N{constructor(){s(this,"_gamepadDevices",{});s(this,"_buttonStates",{});s(this,"_axesStates",{});s(this,"_onDeviceConnected");s(this,"_onDeviceDisconnected");s(this,"_onInputChanged");this._setupGamepadEvents()}onDeviceConnected(e){this._onDeviceConnected=e}onDeviceDisconnected(e){this._onDeviceDisconnected=e}onInputChanged(e){this._onInputChanged=e}getDevices(){return Object.values(this._gamepadDevices).map(e=>({...e}))}getStates(){const e={};return Object.keys(this._buttonStates).forEach(t=>{const i=Number(t),n=this._buttonStates[i]||{},o=this._axesStates[i]||{};e[i]={type:"gamepad",buttons:{...n},axes:{...o}}}),e}getDevice(e){const t=this._gamepadDevices[e];return t?{...t}:null}getState(e){const t=this._buttonStates[e],i=this._axesStates[e];return!t&&!i?null:{type:"gamepad",buttons:t?{...t}:{},axes:i?{...i}:{}}}pollGamepads(){if(!navigator.getGamepads)return;const e=navigator.getGamepads();for(const t of e){if(!t)continue;const i=t.index;if(!this._gamepadDevices[i]){this._handleGamepadConnected(t);continue}const n=this._gamepadDevices[i];if(!n)continue;const o=this._buttonStates[i]||{},r=this._axesStates[i]||{},a={};let u=!1;t.buttons.forEach((g,m)=>{const p=`button-${m}`,v={pressed:g.pressed,touched:g.touched,value:g.value};a[p]=v;const w=o[p];(!w||w.pressed!==v.pressed||w.touched!==v.touched||w.value!==v.value)&&(u=!0)});const l={};let f=!1;if(t.axes.forEach((g,m)=>{const p=`axis-${m}`;l[p]=g,r[p]!==g&&(f=!0)}),this._buttonStates[i]=a,this._axesStates[i]=l,u||f){const g={type:"gamepad",buttons:{...a},axes:{...l}};this._notifyInputChanged(n,g)}}}$teardown(){return window.removeEventListener("gamepadconnected",this._handleGamepadConnected),window.removeEventListener("gamepaddisconnected",this._handleGamepadDisconnected),Promise.resolve()}_setupGamepadEvents(){if(this._handleGamepadConnected=this._handleGamepadConnected.bind(this),this._handleGamepadDisconnected=this._handleGamepadDisconnected.bind(this),window.addEventListener("gamepadconnected",this._handleGamepadConnected),window.addEventListener("gamepaddisconnected",this._handleGamepadDisconnected),navigator.getGamepads){const e=navigator.getGamepads();for(const t of e)t&&this._handleGamepadConnected(t)}}_handleGamepadConnected(e){const t=e instanceof GamepadEvent?e.gamepad:e,i=t.index,n={id:`gamepad-${i}`,name:t.id,type:"gamepad",connected:!0,index:i,mapping:t.mapping,axes:Array.from(t.axes),buttons:Array.from(t.buttons)};this._gamepadDevices[i]=n;const o={};Array.from(t.buttons).forEach((a,u)=>{o[`button-${u}`]={pressed:a.pressed,touched:a.touched,value:a.value}}),this._buttonStates[i]=o;const r={};Array.from(t.axes).forEach((a,u)=>{r[`axis-${u}`]=a}),this._axesStates[i]=r,this._notifyDeviceConnected(n),this._notifyInputChanged(n,{type:"gamepad",buttons:{...o},axes:{...r},event:e instanceof GamepadEvent?e:void 0})}_handleGamepadDisconnected(e){const i=e.gamepad.index,n=this._gamepadDevices[i];n&&(n.connected=!1,this._notifyDeviceDisconnected(n),this._gamepadDevices[i]=void 0,this._buttonStates[i]=void 0,this._axesStates[i]=void 0)}_notifyDeviceConnected(e){this._onDeviceConnected&&this._onDeviceConnected(e)}_notifyDeviceDisconnected(e){this._onDeviceDisconnected&&this._onDeviceDisconnected(e)}_notifyInputChanged(e,t){this._onInputChanged&&this._onInputChanged(e,t)}}class j{constructor(e,t,i){s(this,"_eventBus");s(this,"_keyboardRA");s(this,"_mouseRA");s(this,"_gamepadRA");s(this,"_log");this._eventBus=e,this._log=(i==null?void 0:i.getLogger("UserInputManager"))||new _("UserInputManager"),this._log.info("Initializing UserInputManager"),this._log.debug("Initializing input resource access classes"),this._keyboardRA=new W,this._mouseRA=new F(t),this._gamepadRA=new N,this._log.debug("Registering input event callbacks"),this._keyboardRA.onDeviceConnected(this._publishDeviceConnected.bind(this)),this._keyboardRA.onInputChanged(this._publishInputChanged.bind(this)),this._mouseRA.onDeviceConnected(this._publishDeviceConnected.bind(this)),this._mouseRA.onInputChanged(this._publishInputChanged.bind(this)),this._gamepadRA.onDeviceConnected(this._publishDeviceConnected.bind(this)),this._gamepadRA.onDeviceDisconnected(this._publishDeviceDisconnected.bind(this)),this._gamepadRA.onInputChanged(this._publishInputChanged.bind(this)),this._log.info("UserInputManager initialized successfully")}_publishDeviceConnected(e){this._log.debug(`Device connected: ${e.id} (${e.name})`);const t={type:"input:device:connected",payload:{device:e}};this._eventBus.publish(t)}_publishDeviceDisconnected(e){this._log.debug(`Device disconnected: ${e.id} (${e.name})`);const t={type:"input:device:disconnected",payload:{device:e}};this._eventBus.publish(t)}_publishInputChanged(e,t){this._log.trace(`Input changed: ${e.id}`,t);const i={type:"input:changed",payload:{deviceId:e.id,device:e,state:t}};this._eventBus.publish(i)}$teardown(){return this._log.info("Tearing down UserInputManager"),this._log.debug("Cleaning up input resource access instances"),Promise.all([this._keyboardRA.$teardown(),this._mouseRA.$teardown(),this._gamepadRA.$teardown()]).then(()=>{this._log.info("UserInputManager torn down successfully")})}listDevices(){this._log.debug("Getting all connected input devices");const e=[];return e.push(this._keyboardRA.getDevice()),e.push(this._mouseRA.getDevice()),e.push(...this._gamepadRA.getDevices()),this._log.debug(`Found ${e.length} connected devices`),e}getDevice(e){if(this._log.debug(`Getting device: ${e}`),e.startsWith("keyboard-"))return this._keyboardRA.getDevice();if(e.startsWith("mouse-"))return this._mouseRA.getDevice();if(e.startsWith("gamepad-")){const t=parseInt(e.split("-")[1],10);return this._gamepadRA.getDevice(t)}else{const t=this.listDevices();for(const i of t)if(i.id===e)return i}return this._log.warn(`Device not found: ${e}`),null}pollGamepads(){this._gamepadRA.pollGamepads()}}async function L(c,e){const t=new h.WebGPUEngine(c,e);return await t.initAsync(),t}function C(c,e){return new h.Engine(c,e.antialias,e.options,e.adaptToDeviceRatio)}function J(c){return new h.NullEngine(c)}async function Y(c,e){if(c===null)return console.debug("Using Null Engine"),J(e);const t=e.engine||"auto";if(t==="webgpu")if(V())try{return console.debug("Using forced WebGPU engine"),await L(c,e)}catch(i){throw console.error("Forced WebGPU initialization failed:",i),new Error("Forced WebGPU failed to initialize. If WebGPU is required, check browser compatibility.")}else throw new Error("WebGPU was forced but is not available in this browser.");if(t==="webgl")return console.debug("Using forced WebGL engine"),C(c,e);if(V())try{return console.debug("Using WebGPU"),L(c,e).catch(i=>(console.warn("WebGPU initialization failed, falling back to WebGL:",i),C(c,e)))}catch(i){console.warn("WebGPU initialization failed, falling back to WebGL:",i)}else console.warn("WebGPU not supported, falling back to WebGL.");return console.debug("Using WebGL"),C(c,e)}async function X(){const c=await y();return new h.HavokPlugin(!0,c)}const q=["keyboard","mouse","gamepad"],Z=["trace","debug","info","warn","error","none"];async function Q(c,e,t={}){const i=new A(t.logLevel||"debug"),n=i.getLogger("SAGE");n.info(`Initializing SAGE Game Engine v${b}...`),n.debug("Creating rendering engine...");const o=await Y(c,t.renderOptions||{});n.debug("Creating physics engine...");const r=await X();n.debug("Creating event bus...");const a=new I(i);n.debug("Creating scene engine...");const u=new T(o,r,i);n.debug("Creating managers...");const l=new j(a,c,i),f=new z(a,i),g=new H(a,i,f),m=new P(o,u,g,l,i);n.info(`SAGE Game Engine v${b} initialized successfully.`),n.debug("Loading entities...");for(const p of e)g.registerEntityDefinition(p);if(n.debug("Registering default input bindings..."),t.bindings)for(const p of t.bindings)f.registerBinding(p);return new D(c,o,r,a,i,{sceneEngine:u},{bindingManager:f,entityManager:g,gameManager:m,inputManager:l})}d.ConsoleBackend=k,d.GameEntity=B,d.GameEntityBehavior=O,d.GameEventBus=I,d.LogLevels=Z,d.LoggingUtility=A,d.NullBackend=M,d.SAGELogger=_,d.SkewedAspectGameEngine=D,d.VERSION=b,d.bindingTypes=G,d.createGameEngine=Q,d.validDeviceTypes=q,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"})});
2
2
  //# sourceMappingURL=sage.umd.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skewedaspect/sage",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
4
4
  "main": "dist/sage.umd.js",
5
5
  "module": "dist/sage.es.js",
6
6
  "types": "dist/sage.d.ts",