@skewedaspect/sage 0.10.1 → 0.11.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.
@@ -1 +1 @@
1
- {"version":3,"file":"sage.es.js","names":[],"sources":["../src/utils/capabilities.ts","../src/classes/debugConsole.ts","../src/utils/version.ts","../src/classes/gameEngine.ts","../src/utils/vectors.ts","../src/entities/nodeEntity.ts","../src/entities/entityPicking.ts","../src/classes/loggers/consoleBackend.ts","../src/classes/loggers/nullBackend.ts","../src/utils/logger.ts","../src/entities/nodeSimulation.ts","../src/entities/constants.ts","../src/entities/nodePool.ts","../src/engines/audio.ts","../src/utils/physics.ts","../src/engines/scene.ts","../src/managers/asset.ts","../src/managers/game.ts","../src/input/actionContext.ts","../src/input/actionRegistry.ts","../src/input/deliverAction.ts","../src/input/deviceReaders/gamepad.ts","../src/input/deviceReaders/keyboard.ts","../src/input/deviceReaders/mouse.ts","../src/input/readerFor.ts","../src/input/bindings/trigger.ts","../src/input/bindings/toggle.ts","../src/input/bindings/value.ts","../src/input/bindings/delta.ts","../src/input/resolveBinding.ts","../src/input/scheme.ts","../src/managers/input.ts","../src/handlers/postProcessingPipeline.ts","../src/handlers/postProcessing.ts","../src/utils/bounds.ts","../src/handlers/collider.ts","../src/handlers/lod.ts","../src/handlers/occlusion.ts","../src/utils/metadata.ts","../src/handlers/sound.ts","../src/handlers/trigger.ts","../src/handlers/visible.ts","../src/handlers/index.ts","../src/classes/level.ts","../src/managers/outline.ts","../src/classes/gameLevel.ts","../src/managers/level.ts","../src/managers/audio.ts","../src/managers/simulationSave.ts","../src/managers/colliderDebug.ts","../src/utils/graphics.ts","../src/utils/timer.ts","../src/debug/builtins.ts","../src/interfaces/logger.ts","../src/entities/defineEntity.ts","../src/entities/standardMeshSource.ts","../src/behaviors/sound.ts","../src/behaviors/physicsBody.ts","../src/behaviors/characterController.ts","../src/input/inputCapture.ts","../src/input/configuration.ts","../src/sage.ts"],"sourcesContent":["//----------------------------------------------------------------------------------------------------------------------\n// Utilities to test for capability of the current environment\n//----------------------------------------------------------------------------------------------------------------------\n\nexport function isBrowser() : boolean\n{\n return typeof window !== 'undefined' && typeof window.document !== 'undefined';\n}\n\nexport function hasWebGPU() : boolean\n{\n return isBrowser()\n && !!window.navigator.gpu;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Debug Console\n//\n// Manages a namespace object on `window` that exposes game engine internals to the browser dev console.\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { isBrowser } from '../utils/capabilities.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/** What `expose` takes to define a property that resolves fresh on every read. */\nexport interface DynamicGetter\n{\n get : () => unknown;\n}\n\ninterface ExposedEntry\n{\n getter ?: () => unknown;\n value ?: unknown;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport class DebugConsole\n{\n #namespaceName : string;\n #entries = new Map<string, ExposedEntry>();\n #disposed = false;\n\n constructor(namespaceName = 'sage')\n {\n this.#namespaceName = namespaceName;\n this.#installNamespace();\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Public API\n //------------------------------------------------------------------------------------------------------------------\n\n /** Defines a property that resolves fresh on every read (design/debugging.md#debug-tooling). */\n expose(name : string, getter : DynamicGetter) : void;\n\n /** Puts a value, a callable command included, on the namespace (design/debugging.md#debug-tooling). */\n expose(name : string, value : unknown) : void;\n\n expose(name : string, value : unknown) : void\n {\n if(this.#disposed)\n {\n return;\n }\n\n if(this.#isDynamicGetter(value))\n {\n this.#entries.set(name, { getter: value.get });\n this.#defineGetter(name, value.get);\n }\n else\n {\n this.#entries.set(name, { value });\n this.#defineValue(name, value);\n }\n }\n\n setNamespace(name : string) : void\n {\n if(this.#disposed || name === this.#namespaceName)\n {\n return;\n }\n\n // Checked before the old namespace comes down, so a collision leaves the console where it was\n this.#assertNamespaceFree(name);\n\n this.#removeNamespace();\n this.#namespaceName = name;\n this.#installNamespace();\n\n for(const [ entryName, entry ] of this.#entries)\n {\n if(entry.getter)\n {\n this.#defineGetter(entryName, entry.getter);\n }\n else\n {\n this.#defineValue(entryName, entry.value);\n }\n }\n }\n\n get namespaceName() : string\n {\n return this.#namespaceName;\n }\n\n dispose() : void\n {\n if(this.#disposed)\n {\n return;\n }\n\n this.#removeNamespace();\n this.#entries.clear();\n this.#disposed = true;\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Private\n //------------------------------------------------------------------------------------------------------------------\n\n #isDynamicGetter(value : unknown) : value is DynamicGetter\n {\n return typeof value === 'object'\n && value !== null\n && 'get' in value\n && typeof (value as DynamicGetter).get === 'function';\n }\n\n #getNamespace() : Record<string, unknown> | undefined\n {\n if(!isBrowser())\n {\n return undefined;\n }\n\n return Reflect.get(window, this.#namespaceName) as Record<string, unknown> | undefined;\n }\n\n #assertNamespaceFree(name : string) : void\n {\n if(!isBrowser())\n {\n return;\n }\n\n if(Reflect.has(window, name))\n {\n throw new Error(\n `window.${ name } is already occupied, and the debug console will not overwrite it. `\n + 'Pick a different namespace with the debug.namespace option.'\n );\n }\n }\n\n #installNamespace() : void\n {\n if(!isBrowser())\n {\n return;\n }\n\n this.#assertNamespaceFree(this.#namespaceName);\n\n Reflect.set(window, this.#namespaceName, {});\n }\n\n #removeNamespace() : void\n {\n if(!isBrowser())\n {\n return;\n }\n\n Reflect.deleteProperty(window, this.#namespaceName);\n }\n\n #defineGetter(name : string, getter : () => unknown) : void\n {\n const ns = this.#getNamespace();\n\n if(!ns)\n {\n return;\n }\n\n Object.defineProperty(ns, name, {\n get: getter,\n enumerable: true,\n configurable: true,\n });\n }\n\n #defineValue(name : string, value : unknown) : void\n {\n const ns = this.#getNamespace();\n\n if(!ns)\n {\n return;\n }\n\n Object.defineProperty(ns, name, {\n value,\n writable: true,\n enumerable: true,\n configurable: true,\n });\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Version utility for SAGE\n//----------------------------------------------------------------------------------------------------------------------\n\ndeclare const __APP_VERSION__ : string;\n\nexport const VERSION = typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : '0.0.0-dev';\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Game Engine\n//\n// WARNING: This module must be imported as `import type` everywhere except sage.ts. A regular import\n// would create a circular runtime dependency (managers import GameEngine, GameEngine imports managers).\n// The `import type` is erased at compile time, keeping the runtime graph acyclic.\n//----------------------------------------------------------------------------------------------------------------------\n\n// Interfaces\nimport type { AbstractEngine, Scene } from '@babylonjs/core';\nimport type { EventBus, Unsubscribe } from '@skewedaspect/sage-core';\nimport type { GameCanvas } from '../interfaces/game.ts';\nimport type { Disposable } from '../interfaces/lifecycle.ts';\nimport type { LoggerInterface } from '../interfaces/logger.ts';\n\n// Entities\nimport type { NodeEntityRegistry, NodeSimulation } from '../entities/nodeSimulation.ts';\nimport type { MeshLoader } from '../entities/nodeSimulationContext.ts';\n\n// Engines\nimport type { SceneEngine } from '../engines/scene.ts';\nimport type { AudioEngine } from '../engines/audio.ts';\n\n// Managers\nimport type { AssetManager } from '../managers/asset.ts';\nimport type { ColliderDebugManager } from '../managers/colliderDebug.ts';\nimport type { DebugConsole } from './debugConsole.ts';\nimport type { GameManager } from '../managers/game.ts';\nimport type { InputManager } from '../managers/input.ts';\nimport type { LevelManager } from '../managers/level.ts';\nimport type { SimulationSaveManager } from '../managers/simulationSave.ts';\nimport type { AudioManager } from '../managers/audio.ts';\n\n// Utils\nimport type { LoggingUtility } from '../utils/logger.ts';\nimport type { GameTimer } from '../utils/timer.ts';\nimport { VERSION } from '../utils/version.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Type definition for game lifecycle hook functions\n */\n\nexport type GameHook<Registry extends NodeEntityRegistry = NodeEntityRegistry>\n = (gameEngine : GameEngine<Registry>) => Promise<void>;\n\n/** Builds a NodeSimulation named and bound to a scene and a mesh source -- a fresh one for every level that loads. */\nexport type SimulationFactory<Registry extends NodeEntityRegistry = NodeEntityRegistry>\n = (scene : Scene, name : string, meshSource ?: MeshLoader) => NodeSimulation<Registry>;\n\n/**\n * Interface representing the engines used in the game.\n * All engines must implement Disposable for proper cleanup.\n */\nexport interface Engines extends Record<string, Disposable | undefined>\n{\n sceneEngine : SceneEngine;\n audioEngine ?: AudioEngine;\n}\n\n/**\n * Interface representing the managers used in the game.\n * All managers must implement Disposable for proper cleanup.\n */\nexport interface Managers<Registry extends NodeEntityRegistry = NodeEntityRegistry>\n extends Record<string, Disposable | undefined>\n{\n assetManager : AssetManager;\n gameManager : GameManager<Registry>;\n inputManager : InputManager;\n levelManager : LevelManager<Registry>;\n saveManager : SimulationSaveManager<Registry>;\n audioManager ?: AudioManager;\n}\n\n/**\n * Interface representing the debug tools available on the engine.\n */\nexport interface DebugTools\n{\n colliders : ColliderDebugManager;\n console : DebugConsole | null;\n expose(name : string, value : unknown) : void;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Central hub that owns the render loop, the event bus, and all managers.\n * Created via `createGameEngine()` in sage.ts.\n */\nexport class GameEngine<Registry extends NodeEntityRegistry = NodeEntityRegistry>\n{\n public canvas : GameCanvas;\n public renderEngine : AbstractEngine;\n public managers : Managers<Registry>;\n public engines : Engines;\n public bus : EventBus;\n public logger : LoggingUtility;\n public timer : GameTimer;\n public debug : DebugTools;\n public largeWorldRendering : boolean;\n public started = false;\n\n private _stopped = false;\n\n private _log : LoggerInterface;\n private _buildSimulation : SimulationFactory<Registry>;\n\n // Null until something binds one: the level manager activating a level, or a game binding what it built\n // itself. Only the bound simulation is attached to the bus and stepped\n // (design/simulation.md#which-simulation-the-engine-holds).\n #bound : NodeSimulation<Registry> | null = null;\n\n // Any number of hooks per point, in registration order (design/engine.md#lifecycle-hooks).\n readonly #beforeStartHooks : GameHook<Registry>[] = [];\n readonly #startHooks : GameHook<Registry>[] = [];\n readonly #teardownHooks : GameHook<Registry>[] = [];\n\n /**\n * Creates an instance of GameEngine.\n * @param buildSimulation - Builds a fresh simulation for a scene, once rebuildSimulation is called with one.\n */\n constructor(\n canvas : GameCanvas,\n renderEngine : AbstractEngine,\n bus : EventBus,\n logger : LoggingUtility,\n timer : GameTimer,\n buildSimulation : SimulationFactory<Registry>,\n engines : Engines,\n managers : Managers<Registry>,\n largeWorldRendering = false,\n debug : DebugTools\n )\n {\n this.canvas = canvas;\n this.renderEngine = renderEngine;\n this.bus = bus;\n this.logger = logger;\n this.timer = timer;\n this._buildSimulation = buildSimulation;\n this.engines = engines;\n this.managers = managers;\n this.debug = debug;\n this.largeWorldRendering = largeWorldRendering;\n\n this._log = logger.getLogger('GameEngine');\n }\n\n //------------------------------------------------------------------------------------------------------------------\n\n /** The simulation the engine holds: attached to the bus, stepped by the frame loop, and what a view reads. */\n get simulation() : NodeSimulation<Registry> | null\n {\n return this.#bound;\n }\n\n /**\n * Builds a fresh NodeSimulation named `name` for the given scene and mesh source, handed this engine's bus and\n * not yet bound. A level calls this once its own scene exists, naming the simulation after itself, so every\n * NodeEntity it spawns gets a node in that scene and loads its mesh through that source; left unset, a spawned\n * entity gets a node with no mesh (design/simulation.md#the-simulations-name).\n */\n rebuildSimulation(scene : Scene, name : string, meshSource ?: MeshLoader) : NodeSimulation<Registry>\n {\n return this._buildSimulation(scene, name, meshSource);\n }\n\n /**\n * Binds the engine to a simulation, or to none. The one bound before detaches from the bus and the new one\n * attaches, carrying its entities' subscriptions and held emissions across\n * (design/events.md#one-bus-per-engine).\n */\n bindSimulation(simulation : NodeSimulation<Registry> | null) : void\n {\n if(this.#bound === simulation)\n {\n return;\n }\n\n this.#bound?.detach();\n this.#bound = simulation;\n simulation?.attach();\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Public API\n //------------------------------------------------------------------------------------------------------------------\n\n /** Runs before the frame loop starts, awaited, in registration order (design/engine.md#lifecycle-hooks). */\n onBeforeStart(hook : GameHook<Registry>) : Unsubscribe\n {\n return GameEngine.#register(this.#beforeStartHooks, hook);\n }\n\n /** Runs after the frame loop has started, awaited, in registration order. */\n onStart(hook : GameHook<Registry>) : Unsubscribe\n {\n return GameEngine.#register(this.#startHooks, hook);\n }\n\n /** Runs before anything is disposed, awaited; a hook that throws is logged and does not stop the teardown. */\n onTeardown(hook : GameHook<Registry>) : Unsubscribe\n {\n return GameEngine.#register(this.#teardownHooks, hook);\n }\n\n static #register<Registry extends NodeEntityRegistry>(\n hooks : GameHook<Registry>[],\n hook : GameHook<Registry>\n ) : Unsubscribe\n {\n hooks.push(hook);\n\n return () : void =>\n {\n const index = hooks.indexOf(hook);\n\n if(index >= 0)\n {\n hooks.splice(index, 1);\n }\n };\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Lifecycle\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Starts the frame loop, with the before-start hooks ahead of it and the start hooks after. An engine that has\n * stopped does not start again (design/architecture.md#teardown).\n */\n async start() : Promise<void>\n {\n if(this._stopped)\n {\n throw new Error('The game engine has stopped, and an engine that has stopped does not start again.');\n }\n\n if(this.started)\n {\n this._log.warn('Game engine is already started. Skipping start.');\n return;\n }\n\n this._log.info(`Starting SkewedAspect Game Engine (Version: ${ VERSION })...`);\n\n for(const hook of [ ...this.#beforeStartHooks ])\n {\n // eslint-disable-next-line no-await-in-loop\n await hook(this);\n }\n\n await this.managers.gameManager.start();\n\n for(const hook of [ ...this.#startHooks ])\n {\n // eslint-disable-next-line no-await-in-loop\n await hook(this);\n }\n\n this.started = true;\n this._log.info('Game engine started successfully');\n }\n\n /**\n * Tears the engine down in one order: the frame loop stops, the teardown hooks run, then the debug console, the\n * managers, and the engines dispose. A failure in one disposal does not stop the rest, and the first one raised\n * is raised again once everything else is disposed. Stopping twice changes nothing\n * (design/architecture.md#teardown).\n */\n async stop() : Promise<void>\n {\n if(this._stopped)\n {\n this._log.warn('Game engine is already stopped. Skipping stop.');\n return;\n }\n\n this._stopped = true;\n this._log.info(`Stopping SkewedAspect Game Engine (Version: ${ VERSION })...`);\n\n let firstError : unknown = null;\n\n const attempt = async (label : string, run : () => Promise<void>) : Promise<void> =>\n {\n try\n {\n await run();\n }\n catch(error)\n {\n this._log.error(`Error tearing down ${ label }: ${ error }`);\n\n if(firstError === null)\n {\n firstError = error;\n }\n }\n };\n\n // Nothing renders into what is being disposed.\n await attempt('the frame loop', () => this.managers.gameManager.stop());\n\n // Before any manager disposes, so nothing outlives the level that owned it.\n await attempt('unbinding the simulation', async () => this.bindSimulation(null));\n\n for(const hook of [ ...this.#teardownHooks ])\n {\n // eslint-disable-next-line no-await-in-loop\n await attempt('an onTeardown hook', () => hook(this));\n }\n\n await attempt('the debug console', async () => this.debug.console?.dispose());\n await attempt('the collider debug manager', () => this.debug.colliders.dispose());\n\n for(const managerKey of Object.keys(this.managers) as (keyof Managers)[])\n {\n const manager = this.managers[managerKey];\n\n if(manager)\n {\n // eslint-disable-next-line no-await-in-loop\n await attempt(`manager ${ managerKey }`, () => manager.dispose());\n }\n }\n\n for(const engineKey of Object.keys(this.engines) as (keyof Engines)[])\n {\n const engine = this.engines[engineKey];\n\n if(engine)\n {\n // eslint-disable-next-line no-await-in-loop\n await attempt(`engine ${ engineKey }`, () => engine.dispose());\n }\n }\n\n this.started = false;\n this._log.info('Game engine stopped successfully');\n\n if(firstError !== null)\n {\n throw firstError;\n }\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Vector / Quaternion Conversion Utilities\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { Matrix, Quaternion, type TransformNode, Vector3 } from '@babylonjs/core';\n\nimport type { QuatConfig, Vec3Config } from '../interfaces/level.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Convert a BabylonJS Vector3 to a plain { x, y, z } object.\n */\nexport function toVec3Object(vector : Vector3) : Vec3Config\n{\n return { x: vector.x, y: vector.y, z: vector.z };\n}\n\n/**\n * Convert a plain { x, y, z } object to a BabylonJS Vector3.\n */\nexport function toVector3(vec : Vec3Config) : Vector3\n{\n return new Vector3(vec.x, vec.y, vec.z);\n}\n\n/**\n * Convert a BabylonJS Quaternion to a plain { x, y, z, w } object.\n */\nexport function toQuatObject(quat : Quaternion) : QuatConfig\n{\n return { x: quat.x, y: quat.y, z: quat.z, w: quat.w }; // eslint-disable-line id-length\n}\n\n/**\n * Convert a plain { x, y, z, w } object to a BabylonJS Quaternion.\n */\nexport function toQuaternion(quat : QuatConfig) : Quaternion\n{\n return new Quaternion(quat.x, quat.y, quat.z, quat.w);\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n// Live views\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * A record whose components read and write through to whatever `read` answers with, so `view.x += 1` writes the\n * same fact `write` takes whole (design/entities.md#computed-state).\n */\nexport function vectorView(read : () => Vector3, write : (value : Vec3Config) => void) : Vec3Config\n{\n const view = {} as Vec3Config;\n\n for(const component of [ 'x', 'y', 'z' ] as const)\n {\n Object.defineProperty(view, component, {\n get: () : number => read()[component],\n set: (value : number) : void =>\n {\n const held = toVec3Object(read());\n held[component] = value;\n\n write(held);\n },\n enumerable: true,\n configurable: true,\n });\n }\n\n return view;\n}\n\n/** `vectorView` for a quaternion, with `w` alongside the other three. */\nexport function quaternionView(read : () => Quaternion, write : (value : QuatConfig) => void) : QuatConfig\n{\n const view = {} as QuatConfig;\n\n for(const component of [ 'x', 'y', 'z', 'w' ] as const)\n {\n Object.defineProperty(view, component, {\n get: () : number => read()[component],\n set: (value : number) : void =>\n {\n const held = toQuatObject(read());\n held[component] = value;\n\n write(held);\n },\n enumerable: true,\n configurable: true,\n });\n }\n\n return view;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n// Transform Hierarchy Utilities\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * A TransformNode's own local matrix, recovered from its world matrix and its parent's, since Babylon does\n * not expose the local matrix a node's world matrix was built from.\n */\nfunction localMatrix(node : TransformNode) : Matrix\n{\n node.computeWorldMatrix(true);\n const world = node.getWorldMatrix();\n const parent = node.parent as TransformNode | null;\n\n if(!parent)\n {\n return world.clone();\n }\n\n parent.computeWorldMatrix(true);\n\n return world.multiply(Matrix.Invert(parent.getWorldMatrix()));\n}\n\n/**\n * The rotation and scaling of a matrix, with its translation dropped.\n */\nfunction linearPart(matrix : Matrix) : Matrix\n{\n const linear = matrix.clone();\n linear.setTranslation(Vector3.Zero());\n\n return linear;\n}\n\n/**\n * A node's transform with the mirroring an import conversion ancestor folded into its world matrix taken back\n * out: the world position exactly, and a proper rotation and positive scaling facing the same way.\n *\n * Babylon's glTF loader gives every imported model a `__root__` whose own transform mirrors one axis, and folds\n * that into the world matrix of everything under it. Decomposing such a matrix answers with a negative scaling\n * and a rotation of the wrong handedness, and a node built from those does not sit where the marker does.\n */\nexport function getCanonicalTransform(node : TransformNode) : {\n position : Vector3;\n rotation : Quaternion;\n scaling : Vector3;\n}\n{\n node.computeWorldMatrix(true);\n let canonical = node.getWorldMatrix().clone();\n\n let ancestor = node.parent as TransformNode | null;\n while(ancestor)\n {\n const ancestorLocal = localMatrix(ancestor);\n\n // Multiplying on the left by the inverse of the mirror's rotation and scaling carries the node's own\n // rotation and scaling to the other side of it and leaves the world position alone; the mirror's\n // translation, if it has one, stays folded into that position (design/engine.md#the-canonical-transform).\n if(ancestorLocal.determinant() < 0)\n {\n canonical = Matrix.Invert(linearPart(ancestorLocal)).multiply(canonical);\n }\n\n ancestor = ancestor.parent as TransformNode | null;\n }\n\n const position = new Vector3();\n const rotation = new Quaternion();\n const scaling = new Vector3();\n canonical.decompose(scaling, rotation, position);\n\n return { position, rotation, scaling };\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// NodeEntity\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { Quaternion, TransformNode } from '@babylonjs/core';\nimport { GameEntity } from '@skewedaspect/sage-core';\n\nimport type { AbstractMesh } from '@babylonjs/core';\nimport type {\n Action, BehaviorClass, ComposedOf, DeepReadonly, GameEntityState, GameEntityType, OpsFor,\n StateBag, StateFor, ValidTuple,\n} from '@skewedaspect/sage-core';\n\nimport { quaternionView, vectorView } from '../utils/vectors.ts';\n\nimport type { AudioManager } from '../managers/audio.ts';\nimport type { ColliderDebugConfig } from '../managers/colliderDebug.ts';\nimport type { NodeSet, NodeSimulationContext } from './nodeSimulationContext.ts';\nimport type { MeshConfig } from './meshConfig.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport interface Vector3State\n{\n x : number;\n y : number;\n z : number;\n}\n\nexport interface QuaternionState\n{\n x : number;\n y : number;\n z : number;\n w : number;\n}\n\nexport interface NodeEntityState extends GameEntityState\n{\n parentID ?: string;\n nodeName ?: string;\n position : Vector3State;\n rotation : QuaternionState;\n debugCollider ?: ColliderDebugConfig;\n}\n\n// Declared here, not on sage-core's Behavior, because a GameEntity never has a scene node\n// (design/behaviors.md#hooks). A behavior opts in by implementing either method; NodeEntity recognizes it\n// structurally, the same way it recognizes onCreate/onUpdate/onEvent.\nexport interface NodeBehaviorHooks\n{\n onMeshLoaded ?() : void;\n onDestroy ?() : void;\n}\n\n// A NodeSet a load is still in flight for maps here to whoever wants to hear when it settles. A set with no\n// entry has already settled, mesh or not -- prewarm and an already-finished load both leave nothing pending\n// (design/entities.md#pooling). Keyed by object identity rather than a field on NodeSet itself, so a set an\n// older part of the codebase or a test builds by hand, with no notion of this, is still exactly \"settled.\"\nconst pendingMeshLoads = new WeakMap<NodeSet, () => void>();\n\n// A mesh a loader hands an entity is never walked, so a property authored on it configures nothing; the\n// warning names what it found, once per entity class in a simulation\n// (design/authoring.md#a-spawned-mesh-carries-no-authored-properties).\nconst warnedClasses = new WeakMap<NodeSimulationContext, Set<string>>();\n\nfunction authoredPropertyNames(node : TransformNode) : string[]\n{\n const metadata = node.metadata as { gltf ?: { extras ?: Record<string, unknown> } } | null | undefined;\n const extras = metadata?.gltf?.extras;\n\n if(extras === undefined || extras === null)\n {\n return [];\n }\n\n return Object.keys(extras);\n}\n\nfunction warnAboutAuthoredProperties(\n context : NodeSimulationContext,\n name : string,\n id : string,\n mesh : AbstractMesh\n) : void\n{\n let warned = warnedClasses.get(context);\n\n if(warned === undefined)\n {\n warned = new Set();\n warnedClasses.set(context, warned);\n }\n\n if(warned.has(name))\n {\n return;\n }\n\n const findings : string[] = [];\n\n for(const node of [ mesh, ...mesh.getDescendants(false) ])\n {\n if(node instanceof TransformNode)\n {\n const names = authoredPropertyNames(node);\n\n if(names.length > 0)\n {\n findings.push(`\"${ node.name }\" carries ${ names.join(', ') }`);\n }\n }\n }\n\n if(findings.length === 0)\n {\n return;\n }\n\n warned.add(name);\n context.logger.warn(\n `The mesh handed to ${ name } (${ id }) carries authored properties a spawned mesh never reads: `\n + `${ findings.join('; ') }. Compose HavokPhysicsBodyBehavior for a body, or leave the property on the `\n + 'level\\'s own scene.'\n );\n}\n\n// How an entity came by its node, which decides what construction writes onto it and what destruction does with\n// it (design/entities.md#gameentity-and-nodeentity).\ntype NodeAcquisition = 'adopted' | 'claimed' | 'built';\n\ninterface HeldNode\n{\n set : NodeSet;\n acquisition : NodeAcquisition;\n}\n\n// The node is acquired inside GameEntity's constructor, in declareComputedState, and a `#` field cannot hold it:\n// a subclass's private fields are installed on the instance only once `super(...)` returns, which is after every\n// hook the base constructor runs (design/entities.md#computed-state).\nconst heldNodes = new WeakMap<NodeEntity, HeldNode>();\n\nfunction heldNodeOf(entity : NodeEntity) : HeldNode\n{\n const held = heldNodes.get(entity);\n\n if(held === undefined)\n {\n throw new Error(`NodeEntity: entity \"${ entity.id }\" is asked for its node before construction acquires it.`);\n }\n\n return held;\n}\n\nfunction spatialOf(state : object) : NodeEntityState\n{\n return state as NodeEntityState;\n}\n\nfunction findAuthoredNode(nodeContext : NodeSimulationContext, nodeName : string) : TransformNode\n{\n const found = nodeContext.scene.getNodeByName(nodeName);\n\n if(!(found instanceof TransformNode))\n {\n throw new Error(`NodeEntity: no authored node named \"${ nodeName }\" exists in the scene.`);\n }\n\n return found;\n}\n\n// Adoption is decided before pooling, and a class that is both poolable and adopted for one spawn never touches\n// the pool for it (design/authoring.md#pooling-and-destruction). This is a function rather than a method because\n// it runs during `super(...)`, where the entity's own private methods do not exist yet.\nfunction acquireNode(entity : NodeEntity, spatial : NodeEntityState, nodeContext : NodeSimulationContext) : HeldNode\n{\n if(spatial.nodeName !== undefined)\n {\n return {\n set: { node: findAuthoredNode(nodeContext, spatial.nodeName), mesh: undefined },\n acquisition: 'adopted',\n };\n }\n\n if(spatial.position === undefined)\n {\n spatial.position = { x: 0, y: 0, z: 0 };\n }\n\n if(spatial.rotation === undefined)\n {\n spatial.rotation = { x: 0, y: 0, z: 0, w: 1 }; // eslint-disable-line id-length\n }\n\n const entityClass = entity.constructor as typeof NodeEntity;\n\n if(entityClass.poolable)\n {\n const pooled = nodeContext.pool.take(entity.name);\n\n if(pooled !== undefined)\n {\n // The pool held the set hidden, and the entity claiming it shows it again\n // (design/entities.md#pooling).\n pooled.node.setEnabled(true);\n\n return { set: pooled, acquisition: 'claimed' };\n }\n }\n\n return {\n set: { node: new TransformNode(entity.id, nodeContext.scene), mesh: undefined },\n acquisition: 'built',\n };\n}\n\n// The fill-in of the origin and the identity quaternion never reaches an adopted node, which it would drag away\n// from where authoring put it; only what the spawn state supplies is written through the setters\n// (design/authoring.md#position-rotation-and-scale).\nfunction writeSuppliedTransform(spatial : NodeEntityState, suppliedState : object) : void\n{\n const supplied = suppliedState as Partial<NodeEntityState>;\n\n if(supplied.position !== undefined)\n {\n spatial.position = supplied.position;\n }\n\n if(supplied.rotation !== undefined)\n {\n spatial.rotation = supplied.rotation;\n }\n}\n\n// A node built or authored without one carries no rotationQuaternion at all, and its rotation is the euler\n// triple instead. The quaternion those angles already mean takes its place once, so a component of `rotation`\n// has something to write to (design/entities.md#gameentity-and-nodeentity).\nfunction quaternionOf(node : TransformNode) : Quaternion\n{\n if(node.rotationQuaternion === null)\n {\n node.rotationQuaternion = Quaternion.FromEulerVector(node.rotation);\n }\n\n return node.rotationQuaternion;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// The behaviors tuple types the entity's own state and operations, so a subclass that names its tuple reads\n// both with no declaration of its own (design/entities.md#composition).\nexport abstract class NodeEntity<Behaviors extends readonly BehaviorClass[] = readonly BehaviorClass[]>\n extends GameEntity<NodeSimulationContext>\n{\n static readonly poolable : boolean = false;\n static readonly poolSize ?: number;\n static readonly meshConfig ?: MeshConfig;\n static readonly actions ?: readonly Action[];\n\n declare readonly state : DeepReadonly<StateFor<Behaviors>>;\n declare readonly ops : OpsFor<Behaviors>;\n\n // A NodeEntity is created with a NodeSimulationContext, the context that carries the scene its node goes in\n // (design/entities.md#gameentity-and-nodeentity). Declared here so a plain simulation's entity registry, and a\n // direct create with a plain context, are both compile errors (design/entities.md#the-entity-registry).\n static override create<Type extends GameEntityType>(\n this : Type & { behaviors : ValidTuple<Type['behaviors'], Type['prototype']> },\n id : string,\n context : NodeSimulationContext,\n state ?: Partial<StateFor<Type['behaviors']>>,\n name ?: string\n ) : ComposedOf<Type>\n {\n return super.create(id, context, state, name);\n }\n\n static override restore<Type extends GameEntityType>(\n this : Type & { behaviors : ValidTuple<Type['behaviors'], Type['prototype']> },\n id : string,\n context : NodeSimulationContext,\n state : StateFor<Type['behaviors']>,\n name ?: string\n ) : ComposedOf<Type>\n {\n return super.restore(id, context, state, name);\n }\n\n get node() : TransformNode\n {\n return heldNodeOf(this).set.node;\n }\n\n get mesh() : AbstractMesh | undefined\n {\n return heldNodeOf(this).set.mesh;\n }\n\n set mesh(mesh : AbstractMesh | undefined)\n {\n heldNodeOf(this).set.mesh = mesh;\n }\n\n get audio() : AudioManager | undefined\n {\n return this.context.audio;\n }\n\n showCollider(config ?: ColliderDebugConfig, color ?: string) : void\n {\n const colliders = this.context.colliders;\n const nodes = this.#physicsNodes();\n\n if(config === undefined || config === true)\n {\n for(const node of nodes)\n {\n colliders.showColliderForNode(node);\n }\n\n return;\n }\n\n if(config === false)\n {\n return;\n }\n\n if(typeof config === 'string')\n {\n const target = nodes.find((node) => node.name === config);\n\n if(target)\n {\n colliders.showColliderForNode(target, color);\n }\n\n return;\n }\n\n if('color' in config)\n {\n for(const node of nodes)\n {\n colliders.showColliderForNode(node, config.color);\n }\n\n return;\n }\n\n for(const node of nodes)\n {\n const entry = config.nodes[node.name];\n\n if(entry)\n {\n colliders.showColliderForNode(node, entry);\n }\n }\n }\n\n hideCollider(name ?: string) : void\n {\n const colliders = this.context.colliders;\n const nodes = this.#physicsNodes();\n\n if(name === undefined)\n {\n for(const node of nodes)\n {\n colliders.hideColliderForNode(node);\n }\n\n return;\n }\n\n const target = nodes.find((node) => node.name === name);\n\n if(target)\n {\n colliders.hideColliderForNode(target);\n }\n }\n\n // The fields NodeEntity itself declares are on every NodeEntity's state, whatever tuple a subclass types the\n // rest of it by.\n #spatial() : NodeEntityState\n {\n return spatialOf(this.state);\n }\n\n // The node is acquired here, ahead of every hook, so `onStateReady` and `onCreate` both read a position that\n // is already the node's (design/entities.md#computed-state). What is written onto it afterwards is what\n // adoption changes: an authored node keeps its authored transform, and a built or claimed one is placed from\n // the composed state (design/authoring.md#position-rotation-and-scale).\n protected override declareComputedState(suppliedState : object) : void\n {\n const spatial = spatialOf(this.state);\n const held = acquireNode(this, spatial, this.context);\n\n heldNodes.set(this, held);\n\n const node = held.set.node;\n const composed = { position: spatial.position, rotation: spatial.rotation };\n\n quaternionOf(node);\n\n const writePosition = (value : Vector3State) : void =>\n {\n node.position.set(value.x, value.y, value.z);\n };\n\n const writeRotation = (value : QuaternionState) : void =>\n {\n quaternionOf(node).set(value.x, value.y, value.z, value.w);\n };\n\n // Each answers with one view of the node's own vector, so `position.x += 1` moves the node through the\n // same write assigning the whole record makes (design/entities.md#computed-state).\n const position = vectorView(() => node.position, writePosition);\n const rotation = quaternionView(() => quaternionOf(node), writeRotation);\n\n this.computed<NodeEntityState, 'position'>('position', { get: () => position, set: writePosition });\n this.computed<NodeEntityState, 'rotation'>('rotation', { get: () => rotation, set: writeRotation });\n\n if(held.acquisition === 'adopted')\n {\n writeSuppliedTransform(spatial, suppliedState);\n\n return;\n }\n\n spatial.position = composed.position;\n spatial.rotation = composed.rotation;\n }\n\n // this.node itself counts, alongside its descendants (design/debugging.md#per-entity-collider-visualization).\n #physicsNodes() : TransformNode[]\n {\n const nodes : TransformNode[] = [];\n\n if(this.node.physicsBody)\n {\n nodes.push(this.node);\n }\n\n for(const child of this.node.getChildTransformNodes(false))\n {\n if(child.physicsBody)\n {\n nodes.push(child);\n }\n }\n\n return nodes;\n }\n\n protected constructor(\n id : string,\n context : NodeSimulationContext,\n providedState : object,\n runCreateHooks : boolean,\n name ?: string\n )\n {\n super(id, context, providedState, runCreateHooks, name);\n\n const held = heldNodeOf(this);\n const set = held.set;\n\n // Parented before any onMeshLoaded runs, so a hook that reads where the node stands in the scene reads it\n // under the parent the state names (design/entities.md#the-parent-child-hierarchy).\n const spatial = this.#spatial();\n\n if(spatial.parentID !== undefined)\n {\n const parentEntity = context.getEntity(spatial.parentID);\n\n if(parentEntity instanceof NodeEntity)\n {\n this.node.parent = parentEntity.node;\n }\n }\n\n if(held.acquisition === 'built')\n {\n pendingMeshLoads.set(set, () => { this.#runMeshLoadedHooks(); });\n\n // The catch sits on the load alone, never around the notify below: a throwing onMeshLoaded is not\n // caught, and wrapping the whole chain would swallow it (design/behaviors.md#onmeshloaded).\n context.loadMesh(context.scene, this.name, this.state as StateBag)\n .catch((error : unknown) =>\n {\n context.logger.error(\n `Loading the mesh for ${ this.name } (${ this.id }) failed. The entity keeps its node `\n + 'and carries no mesh.',\n error\n );\n\n return undefined;\n })\n .then((mesh) =>\n {\n if(mesh !== undefined)\n {\n mesh.parent = set.node;\n warnAboutAuthoredProperties(context, this.name, this.id, mesh);\n }\n\n set.mesh = mesh;\n\n const notify = pendingMeshLoads.get(set);\n pendingMeshLoads.delete(set);\n notify?.();\n });\n }\n else if(pendingMeshLoads.has(set))\n {\n pendingMeshLoads.set(set, () => { this.#runMeshLoadedHooks(); });\n }\n else\n {\n this.#settleMeshLoadedNow();\n }\n }\n\n // Runs onMeshLoaded synchronously, inside construction -- adoption or a pooled claim already settled.\n // A throwing hook here fails the construction it runs inside, the same as a throwing onCreate does\n // (design/behaviors.md#onmeshloaded): super.destroy() only unsubscribes, bypassing this class's own\n // destroy(), which assumes a fully-built entity.\n #settleMeshLoadedNow() : void\n {\n try\n {\n this.#runMeshLoadedHooks();\n }\n catch(error)\n {\n super.destroy();\n throw error;\n }\n }\n\n // Collider visualization runs after every behavior's onMeshLoaded, so a body a behavior builds in that hook is\n // what it shows (design/debugging.md#config-driven-visualization).\n #runMeshLoadedHooks() : void\n {\n for(const instance of this.attachedBehaviors())\n {\n (instance as NodeBehaviorHooks).onMeshLoaded?.();\n }\n\n const spatial = this.#spatial();\n\n if(spatial.debugCollider !== undefined)\n {\n this.showCollider(spatial.debugCollider);\n }\n }\n\n #runDestroyHooks() : void\n {\n for(const instance of this.attachedBehaviors())\n {\n (instance as NodeBehaviorHooks).onDestroy?.();\n }\n }\n\n override destroy() : void\n {\n this.hideCollider();\n\n // Every computation comes off while the node, and any body over it, are still there to be asked\n // (design/entities.md#computed-state).\n this.removeComputedState();\n\n this.#runDestroyHooks();\n\n super.destroy();\n\n const held = heldNodeOf(this);\n\n // A set this entity was still waiting to hear from stays pending for whoever claims it next\n // (design/entities.md#pooling); this entity itself just stops being who that is.\n if(pendingMeshLoads.has(held.set))\n {\n pendingMeshLoads.set(held.set, () => { /* this entity is gone; nothing left to notify */ });\n }\n\n // The node is the level's content, not a resource this entity borrowed -- destroying the entity never\n // disposes, hides, or pools it (design/authoring.md#pooling-and-destruction).\n if(held.acquisition === 'adopted')\n {\n return;\n }\n\n const entityClass = this.constructor as typeof NodeEntity;\n\n if(entityClass.poolable)\n {\n this.context.pool.give(this.name, held.set, entityClass.poolSize);\n }\n else\n {\n held.set.node.dispose();\n }\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Entity Picking\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { Vector3 } from '@babylonjs/core';\n\nimport type { AbstractMesh, Node, PickingInfo } from '@babylonjs/core';\nimport type { GameEntity, GameEntityState } from '@skewedaspect/sage-core';\n\nimport { NodeEntity } from './nodeEntity.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport interface EntityPickFilter\n{\n tags ?: readonly string[];\n}\n\nexport interface EntityPickResult\n{\n entity : NodeEntity;\n mesh : AbstractMesh;\n point : Vector3;\n distance : number;\n normal : Vector3;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// Climbs the mesh's own ancestor chain -- itself, then its parent, and so on -- against the simulation's\n// current entity list, rather than a maintained node-to-entity lookup: nothing here needs invalidating on\n// spawn, despawn, or a pooled node changing hands.\nexport function findEntityForMesh(entities : readonly GameEntity[], mesh : AbstractMesh) : NodeEntity | undefined\n{\n const nodeEntities : NodeEntity[] = [];\n\n for(const entity of entities)\n {\n if(entity instanceof NodeEntity)\n {\n nodeEntities.push(entity);\n }\n }\n\n let current : Node | null = mesh;\n\n while(current !== null)\n {\n for(const entity of nodeEntities)\n {\n if(entity.node === current)\n {\n return entity;\n }\n }\n\n current = current.parent;\n }\n\n return undefined;\n}\n\nfunction passesFilter(entity : NodeEntity, filter : EntityPickFilter | undefined) : boolean\n{\n if(filter?.tags === undefined)\n {\n return true;\n }\n\n const tags = (entity.state as GameEntityState).tags;\n\n for(const tag of filter.tags)\n {\n if(!tags.includes(tag))\n {\n return false;\n }\n }\n\n return true;\n}\n\nexport function toEntityPickResult(\n entities : readonly GameEntity[],\n info : PickingInfo | null,\n filter ?: EntityPickFilter\n) : EntityPickResult | undefined\n{\n if(info === null || !info.hit || info.pickedMesh === null || info.pickedPoint === null)\n {\n return undefined;\n }\n\n const entity = findEntityForMesh(entities, info.pickedMesh);\n\n if(entity === undefined || !passesFilter(entity, filter))\n {\n return undefined;\n }\n\n const normal = info.getNormal(true, true);\n\n return {\n entity,\n mesh: info.pickedMesh,\n point: info.pickedPoint,\n distance: info.distance,\n normal: normal ?? Vector3.Zero(),\n };\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Console Logger Backend\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { LogLevel, LoggingBackend } from '../../interfaces/logger.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\ntype ColorLevel = LogLevel | 'timer';\n\n/**\n * A logging backend that logs to the console.\n */\nexport class ConsoleBackend implements LoggingBackend\n{\n private timers : Map<string, number>;\n\n constructor()\n {\n this.timers = new Map();\n }\n\n /**\n * Get the CSS style for a specific log level\n */\n private getStyleForLevel(level : ColorLevel) : string\n {\n // CSS styles for browser console\n const styles : Record<ColorLevel, string> = {\n trace: 'color: #999999', // Dim gray\n debug: 'color: #00AAAA', // Cyan\n info: 'color: #00AA00', // Green\n warn: 'color: #AAAA00', // Yellow\n error: 'color: #AA0000', // Red\n timer: 'color: #AA00AA', // Magenta\n none: 'color: inherit', // Default\n };\n\n return styles[level] || styles.none;\n }\n\n /**\n * Format a log message with category, timestamp and log level\n */\n private formatMessage(category : string, level : ColorLevel) : [string, string, string, string]\n {\n // Format time as HH:MM:SS AM/PM\n const now = new Date();\n const hours = now.getHours() % 12 || 12; // Convert 0 to 12 for 12 AM\n const minutes = now.getMinutes().toString()\n .padStart(2, '0');\n const seconds = now.getSeconds().toString()\n .padStart(2, '0');\n const ampm = now.getHours() >= 12 ? 'PM' : 'AM';\n const timestamp = `[${ hours }:${ minutes }:${ seconds } ${ ampm }]`;\n\n const upperLevel = level.toUpperCase();\n\n // Return format strings and styling that will be used with console methods\n return [\n `${ timestamp } %c${ upperLevel }%c (${ category }): `, // Format string with placeholders\n this.getStyleForLevel(level), // Level style\n '', // Default style\n '', // Reset style\n ];\n }\n\n trace(category : string, message : string, ...args : unknown[]) : void\n {\n const [ format, defaultStyle, levelStyle, resetStyle ] = this.formatMessage(category, 'trace');\n console.debug(format, defaultStyle, levelStyle, resetStyle, message, ...args);\n }\n\n debug(category : string, message : string, ...args : unknown[]) : void\n {\n const [ format, defaultStyle, levelStyle, resetStyle ] = this.formatMessage(category, 'debug');\n console.debug(format, defaultStyle, levelStyle, resetStyle, message, ...args);\n }\n\n info(category : string, message : string, ...args : unknown[]) : void\n {\n const [ format, defaultStyle, levelStyle, resetStyle ] = this.formatMessage(category, 'info');\n console.info(format, defaultStyle, levelStyle, resetStyle, message, ...args);\n }\n\n warn(category : string, message : string, ...args : unknown[]) : void\n {\n const [ format, defaultStyle, levelStyle, resetStyle ] = this.formatMessage(category, 'warn');\n console.warn(format, defaultStyle, levelStyle, resetStyle, message, ...args);\n }\n\n error(category : string, message : string, ...args : unknown[]) : void\n {\n const [ format, defaultStyle, levelStyle, resetStyle ] = this.formatMessage(category, 'error');\n console.error(format, defaultStyle, levelStyle, resetStyle, message, ...args);\n }\n\n /**\n * Start a timer with the specified label.\n */\n time(category : string, label : string) : void\n {\n const timerKey = `${ category }:${ label }`;\n this.timers.set(timerKey, performance.now());\n }\n\n /**\n * End a timer and log the elapsed time.\n */\n timeEnd(category : string, label : string) : void\n {\n const timerKey = `${ category }:${ label }`;\n const startTime = this.timers.get(timerKey);\n\n if(startTime !== undefined)\n {\n const duration = performance.now() - startTime;\n this.timers.delete(timerKey);\n\n const [ format, defaultStyle, levelStyle, resetStyle ] = this.formatMessage(category, 'timer');\n console.info(\n `${ format } Timer '${ label }' completed in ${ duration.toFixed(2) }ms`,\n defaultStyle,\n levelStyle,\n resetStyle\n );\n }\n else\n {\n console.warn(`[${ category }]: Timer '${ label }' does not exist`);\n }\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Null Logger Backend\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { LoggingBackend } from '../../interfaces/logger.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * A logging backend that does nothing (discards all logs).\n * Useful for production environments or when you want to completely disable logging.\n */\nexport class NullBackend implements LoggingBackend\n{\n trace(_category : string, _message : string, ..._args : unknown[]) : void\n {\n // Do nothing\n }\n\n debug(_category : string, _message : string, ..._args : unknown[]) : void\n {\n // Do nothing\n }\n\n info(_category : string, _message : string, ..._args : unknown[]) : void\n {\n // Do nothing\n }\n\n warn(_category : string, _message : string, ..._args : unknown[]) : void\n {\n // Do nothing\n }\n\n error(_category : string, _message : string, ..._args : unknown[]) : void\n {\n // Do nothing\n }\n\n time(_category : string, _label : string) : void\n {\n // Do nothing\n }\n\n timeEnd(_category : string, _label : string) : void\n {\n // Do nothing\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Logger Utility\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type {\n LogLevel,\n LoggerInterface,\n LoggingBackend,\n} from '../interfaces/logger.ts';\n\nimport { ConsoleBackend } from '../classes/loggers/consoleBackend.ts';\nimport { NullBackend } from '../classes/loggers/nullBackend.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Implementation of Logger interface.\n * Each instance is bound to a specific category and backend.\n */\nexport class SAGELogger implements LoggerInterface\n{\n private category : string;\n private backend : LoggingBackend;\n private minLevel : LogLevel;\n\n constructor(category : string, minLevel : LogLevel = 'none', backend : LoggingBackend = new NullBackend())\n {\n this.category = category;\n this.backend = backend;\n this.minLevel = minLevel;\n }\n\n /**\n * Update the logger's backend and minimum level\n */\n public updateSettings(backend : LoggingBackend, minLevel : LogLevel) : void\n {\n this.backend = backend;\n this.minLevel = minLevel;\n }\n\n trace(message : string, ...args : unknown[]) : void\n {\n if(this.shouldLog('trace'))\n {\n this.backend.trace(this.category, message, ...args);\n }\n }\n\n debug(message : string, ...args : unknown[]) : void\n {\n if(this.shouldLog('debug'))\n {\n this.backend.debug(this.category, message, ...args);\n }\n }\n\n info(message : string, ...args : unknown[]) : void\n {\n if(this.shouldLog('info'))\n {\n this.backend.info(this.category, message, ...args);\n }\n }\n\n warn(message : string, ...args : unknown[]) : void\n {\n if(this.shouldLog('warn'))\n {\n this.backend.warn(this.category, message, ...args);\n }\n }\n\n error(message : string, ...args : unknown[]) : void\n {\n if(this.shouldLog('error'))\n {\n this.backend.error(this.category, message, ...args);\n }\n }\n\n time(label : string) : void\n {\n if(this.minLevel !== 'none')\n {\n this.backend.time(this.category, label);\n }\n }\n\n timeEnd(label : string) : void\n {\n if(this.minLevel !== 'none')\n {\n this.backend.timeEnd(this.category, label);\n }\n }\n\n /**\n * Determine if a message at the given level should be logged based on the current minimum level.\n */\n private shouldLog(level : LogLevel) : boolean\n {\n if(this.minLevel === 'none')\n {\n return false;\n }\n\n switch (this.minLevel)\n {\n case 'trace':\n return true;\n case 'debug':\n return level !== 'trace';\n case 'info':\n return level === 'info' || level === 'warn' || level === 'error';\n case 'warn':\n return level === 'warn' || level === 'error';\n case 'error':\n return level === 'error';\n default:\n return false;\n }\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * A utility for creating loggers and managing logging configuration.\n */\nexport class LoggingUtility\n{\n private backend : LoggingBackend;\n private level : LogLevel;\n private loggers : Map<string, LoggerInterface>;\n\n /**\n * Create a new LoggingUtility.\n *\n * @param level - The minimum logging level (defaults to INFO)\n * @param backend - The logging backend to use (defaults to ConsoleBackend)\n */\n constructor(level : LogLevel = 'debug', backend : LoggingBackend = new ConsoleBackend())\n {\n this.backend = backend;\n this.level = level;\n this.loggers = new Map();\n }\n\n /**\n * Set the logging backend. Updates all existing loggers to use the new backend.\n */\n public setBackend(backend : LoggingBackend) : void\n {\n this.backend = backend;\n\n // Update all existing loggers to use the new backend\n this.loggers.forEach((logger) =>\n {\n if(logger instanceof SAGELogger)\n {\n logger.updateSettings(this.backend, this.level);\n }\n });\n }\n\n /**\n * Set the minimum logging level. Updates all existing loggers.\n */\n public setLevel(level : LogLevel) : void\n {\n this.level = level;\n\n // Update all existing loggers to use the new level\n this.loggers.forEach((logger) =>\n {\n if(logger instanceof SAGELogger)\n {\n logger.updateSettings(this.backend, this.level);\n }\n });\n }\n\n /**\n * Get the current minimum logging level.\n */\n public getLevel() : LogLevel\n {\n return this.level;\n }\n\n /**\n * Get or create a logger for the specified category. Returns an existing logger if one was already created.\n *\n * @param category - Typically the class or module name\n */\n public getLogger(category : string) : LoggerInterface\n {\n let logger = this.loggers.get(category);\n if(!logger)\n {\n logger = new SAGELogger(category, this.level, this.backend);\n this.loggers.set(category, logger);\n }\n\n return logger;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport { ConsoleBackend } from '../classes/loggers/consoleBackend.ts';\nexport { NullBackend } from '../classes/loggers/nullBackend.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// NodeSimulation\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { TransformNode } from '@babylonjs/core';\nimport { GameSimulation } from '@skewedaspect/sage-core';\n\nimport type {\n GameEntityRegistry, GameSimulationOptions, GameSimulationSnapshot, StateBag, ValidEntityRegistry,\n} from '@skewedaspect/sage-core';\nimport type { AbstractMesh, Camera, Ray, Scene } from '@babylonjs/core';\n\nimport { findEntityForMesh, toEntityPickResult } from './entityPicking.ts';\nimport type { EntityPickFilter, EntityPickResult } from './entityPicking.ts';\nimport { NodeEntity } from './nodeEntity.ts';\nimport type { NodePool } from './nodePool.ts';\nimport type { MeshLoader, NodeSimulationContext } from './nodeSimulationContext.ts';\nimport type { AudioManager } from '../managers/audio.ts';\nimport type { ColliderDebugManager } from '../managers/colliderDebug.ts';\nimport { SAGELogger } from '../utils/logger.ts';\nimport type { LoggerInterface } from '../interfaces/logger.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport type NodeEntityRegistry = GameEntityRegistry<NodeSimulationContext>;\n\nexport interface NodeSimulationOptions<Registry extends NodeEntityRegistry = NodeEntityRegistry>\n extends GameSimulationOptions<Registry>\n{\n scene : Scene;\n loadMesh : MeshLoader;\n pool : NodePool;\n audio ?: AudioManager;\n colliders : ColliderDebugManager;\n logger ?: LoggerInterface;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport class NodeSimulation<Registry extends NodeEntityRegistry = NodeEntityRegistry>\n extends GameSimulation<Registry, NodeSimulationContext>\n implements NodeSimulationContext\n{\n readonly scene : Scene;\n readonly pool : NodePool;\n readonly loadMesh : MeshLoader;\n readonly audio : AudioManager | undefined;\n readonly colliders : ColliderDebugManager;\n readonly logger : LoggerInterface;\n\n readonly #entityClasses : Registry;\n\n constructor(options : NodeSimulationOptions<Registry> & { entityClasses : ValidEntityRegistry<Registry> })\n {\n super(options);\n\n this.scene = options.scene;\n this.pool = options.pool;\n this.loadMesh = options.loadMesh;\n this.audio = options.audio;\n this.colliders = options.colliders;\n this.logger = options.logger ?? new SAGELogger('NodeSimulation');\n this.#entityClasses = options.entityClasses;\n\n // Babylon advances physics from scene.render(), by the render frame's delta. This simulation\n // advances it instead, once per fixed step, so the two are not on separate clocks\n // (design/physics.md#physics-runs-on-the-fixed-step). The flag reads as \"does this scene have\n // physics\" and is not: it gates the per-frame advance only, and getPhysicsEngine() keeps answering.\n this.scene.physicsEnabled = false;\n }\n\n /**\n * Advances the scene's physics engine by one fixed step.\n *\n * A scene with no physics engine has nothing to advance and does nothing, which is what a game that\n * never asked for physics gets (design/physics.md#physics-is-optional).\n */\n protected override advanceFixedStep(duration : number) : void\n {\n this.scene.getPhysicsEngine()?._step(duration);\n }\n\n async prewarm(name : string, count : number, state : StateBag = {}) : Promise<void>\n {\n const entityClass = this.#entityClasses[name] as typeof NodeEntity | undefined;\n\n const meshes = await Promise.all(\n Array.from({ length: count }, () => this.loadMesh(this.scene, name, state))\n );\n\n for(const mesh of meshes)\n {\n const node = new TransformNode(name, this.scene);\n\n if(mesh !== undefined)\n {\n mesh.parent = node;\n }\n\n this.pool.give(name, { node, mesh }, entityClass?.poolSize);\n }\n }\n\n pickEntityWithRay(ray : Ray, filter ?: EntityPickFilter) : EntityPickResult | undefined\n {\n return toEntityPickResult(this.getEntitiesOfClass(NodeEntity), this.scene.pickWithRay(ray), filter);\n }\n\n pickEntity(x : number, y : number, filter ?: EntityPickFilter) : EntityPickResult | undefined\n {\n return toEntityPickResult(this.getEntitiesOfClass(NodeEntity), this.scene.pick(x, y), filter);\n }\n\n pickEntityForward(camera : Camera, range : number, filter ?: EntityPickFilter) : EntityPickResult | undefined\n {\n return this.pickEntityWithRay(camera.getForwardRay(range), filter);\n }\n\n getEntityForMesh(mesh : AbstractMesh) : NodeEntity | undefined\n {\n return findEntityForMesh(this.getEntitiesOfClass(NodeEntity), mesh);\n }\n\n // Every class this simulation's registry holds is a NodeEntity class, since the registry's context is this\n // simulation's own, so what spawnByName answers with is a NodeEntity (design/entities.md#the-entity-registry).\n // The replacement is what buildReplacementSimulation below builds, so it is a NodeSimulation; the base signature\n // cannot say so, and this one does (design/simulation.md#snapshot-and-restore).\n override buildFromSnapshot(snapshot : GameSimulationSnapshot) : NodeSimulation<Registry>\n {\n return super.buildFromSnapshot(snapshot) as NodeSimulation<Registry>;\n }\n\n protected override buildReplacementSimulation(options : GameSimulationOptions<Registry>) : NodeSimulation<Registry>\n {\n return new NodeSimulation<Registry>({\n ...(options as GameSimulationOptions<Registry> & { entityClasses : ValidEntityRegistry<Registry> }),\n scene: this.scene,\n pool: this.pool,\n loadMesh: this.loadMesh,\n audio: this.audio,\n colliders: this.colliders,\n logger: this.logger,\n });\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Tuning Constants\n//----------------------------------------------------------------------------------------------------------------------\n\nexport const poolSizeLimit = 8;\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// NodePool\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { poolSizeLimit } from './constants.ts';\n\nimport type { NodeSet } from './nodeSimulationContext.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// Node and mesh sets, keyed by entity name, capped per class at `poolSizeLimit`.\nexport class NodePool\n{\n readonly #sets = new Map<string, NodeSet[]>();\n\n take(name : string) : NodeSet | undefined\n {\n const sets = this.#sets.get(name);\n\n if(sets === undefined)\n {\n return undefined;\n }\n\n return sets.pop();\n }\n\n give(name : string, set : NodeSet, limit : number = poolSizeLimit) : void\n {\n set.node.parent = null;\n set.node.setEnabled(false);\n\n let sets = this.#sets.get(name);\n\n if(sets === undefined)\n {\n sets = [];\n this.#sets.set(name, sets);\n }\n\n if(sets.length >= limit)\n {\n set.node.dispose();\n return;\n }\n\n sets.push(set);\n }\n\n clear(name : string) : void\n {\n const sets = this.#sets.get(name);\n\n if(sets === undefined)\n {\n return;\n }\n\n for(const set of sets)\n {\n set.node.dispose();\n }\n\n this.#sets.delete(name);\n }\n\n clearAll() : void\n {\n for(const name of [ ...this.#sets.keys() ])\n {\n this.clear(name);\n }\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Audio Engine\n//----------------------------------------------------------------------------------------------------------------------\n\nimport {\n type AudioBus,\n type AudioEngineV2,\n CreateAudioEngineAsync,\n type IStaticSoundOptions,\n type MainAudioBus,\n type PrimaryAudioBus,\n type StaticSound,\n} from '@babylonjs/core';\n\n// Interfaces\nimport type { Disposable } from '../interfaces/lifecycle.ts';\nimport type { LoggerInterface } from '../interfaces/logger.ts';\n\n// Utils\nimport type { LoggingUtility } from '../utils/logger.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport class AudioEngine implements Disposable\n{\n #engine : AudioEngineV2 | null = null;\n #mainBus : MainAudioBus | null = null;\n #buses = new Map<string, AudioBus>();\n #log : LoggerInterface;\n\n constructor(logger : LoggingUtility)\n {\n this.#log = logger.getLogger('AudioEngine');\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Lifecycle\n //------------------------------------------------------------------------------------------------------------------\n\n async initialize() : Promise<void>\n {\n this.#log.info('Initializing audio engine...');\n\n this.#engine = await CreateAudioEngineAsync();\n this.#mainBus = await this.#engine.createMainBusAsync('main');\n\n this.#log.info('Audio engine initialized.');\n }\n\n async dispose() : Promise<void>\n {\n this.#log.info('Tearing down audio engine...');\n\n this.#buses.clear();\n this.#mainBus = null;\n\n if(this.#engine)\n {\n this.#engine.dispose();\n this.#engine = null;\n }\n\n this.#log.info('Audio engine torn down.');\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Bus Management\n //------------------------------------------------------------------------------------------------------------------\n\n async createBus(name : string) : Promise<AudioBus>\n {\n if(!this.#engine || !this.#mainBus)\n {\n throw new Error('AudioEngine not initialized');\n }\n\n this.#log.debug(`Creating audio bus: ${ name }`);\n const bus = await this.#engine.createBusAsync(name, { outBus: this.#mainBus });\n this.#buses.set(name, bus);\n return bus;\n }\n\n getBus(name : string) : AudioBus | undefined\n {\n return this.#buses.get(name);\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Sound Creation\n //------------------------------------------------------------------------------------------------------------------\n\n async createSound(\n name : string,\n url : string,\n bus ?: AudioBus,\n options ?: Partial<IStaticSoundOptions>\n ) : Promise<StaticSound>\n {\n if(!this.#engine)\n {\n throw new Error('AudioEngine not initialized');\n }\n\n const outBus : PrimaryAudioBus | undefined = bus ?? this.#mainBus ?? undefined;\n\n const soundOptions : Partial<IStaticSoundOptions> = { ...options };\n\n if(outBus !== undefined)\n {\n soundOptions.outBus = outBus;\n }\n\n return this.#engine.createSoundAsync(name, url, soundOptions);\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Volume\n //------------------------------------------------------------------------------------------------------------------\n\n setMasterVolume(volume : number) : void\n {\n if(!this.#engine)\n {\n throw new Error('AudioEngine not initialized');\n }\n\n this.#engine.volume = volume;\n }\n\n getMasterVolume() : number\n {\n if(!this.#engine)\n {\n return 1;\n }\n\n return this.#engine.volume;\n }\n\n setBusVolume(bus : AudioBus, volume : number) : void\n {\n bus.volume = volume;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Physics Utility\n//----------------------------------------------------------------------------------------------------------------------\n\nimport HavokPhysics, { type HavokPhysicsWithBindings } from '@babylonjs/havok';\nimport { HavokPlugin, Vector3 } from '@babylonjs/core';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Earth's gravity, downward, in the units the rest of the scene is authored in. There is one default\n * gravity, not one per call site (design/physics.md#the-physics-engine).\n *\n * A fresh vector every call: Babylon keeps the one it is handed by reference, and a game that wants gravity\n * to change while a level runs writes it onto the running engine.\n */\nexport function defaultGravity() : Vector3\n{\n return new Vector3(0, -9.81, 0);\n}\n\n/**\n * Initializes the Havok WASM module and returns the raw bindings.\n *\n * Each BabylonJS scene needs its own HavokPlugin instance (because scene.dispose() disposes the\n * plugin attached to it). The raw WASM module can be shared across plugins safely.\n *\n * @param wasmBinary - Pre-loaded Havok WASM. Left unset, Havok fetches its own over HTTP, which\n * only works in a browser.\n */\nexport async function initHavok(wasmBinary ?: BufferSource) : Promise<HavokPhysicsWithBindings>\n{\n if(wasmBinary === undefined)\n {\n return await HavokPhysics();\n }\n\n // Emscripten declares the slot as ArrayBuffer, but hands it straight to WebAssembly, which\n // takes any BufferSource. A node Buffer is the common case and is not an ArrayBuffer.\n return await HavokPhysics({ wasmBinary: wasmBinary as ArrayBuffer });\n}\n\n/**\n * Creates a new HavokPlugin from an existing WASM instance.\n */\nexport function createHavokPlugin(havok : HavokPhysicsWithBindings) : HavokPlugin\n{\n return new HavokPlugin(true, havok);\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Scene Engine\n//----------------------------------------------------------------------------------------------------------------------\n\nimport {\n AbstractEngine,\n AbstractMesh,\n AssetContainer,\n DirectionalLight,\n HavokPlugin,\n type HavokPluginParameters,\n HemisphericLight,\n ImportMeshAsync,\n Light,\n LoadAssetContainerAsync,\n Mesh,\n MeshBuilder,\n PointLight,\n RectAreaLight,\n Scene,\n SpotLight,\n Vector3,\n} from '@babylonjs/core';\n\n// Registering at import is what lets loadModel and importMeshes read any format Babylon ships a loader for.\n// Each loader's own code is fetched the first time a file of its kind is read.\nimport { registerBuiltInLoaders } from '@babylonjs/loaders/dynamic';\nregisterBuiltInLoaders();\nimport type { HavokPhysicsWithBindings } from '@babylonjs/havok';\n\n// Interfaces\nimport type { Disposable } from '../interfaces/lifecycle.ts';\nimport type { LoggerInterface } from '../interfaces/logger.ts';\n\n// Utils\nimport { type LoggingUtility } from '../utils/logger.ts';\nimport { defaultGravity, initHavok } from '../utils/physics.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport class SceneEngine implements Disposable\n{\n #engine : AbstractEngine;\n\n // Undefined until a scene asks for physics. Loading is megabytes of WebAssembly, and a game with\n // nothing simulated has no reason to pay for it (design/physics.md#physics-is-optional).\n #havok : HavokPhysicsWithBindings | undefined;\n\n // In a browser Havok fetches its own bytes; on NullEngine there is nothing to fetch from, so a game\n // booting headless hands them in and whatever asks for physics first uses them.\n readonly #havokBinary : BufferSource | undefined;\n\n // Holding the promise rather than the module is what makes two simultaneous asks load it once.\n #havokLoading : Promise<HavokPhysicsWithBindings> | undefined;\n\n #log : LoggerInterface;\n\n constructor(\n engine : AbstractEngine,\n logger : LoggingUtility,\n havokBinary ?: BufferSource\n )\n {\n this.#engine = engine;\n this.#havokBinary = havokBinary;\n this.#log = logger.getLogger('SceneEngine');\n }\n\n /** Whether the Havok module has been loaded, which nothing does until a scene asks for physics. */\n public get $havokLoaded() : boolean\n {\n return this.#havok !== undefined;\n }\n\n /** The one loaded module, for a test asserting every scene's plugin was built over the same one. */\n public get $havokModule() : HavokPhysicsWithBindings | undefined\n {\n return this.#havok;\n }\n\n /**\n * Creates an empty scene on the render engine. It belongs to whoever asked for it.\n */\n public createScene() : Scene\n {\n return new Scene(this.#engine);\n }\n\n public async enablePhysics(\n scene : Scene,\n gravityVector : Vector3 = defaultGravity(),\n floatingOriginWorldRadius ?: number\n ) : Promise<void>\n {\n const havok = await this.#loadHavok();\n\n this.#log.debug(\n `Enabling physics with gravity (${ gravityVector.x }, ${ gravityVector.y }, ${ gravityVector.z })...`\n );\n\n let pluginOptions : HavokPluginParameters | undefined;\n\n if(floatingOriginWorldRadius !== undefined)\n {\n pluginOptions = { floatingOriginWorldRadius };\n }\n\n // Each scene gets its own HavokPlugin so that scene.dispose() doesn't corrupt the shared WASM module.\n const plugin = new HavokPlugin(true, havok, pluginOptions);\n scene.enablePhysics(gravityVector, plugin);\n }\n\n /**\n * The first ask starts the load and every ask after it joins that one. A load that fails is not\n * remembered, so the ask after a failure starts a fresh one (design/physics.md#physics-is-optional).\n */\n async #loadHavok() : Promise<HavokPhysicsWithBindings>\n {\n if(this.#havokLoading === undefined)\n {\n this.#havokLoading = initHavok(this.#havokBinary);\n }\n\n const loading = this.#havokLoading;\n\n try\n {\n const havok = await loading;\n\n // A dispose while this load was in flight forgot it. The scene that asked still gets its plugin\n // over what loaded, but a disposed engine remembers nothing.\n if(this.#havokLoading === loading)\n {\n this.#havok = havok;\n }\n\n return havok;\n }\n catch(error)\n {\n if(this.#havokLoading === loading)\n {\n this.#havokLoading = undefined;\n }\n\n throw error;\n }\n }\n\n // Lighting Utilities\n /**\n * Creates a hemispheric light (ambient lighting)\n */\n public createHemisphericLight(\n name : string,\n direction : Vector3,\n scene : Scene,\n intensity = 0.7\n ) : HemisphericLight\n {\n const light = new HemisphericLight(name, direction, scene);\n light.intensity = intensity;\n return light;\n }\n\n /**\n * Creates a directional light (sun-like lighting)\n */\n public createDirectionalLight(\n name : string,\n direction : Vector3,\n scene : Scene,\n intensity = 1.0\n ) : DirectionalLight\n {\n const light = new DirectionalLight(name, direction, scene);\n light.intensity = intensity;\n return light;\n }\n\n /**\n * Creates a point light (bulb-like lighting)\n */\n public createPointLight(\n name : string,\n position : Vector3,\n scene : Scene,\n intensity = 1.0\n ) : PointLight\n {\n const light = new PointLight(name, position, scene);\n light.intensity = intensity;\n return light;\n }\n\n /**\n * Creates a spotlight (flashlight-like lighting)\n * @param angle - Cone angle in radians\n * @param exponent - Controls light falloff from center to edge of cone\n */\n public createSpotLight(\n name : string,\n position : Vector3,\n direction : Vector3,\n angle : number,\n exponent : number,\n scene : Scene,\n intensity = 1.0\n ) : SpotLight\n {\n const light = new SpotLight(name, position, direction, angle, exponent, scene);\n light.intensity = intensity;\n return light;\n }\n\n /**\n * Creates a rectangular area light (emits from a flat rectangle in the -Z direction)\n */\n public createRectAreaLight(\n name : string,\n position : Vector3,\n width : number,\n height : number,\n scene : Scene,\n intensity = 1.0\n ) : RectAreaLight\n {\n const light = new RectAreaLight(name, position, width, height, scene);\n light.intensity = intensity;\n return light;\n }\n\n // Mesh Creation Utilities\n /**\n * Creates a sphere mesh. Defaults: diameter 1, 32 segments.\n */\n public createSphere(\n name : string,\n options : { diameter ?: number; segments ?: number } = {},\n scene : Scene\n ) : Mesh\n {\n const defaultOptions = { diameter: 1, segments: 32 };\n return MeshBuilder.CreateSphere(name, { ...defaultOptions, ...options }, scene);\n }\n\n /**\n * Creates a box mesh. Default size: 1.\n */\n public createBox(\n name : string,\n options : { size ?: number; width ?: number; height ?: number; depth ?: number } = {},\n scene : Scene\n ) : Mesh\n {\n const defaultOptions = { size: 1 };\n return MeshBuilder.CreateBox(name, { ...defaultOptions, ...options }, scene);\n }\n\n /**\n * Creates a ground mesh. Defaults: 6x6 with 2 subdivisions.\n */\n public createGround(\n name : string,\n options : { width ?: number; height ?: number; subdivisions ?: number } = {},\n scene : Scene\n ) : Mesh\n {\n const defaultOptions = { width: 6, height: 6, subdivisions: 2 };\n return MeshBuilder.CreateGround(name, { ...defaultOptions, ...options }, scene);\n }\n\n /**\n * Creates a cylinder mesh. Defaults: height 2, diameter 1 (top and bottom).\n */\n public createCylinder(\n name : string,\n options : { height ?: number; diameterTop ?: number; diameterBottom ?: number } = {},\n scene : Scene\n ) : Mesh\n {\n const defaultOptions = { height: 2, diameterTop: 1, diameterBottom: 1 };\n return MeshBuilder.CreateCylinder(name, { ...defaultOptions, ...options }, scene);\n }\n\n // Model Loading Utilities\n /**\n * Loads a 3D model/asset into an AssetContainer without adding it to the scene automatically.\n */\n public async loadModel(\n sceneFilename : string,\n scene : Scene\n ) : Promise<AssetContainer>\n {\n this.#log.debug(`Loading model: ${ sceneFilename }`);\n\n try\n {\n const result = await LoadAssetContainerAsync(sceneFilename, scene);\n this.#log.debug(`Model loaded successfully: ${ sceneFilename }`);\n return result;\n }\n catch (error)\n {\n this.#log.error(`Failed to load model: ${ sceneFilename }`, error);\n throw error;\n }\n }\n\n /**\n * Imports meshes from a file directly into the scene.\n * @param meshNames - Specific meshes to import; empty array imports all\n */\n public async importMeshes(\n meshNames : string[],\n sceneFilename : string,\n scene : Scene\n ) : Promise<{\n meshes : AbstractMesh[];\n particleSystems : unknown[];\n skeletons : unknown[];\n animationGroups : unknown[];\n transformNodes : unknown[];\n geometries : unknown[];\n lights : Light[];\n }>\n {\n this.#log.debug(`Importing meshes from: ${ sceneFilename }`);\n\n // To Babylon an empty meshNames means \"import nothing\"; leaving the option out is what imports all.\n let options : { meshNames : string[] } | undefined;\n let imported = 'all';\n\n if(meshNames.length > 0)\n {\n options = { meshNames };\n imported = meshNames.join(', ');\n }\n\n try\n {\n const result = await ImportMeshAsync(sceneFilename, scene, options);\n this.#log.debug(`Meshes imported successfully: ${ imported }`);\n return result;\n }\n catch (error)\n {\n this.#log.error(`Failed to import meshes from: ${ sceneFilename }`, error);\n throw error;\n }\n }\n\n /**\n * Releases the Havok module and forgets a load of it still in flight. The scenes this engine created\n * belong to their levels and are left alone.\n */\n async dispose() : Promise<void>\n {\n this.#log.info('Tearing down SceneEngine');\n\n this.#havok = undefined;\n this.#havokLoading = undefined;\n\n this.#log.info('SceneEngine torn down successfully');\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Asset Manager\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { Mesh } from '@babylonjs/core';\n\nimport type { AbstractMesh, AssetContainer, InstancedMesh, Scene } from '@babylonjs/core';\n\nimport type { EventBus } from '@skewedaspect/sage-core';\nimport type { SceneEngine } from '../engines/scene.ts';\nimport type { Disposable } from '../interfaces/lifecycle.ts';\nimport type { LoggerInterface } from '../interfaces/logger.ts';\nimport { type LoggingUtility, SAGELogger } from '../utils/logger.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\ninterface CachedContainer\n{\n container : AssetContainer;\n scene : Scene;\n refCount : number;\n}\n\n/**\n * How many assets a preload loads at once. Six is the per-origin connection limit browsers have long held\n * for HTTP/1.1: below it a batch leaves the connection pool idle, and above it the extra loads only queue\n * somewhere else while splitting the same bandwidth and competing for the same main thread to parse on.\n */\nconst MAX_CONCURRENT_LOADS = 6;\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Central asset cache with preloading, GLB fragment extraction, mesh instancing, and reference counting.\n *\n * Supports fragment syntax (`models/props.glb#chest_lid`) to extract individual meshes from a shared container.\n * Containers are reference-counted and only disposed when all consumers release them.\n */\nexport class AssetManager implements Disposable\n{\n private _bus : EventBus;\n private _sceneEngine : SceneEngine;\n private _log : LoggerInterface;\n\n /** Full GLB/GLTF asset containers, keyed by base path (without fragment) */\n private _containers = new Map<string, CachedContainer>();\n\n /** Extracted fragment meshes (source meshes for instancing), keyed by full fragment path */\n private _sourceMeshes = new Map<string, AbstractMesh>();\n\n /** Container loads still in flight, keyed by base path, so concurrent callers share one load */\n private _pendingContainers = new Map<string, Promise<AssetContainer>>();\n\n //------------------------------------------------------------------------------------------------------------------\n\n constructor(bus : EventBus, sceneEngine : SceneEngine, logger ?: LoggingUtility)\n {\n this._bus = bus;\n this._sceneEngine = sceneEngine;\n this._log = logger?.getLogger('AssetManager') ?? new SAGELogger('AssetManager');\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Private Helpers\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Split a path on `#` into base path and optional fragment name.\n */\n private _parsePath(path : string) : { basePath : string; fragment ?: string }\n {\n const hashIdx = path.indexOf('#');\n if(hashIdx === -1)\n {\n return { basePath: path };\n }\n\n return {\n basePath: path.substring(0, hashIdx),\n fragment: path.substring(hashIdx + 1),\n };\n }\n\n /**\n * Load (or retrieve from cache) the AssetContainer for a base path, counting one reference for this\n * caller. A caller arriving while the same base path is already loading shares that load instead of\n * starting a second one, which would leave the first container cached over and uncounted.\n */\n private async _loadContainer(basePath : string) : Promise<AssetContainer>\n {\n const cached = this._containers.get(basePath);\n if(cached)\n {\n cached.refCount++;\n return cached.container;\n }\n\n let inFlight = this._pendingContainers.get(basePath);\n if(!inFlight)\n {\n inFlight = this._fetchContainer(basePath);\n this._pendingContainers.set(basePath, inFlight);\n }\n\n const container = await inFlight;\n\n // The fetch caches with no references counted; each caller that awaited it counts its own here.\n // A dispose() during the fetch leaves nothing to count against, so check before counting.\n const entry = this._containers.get(basePath);\n if(entry)\n {\n entry.refCount++;\n }\n\n return container;\n }\n\n /**\n * Read one container off disk and cache it, counting no references; `_loadContainer` does the counting.\n *\n * The scene the read goes into is cached beside the container and lives as long as it does. Babylon's\n * `AssetContainer` subscribes to its scene's disposal and disposes itself, meshes and all, so disposing\n * the scene early would leave an emptied container in the cache\n * (design/engine.md#the-asset-manager).\n */\n private async _fetchContainer(basePath : string) : Promise<AssetContainer>\n {\n this._log.debug(`Loading asset container: ${ basePath }`);\n\n const scene = this._sceneEngine.createScene();\n\n try\n {\n const container = await this._sceneEngine.loadModel(basePath, scene);\n\n this._containers.set(basePath, { container, scene, refCount: 0 });\n this._log.debug(`Cached asset container: ${ basePath }`);\n\n return container;\n }\n catch (error)\n {\n scene.dispose();\n throw error;\n }\n finally\n {\n this._pendingContainers.delete(basePath);\n }\n }\n\n /**\n * Find a mesh by name inside a container. Throws if not found.\n */\n private _findMeshInContainer(container : AssetContainer, meshName : string, fullPath : string) : AbstractMesh\n {\n const mesh = container.meshes.find((entry) => entry.name === meshName);\n if(!mesh)\n {\n const available = container.meshes.map((entry) => entry.name).join(', ');\n throw new Error(\n `Mesh '${ meshName }' not found in container for '${ fullPath }'. `\n + `Available meshes: ${ available }`\n );\n }\n\n return mesh;\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Public API\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Load an asset, caching the result. Supports fragment syntax (`path#meshName`).\n *\n * - Without fragment: loads and caches the full AssetContainer, returns it.\n * - With fragment: loads the container, finds the named mesh, caches it as a source mesh, returns the mesh.\n *\n * Increments refCount on the underlying container each time.\n */\n async load(path : string) : Promise<AssetContainer | AbstractMesh>\n {\n const { basePath, fragment } = this._parsePath(path);\n\n if(fragment)\n {\n // Check if we already cached this fragment mesh\n const cachedMesh = this._sourceMeshes.get(path);\n if(cachedMesh)\n {\n // Still increment refCount on the container\n const cached = this._containers.get(basePath);\n if(cached)\n {\n cached.refCount++;\n }\n\n return cachedMesh;\n }\n\n // Load the container (handles caching + refCount internally)\n const container = await this._loadContainer(basePath);\n\n // Find and cache the named mesh\n const mesh = this._findMeshInContainer(container, fragment, path);\n this._sourceMeshes.set(path, mesh);\n\n return mesh;\n }\n\n // No fragment — return the full container\n return this._loadContainer(basePath);\n }\n\n /**\n * Create an InstancedMesh from a cached source mesh. The path must have been loaded first.\n */\n instance(path : string) : InstancedMesh\n {\n const sourceMesh = this._sourceMeshes.get(path);\n if(!sourceMesh)\n {\n throw new Error(`Asset '${ path }' is not loaded. Call load() first.`);\n }\n\n return (sourceMesh as Mesh).createInstance(`${ sourceMesh.name }-instance`);\n }\n\n /**\n * Clone a cached source mesh into an independent copy, in the scene it is handed. The path must have been\n * loaded first.\n *\n * `Mesh.clone` keeps the source's scene, and the source lives in the scene this manager read the file\n * into, so a clone made that way would never draw in the level that asked for it. Building the copy\n * against the scene it is handed is what puts it in the level's, and `doNotCloneChildren` keeps a mesh\n * parented under the source from being left behind in the manager's\n * (design/engine.md#the-asset-manager).\n */\n clone(path : string, into : Scene) : Mesh\n {\n const sourceMesh = this._sourceMeshes.get(path);\n if(!sourceMesh)\n {\n throw new Error(`Asset '${ path }' is not loaded. Call load() first.`);\n }\n\n const copy = new Mesh(`${ sourceMesh.name }-clone`, into, {\n source: sourceMesh as Mesh,\n doNotCloneChildren: true,\n });\n\n // The source's metadata is the glb's extras, which the level walk reads off the level's own scene and a\n // spawned mesh never carries (design/authoring.md#the-standard-mesh-source).\n copy.metadata = null;\n\n return copy;\n }\n\n /**\n * Preload a batch of asset paths, up to `MAX_CONCURRENT_LOADS` at a time, so a batch costs about as\n * long as its slowest asset rather than the sum of them all.\n *\n * Emits `asset:progress` as each asset finishes loading, naming the asset that finished and counting\n * one to one up to the total. Loads finish out of request order, so the paths do too.\n *\n * Every asset is attempted, and every load has finished before this returns. If any failed, it throws\n * an `AggregateError` naming all of them and emits no `asset:complete`.\n */\n async preload(paths : string[]) : Promise<void>\n {\n const total = paths.length;\n const failures : { path : string; error : unknown }[] = [];\n let nextIndex = 0;\n let loaded = 0;\n\n // A worker takes the next unclaimed path the moment its own load finishes, so a slow asset holds\n // up only itself. Nothing here yields between reading nextIndex and advancing it, so no two\n // workers can claim the same path.\n const runWorker = async () : Promise<void> =>\n {\n while(nextIndex < paths.length)\n {\n const path = paths[nextIndex];\n nextIndex++;\n\n let succeeded = true;\n\n try\n {\n // eslint-disable-next-line no-await-in-loop\n await this.load(path);\n }\n catch (error)\n {\n succeeded = false;\n failures.push({ path, error });\n }\n\n if(succeeded)\n {\n loaded++;\n\n this._bus.emit({\n type: 'asset:progress',\n senderID: 'assetManager',\n payload: {\n path,\n loaded,\n total,\n },\n });\n }\n }\n };\n\n const workers : Promise<void>[] = [];\n const workerCount = Math.min(MAX_CONCURRENT_LOADS, total);\n for(let i = 0; i < workerCount; i++)\n {\n workers.push(runWorker());\n }\n\n await Promise.all(workers);\n\n if(failures.length > 0)\n {\n const failedPaths = failures.map((failure) => failure.path).join(', ');\n\n throw new AggregateError(\n failures.map((failure) => failure.error),\n `Preload failed for ${ failures.length } of ${ total } assets: ${ failedPaths }`\n );\n }\n\n this._bus.emit({\n type: 'asset:complete',\n senderID: 'assetManager',\n payload: { paths },\n });\n }\n\n /**\n * Gives one reference back. Once the count reaches zero the container and every fragment mesh taken from it\n * are disposed (design/engine.md#the-asset-manager).\n */\n release(path : string) : void\n {\n const { basePath } = this._parsePath(path);\n const cached = this._containers.get(basePath);\n if(!cached)\n {\n return;\n }\n\n cached.refCount--;\n\n if(cached.refCount <= 0)\n {\n this._log.debug(`Disposing asset container: ${ basePath } (refCount reached 0)`);\n\n // Clean up all fragment meshes that came from this container\n for(const [ fragmentPath, mesh ] of this._sourceMeshes)\n {\n if(fragmentPath.startsWith(`${ basePath }#`))\n {\n mesh.dispose();\n this._sourceMeshes.delete(fragmentPath);\n }\n }\n\n cached.container.dispose();\n cached.scene.dispose();\n this._containers.delete(basePath);\n }\n }\n\n async dispose() : Promise<void>\n {\n for(const cached of this._containers.values())\n {\n cached.container.dispose();\n cached.scene.dispose();\n }\n\n this._containers.clear();\n this._sourceMeshes.clear();\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Game Manager\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { AbstractEngine, Scene } from '@babylonjs/core';\n\n// Interfaces\nimport type { Disposable } from '../interfaces/lifecycle.ts';\nimport type { LoggerInterface } from '../interfaces/logger.ts';\n\n// Classes\nimport type { EventBus } from '@skewedaspect/sage-core';\nimport type { GameEngine } from '../classes/gameEngine.ts';\n\n// Managers\nimport type { InputManager } from './input.ts';\nimport type { LevelManager } from './level.ts';\nimport type { NodeEntityRegistry } from '../entities/nodeSimulation.ts';\n\n// Utils\nimport { isBrowser } from '../utils/capabilities.ts';\nimport { type LoggingUtility, SAGELogger } from '../utils/logger.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/** Run once per rendered frame, with the frame's elapsed time in seconds. */\nexport type FrameCallback = (elapsed : number) => void;\n\n/** How a frame callback answers to pause (design/ui.md#when-a-view-observes). */\nexport interface FrameCallbackOptions\n{\n suspendedByPause ?: boolean;\n}\n\ninterface RegisteredFrameCallback\n{\n id : number;\n callback : FrameCallback;\n suspendedByPause : boolean;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport class GameManager<Registry extends NodeEntityRegistry = NodeEntityRegistry> implements Disposable\n{\n #renderEngine : AbstractEngine;\n #bus : EventBus;\n #levelManager : LevelManager<Registry>;\n #log : LoggerInterface;\n\n #gameEngine : GameEngine<Registry> | null = null;\n #inputManager : InputManager | null = null;\n\n #frameCallbacks : RegisteredFrameCallback[] = [];\n #nextCallbackID = 0;\n\n #paused = false;\n #timeScale = 1;\n\n #boundRenderLoop : () => void;\n #boundResizeHandler : () => void;\n\n public started = false;\n\n //------------------------------------------------------------------------------------------------------------------\n\n constructor(\n renderEngine : AbstractEngine,\n bus : EventBus,\n levelManager : LevelManager<Registry>,\n logger ?: LoggingUtility\n )\n {\n this.#renderEngine = renderEngine;\n this.#bus = bus;\n this.#levelManager = levelManager;\n this.#log = logger?.getLogger('GameManager') || new SAGELogger('GameManager');\n\n this.#boundRenderLoop = () => { this.#renderFrame(); };\n this.#boundResizeHandler = () => { this.#handleResize(); };\n\n if(isBrowser())\n {\n window.addEventListener('resize', this.#boundResizeHandler);\n }\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Accessors\n //------------------------------------------------------------------------------------------------------------------\n\n get currentScene() : Scene | null\n {\n return this.#levelManager.currentLevel?.scene ?? null;\n }\n\n get isPaused() : boolean\n {\n return this.#paused;\n }\n\n /** The multiplier on the elapsed time the frame hands the simulation (design/engine.md#time-scale). */\n get timeScale() : number\n {\n return this.#timeScale;\n }\n\n set timeScale(scale : number)\n {\n if(!Number.isFinite(scale) || scale < 0)\n {\n throw new RangeError(`timeScale requires a finite scale >= 0, got ${ scale }`);\n }\n\n this.#timeScale = scale;\n this.#log.debug(`Time scale set to ${ scale }`);\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Late binding\n //------------------------------------------------------------------------------------------------------------------\n\n // The engine takes its managers as constructor arguments, so it is built after this one; nothing here reads it\n // before the first frame, which is later still.\n $setGameEngine(gameEngine : GameEngine<Registry>) : void\n {\n this.#gameEngine = gameEngine;\n }\n\n // The routing loop's caller-side half (design/input.md#the-routing-loop): the frame calls\n // `routeCapturedInput` on whatever is registered here immediately before `runStepsForElapsedTime`,\n // once a simulation exists.\n setInputManager(inputManager : InputManager) : void\n {\n this.#inputManager = inputManager;\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Frame callbacks\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Registers a function to run once per rendered frame, in registration order, and answers with a function that\n * removes that one callback (design/engine.md#frame-callbacks).\n */\n registerFrameCallback(callback : FrameCallback, options ?: FrameCallbackOptions) : () => void\n {\n const id = this.#nextCallbackID;\n this.#nextCallbackID += 1;\n\n this.#frameCallbacks.push({ id, callback, suspendedByPause: options?.suspendedByPause ?? true });\n this.#log.debug(`Registered frame callback (id: ${ id })`);\n\n return () =>\n {\n const index = this.#frameCallbacks.findIndex((registered) => registered.id === id);\n\n if(index !== -1)\n {\n this.#frameCallbacks.splice(index, 1);\n this.#log.debug(`Unregistered frame callback (id: ${ id })`);\n }\n };\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // The frame\n //------------------------------------------------------------------------------------------------------------------\n\n #renderFrame() : void\n {\n const elapsed = this.#renderEngine.getDeltaTime() / 1000;\n const scaledElapsed = elapsed * this.#timeScale;\n\n let steppedThisFrame = false;\n\n if(!this.#paused)\n {\n if(this.#gameEngine === null)\n {\n throw new Error('GameManager: gameEngine not set. Call $setGameEngine() first.');\n }\n\n // No simulation is bound until a level is activated, or a game binds what it built itself.\n const simulation = this.#gameEngine.simulation;\n\n if(simulation !== null)\n {\n if(this.#inputManager !== null)\n {\n this.#inputManager.routeCapturedInput(simulation);\n }\n\n // Not wrapped in a try/catch: a throw out of an entity's update is a defect in a behavior, and it\n // leaves the frame loop rather than being carried on as news (design/simulation.md#failure).\n const tickBefore = simulation.tick;\n simulation.runStepsForElapsedTime(scaledElapsed);\n steppedThisFrame = simulation.tick !== tickBefore;\n }\n }\n\n // The fixed step drains the bus; on a frame no step ran this drains once where the step's own drain would\n // have fallen, so a frame callback sees the same delivered events either way (design/events.md#the-drain).\n if(!steppedThisFrame)\n {\n this.#bus.drain();\n }\n\n this.#runFrameCallbacks(elapsed, scaledElapsed);\n\n // Rendering is untouched by pause and by the time scale, so the last frame stays on the screen.\n if(this.currentScene !== null)\n {\n this.currentScene.render();\n }\n }\n\n // A callback that answers to pause reads game time; one registered to run through pause reads the wall clock,\n // because presentation follows the wall clock (design/engine.md#time-scale).\n #runFrameCallbacks(elapsed : number, scaledElapsed : number) : void\n {\n for(const { callback, suspendedByPause } of this.#frameCallbacks)\n {\n const suspended = this.#paused && suspendedByPause;\n\n if(!suspended)\n {\n let handed = elapsed;\n\n if(suspendedByPause)\n {\n handed = scaledElapsed;\n }\n\n try\n {\n callback(handed);\n }\n catch (err)\n {\n this.#log.error('Error in frame callback:', err);\n }\n }\n }\n }\n\n #handleResize() : void\n {\n if(this.started)\n {\n this.#renderEngine.resize();\n }\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Public API\n //------------------------------------------------------------------------------------------------------------------\n\n async start() : Promise<void>\n {\n this.#renderEngine.runRenderLoop(this.#boundRenderLoop);\n this.started = true;\n\n this.#log.info('SkewedAspect Game Engine started successfully');\n }\n\n async stop() : Promise<void>\n {\n this.started = false;\n this.#renderEngine.stopRenderLoop();\n\n this.#log.info('SkewedAspect Game Engine stopped successfully');\n }\n\n /** Suspends the routing loop, the fixed step, and every frame callback that did not opt out. */\n pause(reason ?: string) : void\n {\n if(this.#paused)\n {\n this.#log.debug('Game is already paused');\n return;\n }\n\n this.#paused = true;\n\n let reasonSuffix = '';\n if(reason)\n {\n reasonSuffix = `(reason: ${ reason })`;\n }\n\n this.#log.info('Game paused', reasonSuffix);\n\n this.#bus.emit({\n type: 'game:paused',\n senderID: 'gameManager',\n payload: { reason },\n });\n }\n\n /** Puts the routing loop, the fixed step, and the suspended frame callbacks back. */\n resume(reason ?: string) : void\n {\n if(!this.#paused)\n {\n this.#log.debug('Game is not paused');\n return;\n }\n\n this.#paused = false;\n\n let reasonSuffix = '';\n if(reason)\n {\n reasonSuffix = `(reason: ${ reason })`;\n }\n\n this.#log.info('Game resumed', reasonSuffix);\n\n this.#bus.emit({\n type: 'game:resumed',\n senderID: 'gameManager',\n payload: { reason },\n });\n }\n\n async dispose() : Promise<void>\n {\n this.#log.info('Tearing down GameManager');\n\n if(this.started)\n {\n await this.stop();\n }\n\n if(isBrowser())\n {\n window.removeEventListener('resize', this.#boundResizeHandler);\n }\n\n this.#frameCallbacks = [];\n\n this.#log.info('GameManager torn down successfully');\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Action Context\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { LoggerInterface } from '../interfaces/logger.ts';\nimport type { Binding } from './binding.ts';\n\nimport { type LoggingUtility, SAGELogger } from '../utils/logger.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\ninterface ContextEntry\n{\n readonly exclusive : boolean;\n active : boolean;\n}\n\n// A named group of bindings that activate and deactivate together (input.md#action-contexts).\nexport class ActionContextRegistry\n{\n readonly #contexts = new Map<string, ContextEntry>();\n readonly #bindings : Binding<unknown>[] = [];\n readonly #log : LoggerInterface;\n\n constructor(logger ?: LoggingUtility)\n {\n this.#log = logger?.getLogger('ActionContextRegistry') ?? new SAGELogger('ActionContextRegistry');\n }\n\n // Exclusive by default. Registering over a name already registered warns and replaces it, the way the\n // level manager's own registries do (input.md#action-contexts).\n registerContext(name : string, exclusive = true) : void\n {\n if(this.#contexts.has(name))\n {\n this.#log.warn(`Action context '${ name }' is already registered. Overwriting.`);\n }\n\n this.#contexts.set(name, { exclusive, active: false });\n }\n\n // Activating an exclusive context deactivates every other exclusive one. Primes every member binding\n // against the current state the caller reports for it, so an input already held does not read as a\n // fresh press.\n activateContext(name : string, currentState : (binding : Binding<unknown>) => unknown) : void\n {\n const context = this.#requireContext(name);\n\n if(context.exclusive)\n {\n for(const [ otherName, other ] of this.#contexts)\n {\n if(otherName !== name && other.exclusive)\n {\n other.active = false;\n }\n }\n }\n\n context.active = true;\n\n for(const binding of this.#bindings)\n {\n if(binding.context === name)\n {\n binding.prime(currentState(binding));\n }\n }\n }\n\n deactivateContext(name : string) : void\n {\n this.#requireContext(name).active = false;\n }\n\n isActive(name : string) : boolean\n {\n return this.#contexts.get(name)?.active === true;\n }\n\n isRegistered(name : string) : boolean\n {\n return this.#contexts.has(name);\n }\n\n // Registers a binding under its own named context, or under none.\n addBinding(binding : Binding<unknown>) : void\n {\n this.#bindings.push(binding);\n }\n\n // Retires a binding entirely: the routing loop stops delivering through it and the next context\n // activation stops priming it. Idempotent -- removing a binding not held, or removing it twice, changes\n // nothing (input.md#action-contexts).\n removeBinding(binding : Binding<unknown>) : void\n {\n const index = this.#bindings.indexOf(binding);\n\n if(index !== -1)\n {\n this.#bindings.splice(index, 1);\n }\n }\n\n // Every binding currently registered, in the order it was added -- the same list the routing loop walks\n // and a context activation primes (input.md#action-contexts).\n list() : readonly Binding<unknown>[]\n {\n return [ ...this.#bindings ];\n }\n\n // True when the binding names no context, or names one that is active.\n isLive(binding : Binding<unknown>) : boolean\n {\n if(binding.context === undefined)\n {\n return true;\n }\n\n return this.isActive(binding.context);\n }\n\n #requireContext(name : string) : ContextEntry\n {\n const context = this.#contexts.get(name);\n\n if(context === undefined)\n {\n throw new Error(`No context is registered under the name \"${ name }\".`);\n }\n\n return context;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Action Registry\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { Action } from '@skewedaspect/sage-core';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// An action is registered by name before anything binds to it (input.md#actions).\nexport class ActionRegistry\n{\n readonly #actions = new Map<string, Action>();\n\n register(action : Action) : void\n {\n if(this.#actions.has(action.name))\n {\n throw new Error(`An action is already registered under the name \"${ action.name }\".`);\n }\n\n if(action.type === 'analog' && action.minValue !== undefined && action.maxValue !== undefined)\n {\n if(action.minValue > action.maxValue)\n {\n throw new Error(\n `Action \"${ action.name }\": minValue (${ action.minValue }) is above `\n + `maxValue (${ action.maxValue }).`\n );\n }\n }\n\n this.#actions.set(action.name, action);\n }\n\n get(name : string) : Action | undefined\n {\n return this.#actions.get(name);\n }\n\n list() : readonly Action[]\n {\n return [ ...this.#actions.values() ];\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Delivering an Action\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { Action, GameEvent, GameSimulationView } from '@skewedaspect/sage-core';\n\nimport type { Binding } from './binding.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// A fired binding reaches every subscribed entity as a broadcast `action:<name>` event, carrying the\n// boolean or number the binding computed -- deciding which entities answer is game logic, not something\n// this delivers (events.md#delivery, input.md#actions).\nexport function deliverAction<State>(\n binding : Binding<State>,\n state : State,\n context : GameSimulationView,\n senderID : string\n) : void\n{\n const fire = binding.process(state);\n\n if(fire === undefined)\n {\n return;\n }\n\n // The action name is only known at runtime, so the event map can never narrow `type` to a literal here --\n // the same gap sage-core's own GameEntity.emit() bridges by widening payload to `unknown` before the cast.\n const event : { type : string; senderID : string; payload : unknown } = {\n type: `action:${ binding.action.name }`,\n senderID,\n payload: fire.value,\n };\n\n context.bus.emit(event as GameEvent);\n}\n\n// A value action reaches every subscribed entity the same way, as a broadcast carrying the value the\n// routing loop computed for it. No one binding decides that value, so this takes the action and the value\n// rather than a binding (input.md#value-actions, input.md#the-routing-loop).\nexport function deliverValueAction(\n action : Action,\n value : number,\n context : GameSimulationView,\n senderID : string\n) : void\n{\n const event : { type : string; senderID : string; payload : unknown } = {\n type: `action:${ action.name }`,\n senderID,\n payload: value,\n };\n\n context.bus.emit(event as GameEvent);\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Gamepad Device Reader\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { GamepadState } from '../deviceState.ts';\nimport type { DeviceReader } from '../deviceReader.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport type GamepadSourceType = 'button' | 'axis';\n\n// Reads a gamepad button or stick axis (input.md#device-readers).\nexport class GamepadReader implements DeviceReader<GamepadState>\n{\n readonly sourceType : GamepadSourceType;\n readonly sourceKey : string;\n\n constructor(sourceType : GamepadSourceType, sourceKey : string)\n {\n this.sourceType = sourceType;\n this.sourceKey = sourceKey;\n }\n\n getValue(state : GamepadState) : boolean | number | undefined\n {\n if(this.sourceType === 'button')\n {\n return state.buttons[this.sourceKey];\n }\n\n if(this.sourceType === 'axis')\n {\n return state.axes[this.sourceKey];\n }\n\n return undefined;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Keyboard Device Reader\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { KeyboardState } from '../deviceState.ts';\nimport type { DeviceReader } from '../deviceReader.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// Reads one key by its DOM code (input.md#device-readers).\nexport class KeyboardReader implements DeviceReader<KeyboardState>\n{\n readonly sourceType = 'key';\n readonly sourceKey : string;\n\n constructor(sourceKey : string)\n {\n this.sourceKey = sourceKey;\n }\n\n getValue(state : KeyboardState) : boolean | undefined\n {\n return state.keys[this.sourceKey];\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Mouse Device Reader\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { MouseState } from '../deviceState.ts';\nimport type { DeviceReader } from '../deviceReader.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport type MouseSourceType = 'button' | 'position' | 'wheel' | 'movement';\n\n// Reads a mouse button, the cursor position, or a wheel axis (input.md#device-readers).\nexport class MouseReader implements DeviceReader<MouseState>\n{\n readonly sourceType : MouseSourceType;\n readonly sourceKey : string;\n\n constructor(sourceType : MouseSourceType, sourceKey : string)\n {\n this.sourceType = sourceType;\n this.sourceKey = sourceKey;\n }\n\n getValue(state : MouseState) : boolean | number | undefined\n {\n if(this.sourceType === 'button')\n {\n return state.buttons[this.sourceKey];\n }\n\n if(this.sourceType === 'movement')\n {\n if(this.sourceKey === 'x')\n {\n return state.movement.x;\n }\n\n if(this.sourceKey === 'y')\n {\n return state.movement.y;\n }\n\n return undefined;\n }\n\n if(this.sourceType === 'position')\n {\n if(this.sourceKey === 'x')\n {\n return state.position.x;\n }\n\n if(this.sourceKey === 'y')\n {\n return state.position.y;\n }\n\n return undefined;\n }\n\n if(this.sourceType === 'wheel')\n {\n if(this.sourceKey === 'deltaX')\n {\n return state.wheel.deltaX;\n }\n\n if(this.sourceKey === 'deltaY')\n {\n return state.wheel.deltaY;\n }\n\n if(this.sourceKey === 'deltaZ')\n {\n return state.wheel.deltaZ;\n }\n\n return undefined;\n }\n\n return undefined;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Device Reader Factory\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { BindingInput } from './binding.ts';\nimport type { DeviceReader } from './deviceReader.ts';\n\nimport { GamepadReader, type GamepadSourceType } from './deviceReaders/gamepad.ts';\nimport { KeyboardReader } from './deviceReaders/keyboard.ts';\nimport { MouseReader, type MouseSourceType } from './deviceReaders/mouse.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nfunction isMouseSourceType(sourceType : string) : sourceType is MouseSourceType\n{\n return sourceType === 'button' || sourceType === 'position' || sourceType === 'wheel'\n || sourceType === 'movement';\n}\n\nfunction isGamepadSourceType(sourceType : string) : sourceType is GamepadSourceType\n{\n return sourceType === 'button' || sourceType === 'axis';\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// Builds the reader a binding input names: a keyboard key by its DOM code, a mouse button, cursor position,\n// wheel axis, or movement axis, a gamepad button or stick axis (input.md#device-readers).\nexport function readerFor(input : BindingInput) : DeviceReader<unknown>\n{\n if(input.device.type === 'keyboard' && input.sourceType === 'key')\n {\n return new KeyboardReader(input.sourceKey);\n }\n\n if(input.device.type === 'mouse' && isMouseSourceType(input.sourceType))\n {\n return new MouseReader(input.sourceType, input.sourceKey);\n }\n\n if(input.device.type === 'gamepad' && isGamepadSourceType(input.sourceType))\n {\n return new GamepadReader(input.sourceType, input.sourceKey);\n }\n\n throw new Error(`A ${ input.device.type } reads nothing named by the source type \"${ input.sourceType }\".`);\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Trigger Binding\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { Action } from '@skewedaspect/sage-core';\n\nimport type { Binding, BindingDefinition, BindingFire, BindingInput, DeviceID } from '../binding.ts';\nimport type { DeviceReader } from '../deviceReader.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport type EdgeMode = 'rising' | 'falling' | 'both';\n\nexport interface TriggerOptions\n{\n edge ?: EdgeMode;\n threshold ?: number;\n\n /** Takes this input from the browser while the binding is live (input.md#claiming-an-input-from-the-browser). */\n preventDefault ?: boolean;\n}\n\n// Fires once on the rising edge, the falling edge, or both. An analog input crossing the threshold counts\n// as the change (input.md#bindings).\nexport class TriggerBinding<State> implements Binding<State>\n{\n readonly kind = 'trigger' as const;\n readonly action : Action;\n readonly device : DeviceID;\n readonly context ?: string;\n readonly preventDefault : boolean;\n\n readonly #reader : DeviceReader<State>;\n readonly #edge : EdgeMode;\n readonly #threshold ?: number;\n\n // undefined until the first process()/prime() call establishes a baseline, so that call never fires.\n #pressed : boolean | undefined;\n\n constructor(\n action : Action,\n device : DeviceID,\n reader : DeviceReader<State>,\n options : TriggerOptions = {},\n context ?: string\n )\n {\n this.action = action;\n this.device = device;\n this.context = context;\n this.preventDefault = options.preventDefault ?? false;\n\n this.#reader = reader;\n this.#edge = options.edge ?? 'rising';\n this.#threshold = options.threshold;\n }\n\n get input() : BindingInput\n {\n return { device: this.device, sourceType: this.#reader.sourceType, sourceKey: this.#reader.sourceKey };\n }\n\n process(state : State) : BindingFire | undefined\n {\n const pressed = this.#isPressed(this.#reader.getValue(state));\n const previous = this.#pressed;\n\n this.#pressed = pressed;\n\n if(previous === undefined)\n {\n return undefined;\n }\n\n if(!this.#crossesEdge(previous, pressed))\n {\n return undefined;\n }\n\n if(this.action.type === 'analog')\n {\n return { value: this.#numericValue(this.#reader.getValue(state)) };\n }\n\n return { value: pressed };\n }\n\n prime(state : State) : void\n {\n this.#pressed = this.#isPressed(this.#reader.getValue(state));\n }\n\n toDefinition() : BindingDefinition\n {\n const options : Record<string, unknown> = { edge: this.#edge };\n\n if(this.#threshold !== undefined)\n {\n options.threshold = this.#threshold;\n }\n\n if(this.preventDefault)\n {\n options.preventDefault = true;\n }\n\n return {\n kind: 'trigger',\n action: this.action.name,\n input: this.input,\n context: this.context,\n options,\n };\n }\n\n #crossesEdge(previous : boolean, pressed : boolean) : boolean\n {\n if(this.#edge === 'rising')\n {\n return !previous && pressed;\n }\n\n if(this.#edge === 'falling')\n {\n return previous && !pressed;\n }\n\n return previous !== pressed;\n }\n\n #isPressed(raw : boolean | number | undefined) : boolean\n {\n if(typeof raw === 'boolean')\n {\n return raw;\n }\n\n if(typeof raw === 'number')\n {\n if(this.#threshold !== undefined)\n {\n return raw >= this.#threshold;\n }\n\n return raw !== 0;\n }\n\n return false;\n }\n\n #numericValue(raw : boolean | number | undefined) : number\n {\n if(typeof raw === 'number')\n {\n return raw;\n }\n\n if(raw === true)\n {\n return 1;\n }\n\n return 0;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Toggle Binding\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { Action } from '@skewedaspect/sage-core';\n\nimport type { Binding, BindingDefinition, BindingFire, BindingInput, DeviceID } from '../binding.ts';\nimport type { DeviceReader } from '../deviceReader.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport interface ToggleOptions\n{\n onValue ?: boolean | number;\n offValue ?: boolean | number;\n initialState ?: boolean;\n threshold ?: number;\n invert ?: boolean;\n\n /** Takes this input from the browser while the binding is live (input.md#claiming-an-input-from-the-browser). */\n preventDefault ?: boolean;\n}\n\n// Alternates each time its input is pressed and holds its state between presses (input.md#bindings).\nexport class ToggleBinding<State> implements Binding<State>\n{\n readonly kind = 'toggle' as const;\n readonly action : Action;\n readonly device : DeviceID;\n readonly context ?: string;\n readonly preventDefault : boolean;\n\n readonly #reader : DeviceReader<State>;\n readonly #onValue : boolean | number;\n readonly #offValue : boolean | number;\n readonly #threshold ?: number;\n readonly #invert : boolean;\n\n // No declared default (input.md#bindings): left undeclared, the on/off bit that alternation flips starts\n // undefined, and the first flip lands on true the same way any boolean negation of undefined does.\n #isOn : boolean | undefined;\n\n // What the game declared, written back as declared: where the player has left the toggle since is play\n // state, not configuration (design/input.md#bindings).\n readonly #initialState : boolean | undefined;\n\n // undefined until the first process()/prime() call establishes a baseline, so that call never fires.\n #previousEffective : boolean | undefined;\n\n constructor(\n action : Action,\n device : DeviceID,\n reader : DeviceReader<State>,\n options : ToggleOptions = {},\n context ?: string\n )\n {\n this.action = action;\n this.device = device;\n this.context = context;\n this.preventDefault = options.preventDefault ?? false;\n\n this.#reader = reader;\n this.#onValue = options.onValue ?? true;\n this.#offValue = options.offValue ?? false;\n this.#threshold = options.threshold;\n this.#invert = options.invert ?? false;\n this.#isOn = options.initialState;\n this.#initialState = options.initialState;\n }\n\n get input() : BindingInput\n {\n return { device: this.device, sourceType: this.#reader.sourceType, sourceKey: this.#reader.sourceKey };\n }\n\n process(state : State) : BindingFire | undefined\n {\n const effective = this.#effective(this.#reader.getValue(state));\n const previous = this.#previousEffective;\n\n this.#previousEffective = effective;\n\n if(previous === undefined)\n {\n return undefined;\n }\n\n if(previous || !effective)\n {\n return undefined;\n }\n\n this.#isOn = !this.#isOn;\n\n if(this.#isOn)\n {\n return { value: this.#onValue };\n }\n\n return { value: this.#offValue };\n }\n\n prime(state : State) : void\n {\n this.#previousEffective = this.#effective(this.#reader.getValue(state));\n }\n\n toDefinition() : BindingDefinition\n {\n const options : Record<string, unknown> = {\n onValue: this.#onValue,\n offValue: this.#offValue,\n invert: this.#invert,\n };\n\n if(this.#threshold !== undefined)\n {\n options.threshold = this.#threshold;\n }\n\n if(this.#initialState !== undefined)\n {\n options.initialState = this.#initialState;\n }\n\n if(this.preventDefault)\n {\n options.preventDefault = true;\n }\n\n return {\n kind: 'toggle',\n action: this.action.name,\n input: this.input,\n context: this.context,\n options,\n };\n }\n\n // The pressed state, inverted when the binding alternates on a release instead of a press.\n #effective(raw : boolean | number | undefined) : boolean\n {\n const pressed = this.#isPressed(raw);\n\n if(this.#invert)\n {\n return !pressed;\n }\n\n return pressed;\n }\n\n #isPressed(raw : boolean | number | undefined) : boolean\n {\n if(typeof raw === 'boolean')\n {\n return raw;\n }\n\n if(typeof raw === 'number')\n {\n if(this.#threshold !== undefined)\n {\n return raw >= this.#threshold;\n }\n\n return raw !== 0;\n }\n\n return false;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Value Binding\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { Action } from '@skewedaspect/sage-core';\n\nimport type { Binding, BindingDefinition, BindingFire, BindingInput, DeviceID } from '../binding.ts';\nimport type { DeviceReader } from '../deviceReader.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport interface ValueOptions\n{\n scale ?: number;\n offset ?: number;\n invert ?: boolean;\n deadzone ?: number;\n min ?: number;\n max ?: number;\n\n /** Takes this input from the browser while the binding is live (input.md#claiming-an-input-from-the-browser). */\n preventDefault ?: boolean;\n}\n\n// Passes its input through continuously: scales, offsets, clamps, inverts, and ignores magnitudes inside\n// a deadzone. What it computes is a contribution to its action's value, not a delivery of its own, so it\n// always answers with one (input.md#bindings, input.md#value-actions).\nexport class ValueBinding<State> implements Binding<State>\n{\n readonly kind = 'value' as const;\n readonly action : Action;\n readonly device : DeviceID;\n readonly context ?: string;\n readonly preventDefault : boolean;\n\n readonly #reader : DeviceReader<State>;\n readonly #scale : number;\n readonly #offset : number;\n readonly #invert : boolean;\n readonly #deadzone : number;\n readonly #min ?: number;\n readonly #max ?: number;\n\n constructor(\n action : Action,\n device : DeviceID,\n reader : DeviceReader<State>,\n options : ValueOptions = {},\n context ?: string\n )\n {\n this.action = action;\n this.device = device;\n this.context = context;\n this.preventDefault = options.preventDefault ?? false;\n\n this.#reader = reader;\n this.#scale = options.scale ?? 1;\n this.#offset = options.offset ?? 0;\n this.#invert = options.invert ?? false;\n this.#deadzone = options.deadzone ?? 0;\n this.#min = options.min;\n this.#max = options.max;\n }\n\n get input() : BindingInput\n {\n return { device: this.device, sourceType: this.#reader.sourceType, sourceKey: this.#reader.sourceKey };\n }\n\n process(state : State) : BindingFire\n {\n return { value: this.#compute(state) };\n }\n\n // A value binding has no edge to miss, so an input already held when its context activates needs no\n // baseline: it reports its value on the next pass like any other (input.md#action-contexts).\n prime(_state : State) : void\n {\n // Nothing to prime.\n }\n\n toDefinition() : BindingDefinition\n {\n const options : Record<string, unknown> = {\n scale: this.#scale,\n offset: this.#offset,\n invert: this.#invert,\n deadzone: this.#deadzone,\n };\n\n if(this.#min !== undefined)\n {\n options.min = this.#min;\n }\n\n if(this.#max !== undefined)\n {\n options.max = this.#max;\n }\n\n if(this.preventDefault)\n {\n options.preventDefault = true;\n }\n\n return {\n kind: 'value',\n action: this.action.name,\n input: this.input,\n context: this.context,\n options,\n };\n }\n\n #compute(state : State) : number\n {\n let value = this.#toNumber(this.#reader.getValue(state));\n\n value = value * this.#scale;\n value = value + this.#offset;\n\n if(this.#min !== undefined && value < this.#min)\n {\n value = this.#min;\n }\n\n if(this.#max !== undefined && value > this.#max)\n {\n value = this.#max;\n }\n\n if(this.#invert)\n {\n value = -value;\n }\n\n if(Math.abs(value) < this.#deadzone)\n {\n value = 0;\n }\n\n return value;\n }\n\n #toNumber(raw : boolean | number | undefined) : number\n {\n if(typeof raw === 'number')\n {\n return raw;\n }\n\n if(raw === true)\n {\n return 1;\n }\n\n return 0;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Delta Binding\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { Action } from '@skewedaspect/sage-core';\n\nimport type { Binding, BindingDefinition, BindingFire, BindingInput, DeviceID } from '../binding.ts';\nimport type { DeviceReader } from '../deviceReader.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport interface DeltaOptions\n{\n scale ?: number;\n invert ?: boolean;\n deadzone ?: number;\n\n /** Takes this input from the browser while the binding is live (input.md#claiming-an-input-from-the-browser). */\n preventDefault ?: boolean;\n}\n\n// Fires once for a one-shot sample its input reports -- a wheel notch, a frame of raw mouse movement --\n// treating the sample as consumed. Applies scale, invert, and deadzone; never offset or clamp\n// (input.md#bindings).\nexport class DeltaBinding<State> implements Binding<State>\n{\n readonly kind = 'delta' as const;\n readonly action : Action;\n readonly device : DeviceID;\n readonly context ?: string;\n readonly preventDefault : boolean;\n\n readonly #reader : DeviceReader<State>;\n readonly #scale : number;\n readonly #invert : boolean;\n readonly #deadzone : number;\n\n constructor(\n action : Action,\n device : DeviceID,\n reader : DeviceReader<State>,\n options : DeltaOptions = {},\n context ?: string\n )\n {\n this.action = action;\n this.device = device;\n this.context = context;\n this.preventDefault = options.preventDefault ?? false;\n\n this.#reader = reader;\n this.#scale = options.scale ?? 1;\n this.#invert = options.invert ?? false;\n this.#deadzone = options.deadzone ?? 0;\n }\n\n get input() : BindingInput\n {\n return { device: this.device, sourceType: this.#reader.sourceType, sourceKey: this.#reader.sourceKey };\n }\n\n process(state : State) : BindingFire | undefined\n {\n let value = this.#toNumber(this.#reader.getValue(state));\n\n value = value * this.#scale;\n\n if(this.#invert)\n {\n value = -value;\n }\n\n if(Math.abs(value) < this.#deadzone)\n {\n value = 0;\n }\n\n // No held value to compare against -- a delta fires whenever the sample computes nonzero, every\n // time, and stays silent otherwise (input.md#bindings).\n if(value === 0)\n {\n return undefined;\n }\n\n return { value };\n }\n\n // A delta has no held state to prime -- it fires from a fresh sample every time, never from what a\n // context activation reports (input.md#bindings).\n prime(_state : State) : void\n {\n // Nothing to prime.\n }\n\n toDefinition() : BindingDefinition\n {\n const options : Record<string, unknown> = {\n scale: this.#scale, invert: this.#invert, deadzone: this.#deadzone,\n };\n\n if(this.preventDefault)\n {\n options.preventDefault = true;\n }\n\n return {\n kind: 'delta',\n action: this.action.name,\n input: this.input,\n context: this.context,\n options,\n };\n }\n\n #toNumber(raw : boolean | number | undefined) : number\n {\n if(typeof raw === 'number')\n {\n return raw;\n }\n\n if(raw === true)\n {\n return 1;\n }\n\n return 0;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Binding Resolution\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { Action } from '@skewedaspect/sage-core';\n\nimport type { Binding, BindingDefinition } from './binding.ts';\nimport type { DeviceReader } from './deviceReader.ts';\n\nimport { readerFor } from './readerFor.ts';\n\nimport { TriggerBinding, type TriggerOptions } from './bindings/trigger.ts';\nimport { ToggleBinding, type ToggleOptions } from './bindings/toggle.ts';\nimport { ValueBinding, type ValueOptions } from './bindings/value.ts';\nimport { DeltaBinding, type DeltaOptions } from './bindings/delta.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// Builds the live binding a definition describes: the kind picks trigger, toggle, value, or delta, and the\n// definition's own input names the reader. A caller reading a device state shape of its own hands that\n// reader in instead (input.md#bindings, input.md#device-readers).\nexport function resolveBinding<State>(\n definition : BindingDefinition,\n action : Action,\n reader ?: DeviceReader<State>\n) : Binding<State>\n{\n const device = definition.input.device;\n const options = definition.options ?? {};\n const resolved = reader ?? readerFor(definition.input);\n\n if(definition.kind === 'trigger')\n {\n return new TriggerBinding(action, device, resolved, options as TriggerOptions, definition.context);\n }\n\n if(definition.kind === 'toggle')\n {\n return new ToggleBinding(action, device, resolved, options as ToggleOptions, definition.context);\n }\n\n if(definition.kind === 'delta')\n {\n return new DeltaBinding(action, device, resolved, options as DeltaOptions, definition.context);\n }\n\n return new ValueBinding(action, device, resolved, options as ValueOptions, definition.context);\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Input Scheme\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { DeviceType } from './binding.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// One way of playing. Every device type belongs to exactly one (input.md#input-schemes).\nexport type InputScheme = 'keyboard-mouse' | 'gamepad';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// The scheme the named device type belongs to (input.md#input-schemes).\nexport function schemeOf(deviceType : DeviceType) : InputScheme\n{\n if(deviceType === 'gamepad')\n {\n return 'gamepad';\n }\n\n return 'keyboard-mouse';\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Input Manager\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { Action, GameSimulationView, Unsubscribe } from '@skewedaspect/sage-core';\n\n// Interfaces\nimport type { Disposable } from '../interfaces/lifecycle.ts';\n\n// Input\nimport type { Binding, BindingDefinition, DeviceType } from '../input/binding.ts';\nimport type { CandidateInput, InputCapture } from '../input/inputCapture.ts';\nimport type { GamepadState, KeyboardState, MouseState } from '../input/deviceState.ts';\n\nimport type { InputScheme } from '../input/scheme.ts';\n\nimport { ActionContextRegistry } from '../input/actionContext.ts';\nimport { ActionRegistry } from '../input/actionRegistry.ts';\nimport { deliverAction, deliverValueAction } from '../input/deliverAction.ts';\nimport { resolveBinding } from '../input/resolveBinding.ts';\nimport { schemeOf } from '../input/scheme.ts';\n\n// Utils\nimport type { LoggingUtility } from '../utils/logger.ts';\n\nimport { isBrowser } from '../utils/capabilities.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// `input:deviceChanged` and `input:schemeChanged` are the library's own events, so `sage` augments the map\n// core declares (events.md#the-event-map).\ndeclare module '@skewedaspect/sage-core'\n{\n interface EventMap\n {\n 'input:deviceChanged' : DeviceChangedPayload;\n 'input:schemeChanged' : SchemeChangedPayload;\n }\n}\n\nexport interface DeviceChangedPayload\n{\n deviceType : DeviceType;\n\n /** Undefined for the first deliberate input of a session, which replaces no device type. */\n previousDeviceType : DeviceType | undefined;\n}\n\nexport interface SchemeChangedPayload\n{\n scheme : InputScheme;\n\n /** Undefined for the first deliberate input of a session, which replaces no scheme. */\n previousScheme : InputScheme | undefined;\n}\n\n// The routing loop's own emissions carry this fixed sender, naming the routing loop itself and no entity,\n// so a test or a behavior can compare against it (input.md#the-routing-loop).\nexport const ROUTING_LOOP_SENDER_ID = 'sage-input';\n\n// An analog input is deliberate once its distance from rest passes this; a stick at rest drifts below it\n// for as long as its gamepad is connected (input.md#device-state-capture).\nconst DELIBERATE_THRESHOLD = 0.2;\n\n// At rest. A gamepad button or axis at exactly this value is not deliberate, the same threshold InputCapture\n// applies to a candidate, and it is where a value action rests before the action's own bounds clamp it\n// (input.md#device-state-capture, input.md#value-actions).\nconst REST_VALUE = 0;\n\n// Raw mouse movement is measured in pixels, where the deliberate threshold would be no threshold at all\n// (input.md#device-state-capture).\nconst MOVEMENT_THRESHOLD = 2;\n\n// A gamepad reports its buttons as numbers, so a worn trigger is held to the threshold the same as a stick.\nfunction isDeliberate(value : number) : boolean\n{\n return Math.abs(value) > DELIBERATE_THRESHOLD;\n}\n\nfunction isDeliberateMovement(x : number, y : number) : boolean\n{\n return Math.abs(x) > MOVEMENT_THRESHOLD || Math.abs(y) > MOVEMENT_THRESHOLD;\n}\n\ninterface MutableGamepadState\n{\n buttons : Record<string, number>;\n axes : Record<string, number>;\n}\n\n// One action's running total over the live value bindings driving it, for the pass being routed\n// (input.md#value-actions).\ninterface ValueActionSum\n{\n action : Action;\n value : number;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// `sage`'s owner of input: one action registry, one action context registry, device state capture, and the\n// routing loop that runs before each frame's fixed steps (input.md).\nexport class InputManager implements Disposable\n{\n readonly actions = new ActionRegistry();\n readonly contexts : ActionContextRegistry;\n\n readonly #keys : Record<string, boolean> = {};\n readonly #mouseButtons : Record<string, boolean> = {};\n readonly #mousePosition = { x: 0, y: 0 };\n readonly #mouseWheel = { deltaX: 0, deltaY: 0, deltaZ: 0 };\n readonly #mouseMovement = { x: 0, y: 0 };\n readonly #gamepadStates = new Map<number, MutableGamepadState>();\n\n readonly #captures = new Set<InputCapture>();\n\n // Every value action the last pass delivered away from rest, by name. A pass that finds one back at\n // rest delivers that rest once and drops it from here (input.md#value-actions).\n readonly #valueActionsAwayFromRest = new Map<string, Action>();\n\n #lastActiveDeviceType : DeviceType | undefined;\n\n #activeScheme : InputScheme | undefined;\n\n // What the previous routing pass left in #lastActiveDeviceType (input.md#the-routing-loop).\n #reportedDeviceType : DeviceType | undefined;\n\n // What the previous routing pass left in #activeScheme (input.md#the-routing-loop).\n #reportedScheme : InputScheme | undefined;\n\n readonly #handleKeyDown : (event : KeyboardEvent) => void;\n readonly #handleKeyUp : (event : KeyboardEvent) => void;\n readonly #handlePointerDown : (event : PointerEvent) => void;\n readonly #handlePointerUp : (event : PointerEvent) => void;\n readonly #handlePointerMove : (event : PointerEvent) => void;\n readonly #handleWheel : (event : WheelEvent) => void;\n\n constructor(logger ?: LoggingUtility)\n {\n this.contexts = new ActionContextRegistry(logger);\n\n this.#handleKeyDown = (event) =>\n {\n if(this.#claims('keyboard', 'key', event.code))\n {\n event.preventDefault();\n }\n\n this.#keys[event.code] = true;\n this.#reportDeliberateInput('keyboard');\n this.#feedCandidate({\n device: { type: 'keyboard', index: 0 }, sourceType: 'key', sourceKey: event.code, value: true,\n });\n };\n\n this.#handleKeyUp = (event) =>\n {\n this.#keys[event.code] = false;\n };\n\n this.#handlePointerDown = (event) =>\n {\n if(event.pointerType !== 'mouse') { return; }\n\n const sourceKey = String(event.button);\n\n if(this.#claims('mouse', 'button', sourceKey))\n {\n event.preventDefault();\n }\n\n this.#mouseButtons[sourceKey] = true;\n this.#reportDeliberateInput('mouse');\n this.#feedCandidate({ device: { type: 'mouse', index: 0 }, sourceType: 'button', sourceKey, value: true });\n };\n\n this.#handlePointerUp = (event) =>\n {\n if(event.pointerType !== 'mouse') { return; }\n\n this.#mouseButtons[String(event.button)] = false;\n };\n\n this.#handlePointerMove = (event) =>\n {\n if(event.pointerType !== 'mouse') { return; }\n\n this.#mousePosition.x = event.clientX;\n this.#mousePosition.y = event.clientY;\n\n this.#mouseMovement.x += event.movementX;\n this.#mouseMovement.y += event.movementY;\n\n // Movement names the mouse and stops there: a hand resting on a mouse moves it, and a nudge must\n // not swap a gamepad player's prompts (input.md#input-schemes).\n if(isDeliberateMovement(event.movementX, event.movementY))\n {\n this.#lastActiveDeviceType = 'mouse';\n }\n };\n\n this.#handleWheel = (event) =>\n {\n if(this.#claims('mouse', 'wheel'))\n {\n event.preventDefault();\n }\n\n // Every notch a frame reported adds up, the way raw movement does: the routing loop reads the\n // frame's whole sample and resets it (input.md#device-state-capture).\n this.#mouseWheel.deltaX += event.deltaX;\n this.#mouseWheel.deltaY += event.deltaY;\n this.#mouseWheel.deltaZ += event.deltaZ;\n\n if(event.deltaX !== REST_VALUE || event.deltaY !== REST_VALUE || event.deltaZ !== REST_VALUE)\n {\n this.#reportDeliberateInput('mouse');\n this.#feedWheelCandidates(event);\n }\n };\n\n if(isBrowser())\n {\n window.addEventListener('keydown', this.#handleKeyDown);\n window.addEventListener('keyup', this.#handleKeyUp);\n window.addEventListener('pointerdown', this.#handlePointerDown);\n window.addEventListener('pointerup', this.#handlePointerUp);\n window.addEventListener('pointermove', this.#handlePointerMove);\n window.addEventListener('wheel', this.#handleWheel, { passive: false });\n }\n }\n\n // The device type behind the most recent deliberate input -- a button held true, or an analog value\n // away from zero -- undefined before any device has reported one (input.md#device-state-capture).\n get lastActiveDeviceType() : DeviceType | undefined\n {\n return this.#lastActiveDeviceType;\n }\n\n // The scheme of the most recent deliberate input, undefined before any device has reported one. Raw\n // mouse movement is the one deliberate input that never sets it (input.md#input-schemes).\n get activeScheme() : InputScheme | undefined\n {\n return this.#activeScheme;\n }\n\n // The state currently captured for one device, the same shape every device reader and a context\n // activation's priming read (input.md#device-state-capture).\n deviceState(type : 'keyboard', index : number) : KeyboardState;\n deviceState(type : 'mouse', index : number) : MouseState;\n deviceState(type : 'gamepad', index : number) : GamepadState;\n deviceState(type : DeviceType, index : number) : KeyboardState | MouseState | GamepadState;\n deviceState(type : DeviceType, index : number) : KeyboardState | MouseState | GamepadState\n {\n return this.#captureFor(type, index);\n }\n\n // Builds the binding a definition describes, reader and all, and registers it under the context it\n // names, so a game binds every definition an imported configuration hands back without writing reader\n // code of its own (input.md#bindings, input.md#device-readers).\n bind(definition : BindingDefinition) : Binding<unknown>\n {\n const action = this.actions.get(definition.action);\n\n if(action === undefined)\n {\n throw new Error(`No action is registered under the name \"${ definition.action }\".`);\n }\n\n const binding = resolveBinding(definition, action);\n\n this.contexts.addBinding(binding);\n\n return binding;\n }\n\n // Feeds every deliberate candidate this manager's device state capture observes to the given capture,\n // while it holds a pending wait. Answers with a function that unregisters it (input.md#input-capture).\n registerCapture(capture : InputCapture) : Unsubscribe\n {\n this.#captures.add(capture);\n\n return () =>\n {\n this.#captures.delete(capture);\n };\n }\n\n // Every binding registered for the named action, filtered to a device type, an input scheme, or a\n // context when the caller gives one, in the order the bindings were registered\n // (input.md#looking-up-bindings).\n getBindingsForAction(\n action : string,\n deviceTypeOrScheme ?: DeviceType | InputScheme,\n context ?: string\n ) : readonly Binding<unknown>[]\n {\n const matches : Binding<unknown>[] = [];\n\n for(const binding of this.contexts.list())\n {\n const matchesAction = binding.action.name === action;\n const matchesDevice = deviceTypeOrScheme === undefined\n || binding.device.type === deviceTypeOrScheme\n || schemeOf(binding.device.type) === deviceTypeOrScheme;\n const matchesContext = context === undefined || binding.context === context;\n\n if(matchesAction && matchesDevice && matchesContext)\n {\n matches.push(binding);\n }\n }\n\n return matches;\n }\n\n // Captures device state, updates lastActiveDeviceType, and routes: every live trigger, toggle, and delta\n // binding hands its device's captured state to `deliverAction`, while the live value bindings are summed\n // by action and each action that has one is delivered once with the value that sum computes. Both\n // broadcast with `ROUTING_LOOP_SENDER_ID` (input.md#the-routing-loop, input.md#value-actions).\n routeCapturedInput(context : GameSimulationView) : void\n {\n this.#pollGamepads();\n\n const sums = new Map<string, ValueActionSum>();\n\n for(const binding of this.contexts.list())\n {\n if(this.contexts.isLive(binding))\n {\n const state = this.#captureFor(binding.device.type, binding.device.index);\n\n if(binding.kind === 'value')\n {\n this.#addContribution(sums, binding, state);\n }\n else\n {\n deliverAction(binding, state, context, ROUTING_LOOP_SENDER_ID);\n }\n }\n }\n\n this.#deliverValueActions(sums, context);\n this.#reportDeviceChange(context);\n this.#reportSchemeChange(context);\n\n // A one-shot sample -- the mouse wheel -- resets once this pass has read it, so a still wheel\n // reports nothing on the next frame. Held state is never reset this way (input.md#device-state-capture).\n this.#mouseMovement.x = REST_VALUE;\n this.#mouseMovement.y = REST_VALUE;\n\n this.#mouseWheel.deltaX = REST_VALUE;\n this.#mouseWheel.deltaY = REST_VALUE;\n this.#mouseWheel.deltaZ = REST_VALUE;\n }\n\n // True while a live binding claims the captured input named. An undefined sourceKey matches any key of\n // that source type, which is what a wheel event needs: it carries every axis at once\n // (input.md#claiming-an-input-from-the-browser).\n #claims(deviceType : DeviceType, sourceType : string, sourceKey ?: string) : boolean\n {\n for(const binding of this.contexts.list())\n {\n if(binding.preventDefault && this.contexts.isLive(binding))\n {\n const { device, sourceType: boundType, sourceKey: boundKey } = binding.input;\n\n if(device.type === deviceType && boundType === sourceType\n && (sourceKey === undefined || boundKey === sourceKey))\n {\n return true;\n }\n }\n }\n\n return false;\n }\n\n async dispose() : Promise<void>\n {\n if(isBrowser())\n {\n window.removeEventListener('keydown', this.#handleKeyDown);\n window.removeEventListener('keyup', this.#handleKeyUp);\n window.removeEventListener('pointerdown', this.#handlePointerDown);\n window.removeEventListener('pointerup', this.#handlePointerUp);\n window.removeEventListener('pointermove', this.#handlePointerMove);\n window.removeEventListener('wheel', this.#handleWheel);\n }\n }\n\n // Adds one value binding's contribution to its action's running total, starting that total when this is\n // the first binding the pass has seen for the action (input.md#value-actions).\n #addContribution(sums : Map<string, ValueActionSum>, binding : Binding<unknown>, state : unknown) : void\n {\n const fire = binding.process(state);\n\n if(fire === undefined)\n {\n return;\n }\n\n // `BindingFire` carries whatever any of the four kinds sends; a value binding's is always a number.\n const contribution = Number(fire.value);\n const running = sums.get(binding.action.name);\n\n if(running === undefined)\n {\n sums.set(binding.action.name, { action: binding.action, value: contribution });\n }\n else\n {\n running.value = running.value + contribution;\n }\n }\n\n // Delivers each value action on every pass it is away from rest, and once more on the pass it returns\n // to rest. An action left with no live value binding to contribute still settles: an empty total is\n // zero, which is rest (input.md#value-actions).\n #deliverValueActions(sums : Map<string, ValueActionSum>, context : GameSimulationView) : void\n {\n for(const [ name, action ] of this.#valueActionsAwayFromRest)\n {\n if(!sums.has(name))\n {\n sums.set(name, { action, value: 0 });\n }\n }\n\n for(const [ name, sum ] of sums)\n {\n const value = this.#clampToAction(sum.action, sum.value);\n const awayFromRest = value !== this.#clampToAction(sum.action, REST_VALUE);\n const wasAwayFromRest = this.#valueActionsAwayFromRest.has(name);\n\n if(awayFromRest)\n {\n this.#valueActionsAwayFromRest.set(name, sum.action);\n }\n else\n {\n this.#valueActionsAwayFromRest.delete(name);\n }\n\n if(awayFromRest || wasAwayFromRest)\n {\n deliverValueAction(sum.action, value, context, ROUTING_LOOP_SENDER_ID);\n }\n }\n }\n\n // A value action's value stays inside the bounds its declaration set, its rest included\n // (input.md#value-actions).\n #clampToAction(action : Action, value : number) : number\n {\n if(action.type !== 'analog')\n {\n return value;\n }\n\n if(action.minValue !== undefined && value < action.minValue)\n {\n return action.minValue;\n }\n\n if(action.maxValue !== undefined && value > action.maxValue)\n {\n return action.maxValue;\n }\n\n return value;\n }\n\n #captureFor(type : DeviceType, index : number) : KeyboardState | MouseState | GamepadState\n {\n if(type === 'keyboard')\n {\n return { keys: this.#keys };\n }\n\n if(type === 'mouse')\n {\n return {\n buttons: this.#mouseButtons,\n position: this.#mousePosition,\n wheel: this.#mouseWheel,\n movement: this.#mouseMovement,\n };\n }\n\n const gamepad = this.#gamepadStates.get(index);\n\n if(gamepad === undefined)\n {\n return { buttons: {}, axes: {} };\n }\n\n return gamepad;\n }\n\n // Polls every connected gamepad (input.md#device-state-capture). Unlike the DOM listeners above, this\n // needs no window: `navigator.getGamepads` is a plain function, present or absent on its own account.\n #pollGamepads() : void\n {\n if(typeof navigator === 'undefined' || typeof navigator.getGamepads !== 'function')\n {\n return;\n }\n\n const gamepads = navigator.getGamepads();\n\n for(let index = 0; index < gamepads.length; index++)\n {\n const gamepad = gamepads[index];\n\n if(gamepad !== null)\n {\n this.#pollOneGamepad(index, gamepad);\n }\n }\n }\n\n #pollOneGamepad(index : number, gamepad : Gamepad) : void\n {\n const buttons : Record<string, number> = {};\n let deliberate = false;\n\n for(let buttonIndex = 0; buttonIndex < gamepad.buttons.length; buttonIndex++)\n {\n const value = gamepad.buttons[buttonIndex].value;\n const sourceKey = String(buttonIndex);\n\n buttons[sourceKey] = value;\n\n if(isDeliberate(value))\n {\n deliberate = true;\n this.#feedCandidate({ device: { type: 'gamepad', index }, sourceType: 'button', sourceKey, value });\n }\n }\n\n const axes : Record<string, number> = {};\n\n for(let axisIndex = 0; axisIndex < gamepad.axes.length; axisIndex++)\n {\n const value = gamepad.axes[axisIndex];\n const sourceKey = String(axisIndex);\n\n axes[sourceKey] = value;\n\n if(isDeliberate(value))\n {\n deliberate = true;\n this.#feedCandidate({ device: { type: 'gamepad', index }, sourceType: 'axis', sourceKey, value });\n }\n }\n\n this.#gamepadStates.set(index, { buttons, axes });\n\n if(deliberate)\n {\n this.#reportDeliberateInput('gamepad');\n }\n }\n\n // Emitted when this pass leaves a different device type than the previous pass did: a broadcast under\n // the routing loop's own sender, alongside the actions it just delivered (input.md#the-routing-loop).\n #reportDeviceChange(context : GameSimulationView) : void\n {\n if(this.#lastActiveDeviceType === this.#reportedDeviceType)\n {\n return;\n }\n\n const previousDeviceType = this.#reportedDeviceType;\n this.#reportedDeviceType = this.#lastActiveDeviceType;\n\n if(this.#lastActiveDeviceType === undefined)\n {\n return;\n }\n\n context.bus.emit({\n type: 'input:deviceChanged',\n senderID: ROUTING_LOOP_SENDER_ID,\n payload: { deviceType: this.#lastActiveDeviceType, previousDeviceType },\n });\n }\n\n // A deliberate input names its device type and makes that device type's scheme the active one\n // (input.md#device-state-capture, input.md#input-schemes).\n #reportDeliberateInput(deviceType : DeviceType) : void\n {\n this.#lastActiveDeviceType = deviceType;\n this.#activeScheme = schemeOf(deviceType);\n }\n\n // Emitted when this pass leaves a different scheme than the previous pass did, under the same rules and\n // the same sender as `input:deviceChanged` (input.md#the-routing-loop).\n #reportSchemeChange(context : GameSimulationView) : void\n {\n if(this.#activeScheme === this.#reportedScheme)\n {\n return;\n }\n\n const previousScheme = this.#reportedScheme;\n this.#reportedScheme = this.#activeScheme;\n\n if(this.#activeScheme === undefined)\n {\n return;\n }\n\n context.bus.emit({\n type: 'input:schemeChanged',\n senderID: ROUTING_LOOP_SENDER_ID,\n payload: { scheme: this.#activeScheme, previousScheme },\n });\n }\n\n // One candidate per axis the notch actually turned, so a rebind screen binds the axis the player\n // scrolled rather than whichever this reads first (input.md#input-capture).\n #feedWheelCandidates(event : WheelEvent) : void\n {\n const axes = [\n { sourceKey: 'deltaY', value: event.deltaY },\n { sourceKey: 'deltaX', value: event.deltaX },\n { sourceKey: 'deltaZ', value: event.deltaZ },\n ];\n\n for(const axis of axes)\n {\n if(axis.value !== REST_VALUE)\n {\n this.#feedCandidate({\n device: { type: 'mouse', index: 0 },\n sourceType: 'wheel',\n sourceKey: axis.sourceKey,\n value: axis.value,\n });\n }\n }\n }\n\n #feedCandidate(candidate : CandidateInput) : void\n {\n for(const capture of this.#captures)\n {\n capture.feed(candidate);\n }\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Post-Processing Pipeline Backend\n//\n// Applies post-processing effects via BabylonJS DefaultRenderingPipeline and SSAO2RenderingPipeline.\n//----------------------------------------------------------------------------------------------------------------------\n\nimport {\n Color4,\n DefaultRenderingPipeline,\n ImageProcessingConfiguration,\n SSAO2RenderingPipeline,\n type Scene,\n} from '@babylonjs/core';\n\nimport type { PostProcessingConfig } from '../interfaces/level.ts';\nimport type { LoggerInterface } from '../interfaces/logger.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Map from user-facing tonemap operator names to BabylonJS constants.\n * BabylonJS only has STANDARD (0) and ACES (1) — the other names all map to STANDARD.\n */\nconst TONEMAP_OPERATORS : Record<string, number> = {\n hable: ImageProcessingConfiguration.TONEMAPPING_STANDARD,\n reinhard: ImageProcessingConfiguration.TONEMAPPING_STANDARD,\n hejidawson: ImageProcessingConfiguration.TONEMAPPING_STANDARD,\n photographic: ImageProcessingConfiguration.TONEMAPPING_STANDARD,\n aces: ImageProcessingConfiguration.TONEMAPPING_ACES,\n};\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Apply post-processing effects to a scene based on a declarative config.\n * Creates BabylonJS rendering pipelines and configures them according to the provided settings.\n *\n * This is a level-config handler, not a node property handler. The logger is the level's own.\n */\nexport function applyPipelinePostProcessing(\n scene : Scene,\n config : PostProcessingConfig,\n logger : LoggerInterface\n) : void\n{\n // Can't set up post-processing without a camera\n if(!scene.activeCamera)\n {\n return;\n }\n\n // Volumetric lighting is recognized and applied by neither backend.\n if(config.volumetric)\n {\n logger.warn('Volumetric lighting is recognized and applied by neither backend. Ignored.');\n }\n\n // Create the default rendering pipeline\n const pipeline = new DefaultRenderingPipeline('sage-default', true, scene, scene.cameras);\n\n // Bloom\n if(config.bloom)\n {\n pipeline.bloomEnabled = true;\n\n if(config.bloom.weight !== undefined)\n {\n pipeline.bloomWeight = config.bloom.weight;\n }\n if(config.bloom.threshold !== undefined)\n {\n pipeline.bloomThreshold = config.bloom.threshold;\n }\n if(config.bloom.scale !== undefined)\n {\n pipeline.bloomScale = config.bloom.scale;\n }\n if(config.bloom.kernel !== undefined)\n {\n pipeline.bloomKernel = config.bloom.kernel;\n }\n }\n\n // Tone mapping\n if(config.tonemap)\n {\n pipeline.imageProcessingEnabled = true;\n pipeline.imageProcessing.toneMappingEnabled = true;\n\n if(config.tonemap.operator)\n {\n pipeline.imageProcessing.toneMappingType\n = TONEMAP_OPERATORS[config.tonemap.operator] ?? ImageProcessingConfiguration.TONEMAPPING_STANDARD;\n }\n }\n\n // Film grain\n if(config.grain)\n {\n pipeline.grainEnabled = true;\n\n if(config.grain.intensity !== undefined)\n {\n pipeline.grain.intensity = config.grain.intensity;\n }\n if(config.grain.animated !== undefined)\n {\n pipeline.grain.animated = config.grain.animated;\n }\n }\n\n // Vignette\n if(config.vignette)\n {\n pipeline.imageProcessingEnabled = true;\n pipeline.imageProcessing.vignetteEnabled = true;\n\n if(config.vignette.weight !== undefined)\n {\n pipeline.imageProcessing.vignetteWeight = config.vignette.weight;\n }\n if(config.vignette.stretch !== undefined)\n {\n pipeline.imageProcessing.vignetteStretch = config.vignette.stretch;\n }\n if(config.vignette.color)\n {\n pipeline.imageProcessing.vignetteColor = new Color4(\n config.vignette.color.r,\n config.vignette.color.g,\n config.vignette.color.b,\n 1\n );\n }\n }\n\n // Sharpen\n if(config.sharpen)\n {\n pipeline.sharpenEnabled = true;\n\n if(config.sharpen.edge !== undefined)\n {\n pipeline.sharpen.edgeAmount = config.sharpen.edge;\n }\n if(config.sharpen.color !== undefined)\n {\n pipeline.sharpen.colorAmount = config.sharpen.color;\n }\n }\n\n // Chromatic aberration\n if(config.chromaticAberration)\n {\n pipeline.chromaticAberrationEnabled = true;\n\n if(config.chromaticAberration.amount !== undefined)\n {\n pipeline.chromaticAberration.aberrationAmount = config.chromaticAberration.amount;\n }\n }\n\n // SSAO — uses its own separate pipeline\n if(config.ssao)\n {\n const ssao = new SSAO2RenderingPipeline('sage-ssao', scene, { ssaoRatio: 0.5, blurRatio: 1.0 });\n\n if(config.ssao.radius !== undefined)\n {\n ssao.radius = config.ssao.radius;\n }\n if(config.ssao.samples !== undefined)\n {\n ssao.samples = config.ssao.samples;\n }\n if(config.ssao.totalStrength !== undefined)\n {\n ssao.totalStrength = config.ssao.totalStrength;\n }\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Post-Processing Dispatcher\n//\n// Routes to either the DefaultRenderingPipeline backend or the Frame Graph backend\n// based on config.renderer.\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { Scene } from '@babylonjs/core';\n\nimport type { PostProcessingConfig } from '../interfaces/level.ts';\nimport type { LoggerInterface } from '../interfaces/logger.ts';\n\nimport { applyPipelinePostProcessing } from './postProcessingPipeline.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Applies a level's post-processing configuration to its scene through the backend the configuration names. The\n * logger is the level's own, and is where a backend's warning is written (design/authoring.md#post-processing).\n */\nexport async function applyPostProcessing(\n scene : Scene,\n config : PostProcessingConfig,\n logger : LoggerInterface\n) : Promise<void>\n{\n if(config.renderer === 'frameGraph')\n {\n const { applyFrameGraphPostProcessing } = await import('./postProcessingFrameGraph.ts');\n await applyFrameGraphPostProcessing(scene, config, logger);\n }\n else\n {\n applyPipelinePostProcessing(scene, config, logger);\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Hierarchy Bounds\n//\n// Blender's glTF exporter splits a multi-material model into primitive children, leaving a bare\n// TransformNode parent with no geometry of its own. `PhysicsAggregate` reads such a node's raw bounding\n// info -- a default unit cube -- so anything sizing a collision shape from one of these nodes has to\n// measure the descendants instead.\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { Quaternion, type TransformNode, Vector3 } from '@babylonjs/core';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport interface HierarchyBounds\n{\n /** Full box size across the node's descendants. */\n extents : Vector3;\n\n /** Offset from the node's origin, in the node's local frame. */\n center : Vector3;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Measures a node's descendant geometry as a box, in the format `PhysicsAggregate` takes in its options.\n */\nexport function computeHierarchyBounds(node : TransformNode) : HierarchyBounds\n{\n const { min, max } = node.getHierarchyBoundingVectors(true);\n const extents = max.subtract(min);\n const worldCenter = min.add(max).scale(0.5);\n\n // Undo the node's rotation so the center lands in its local frame; for the common identity case this\n // is just worldCenter - absolutePosition.\n const worldOffset = worldCenter.subtract(node.absolutePosition);\n const inverseRotation = Quaternion.Inverse(node.absoluteRotationQuaternion);\n const center = new Vector3();\n\n worldOffset.rotateByQuaternionToRef(inverseRotation, center);\n\n return { extents, center };\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Collider Property Handler\n//----------------------------------------------------------------------------------------------------------------------\n\nimport {\n Mesh,\n PhysicsAggregate,\n type PhysicsAggregateParameters,\n PhysicsMotionType,\n PhysicsPrestepType,\n PhysicsShapeType,\n type TransformNode,\n} from '@babylonjs/core';\n\nimport type { LoggerInterface } from '../interfaces/logger.ts';\nimport type { LevelManager } from '../managers/level.ts';\nimport { type HierarchyBounds, computeHierarchyBounds } from '../utils/bounds.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nconst DEFAULT_COLLIDER_MASS = 1;\n\n// The words the authored property takes, and nothing else: the body behavior's own vocabulary is wider\n// (design/physics.md#the-authored-collider-property).\nconst AUTHORED_SHAPES = [ 'box', 'sphere', 'mesh', 'none' ] as const;\n\ntype AuthoredShape = typeof AUTHORED_SHAPES[number];\n\n/**\n * What a node's `collider_motion` and `collider_mass` ask the body to be.\n */\ninterface ColliderMotion\n{\n type : PhysicsMotionType;\n mass : number;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n// Reading the authored properties\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Reads the shape a node's `collider` names, warning and answering with nothing for a value outside the four\n * (design/physics.md#the-authored-collider-property).\n */\nfunction readShape(node : TransformNode, value : unknown, logger : LoggerInterface) : AuthoredShape | undefined\n{\n for(const shape of AUTHORED_SHAPES)\n {\n if(value === shape)\n {\n return shape;\n }\n }\n\n logger.warn(\n `Unusable collider on \"${ node.name }\": ${ JSON.stringify(value) }. `\n + `The collider property takes ${ AUTHORED_SHAPES.join(', ') }. Building nothing.`\n );\n\n return undefined;\n}\n\n/**\n * Reads a node's mass, falling back to the default for anything that is not a finite number\n * (design/physics.md#the-authored-collider-property).\n */\nfunction readColliderMass(node : TransformNode, logger : LoggerInterface) : number\n{\n const authored = node.metadata?.collider_mass;\n\n if(authored === undefined || authored === null)\n {\n return DEFAULT_COLLIDER_MASS;\n }\n\n if(typeof authored !== 'number' || !Number.isFinite(authored))\n {\n logger.warn(\n `Unusable collider_mass on ${ node.name }: ${ JSON.stringify(authored) }. `\n + `Using the default of ${ DEFAULT_COLLIDER_MASS }.`\n );\n\n return DEFAULT_COLLIDER_MASS;\n }\n\n return authored;\n}\n\n/**\n * Reads a node's motion. An authored collider with no `collider_motion` is static: a node in a level is\n * scenery until its author says otherwise, and a floor that falls out of the level takes the level with it\n * (design/physics.md#the-authored-collider-property).\n */\nfunction readColliderMotion(node : TransformNode, logger : LoggerInterface) : ColliderMotion\n{\n const mass = readColliderMass(node, logger);\n const authored = node.metadata?.collider_motion as string | undefined;\n\n if(authored === 'dynamic')\n {\n return { type: PhysicsMotionType.DYNAMIC, mass };\n }\n else if(authored === 'kinematic')\n {\n return { type: PhysicsMotionType.ANIMATED, mass };\n }\n else if(authored === 'static' || authored === undefined)\n {\n return { type: PhysicsMotionType.STATIC, mass };\n }\n else\n {\n logger.warn(`Unknown collider_motion on ${ node.name }: \"${ authored }\". Building a static body.`);\n return { type: PhysicsMotionType.STATIC, mass };\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n// Building the body\n//----------------------------------------------------------------------------------------------------------------------\n\nfunction hasOwnGeometry(node : TransformNode) : boolean\n{\n return node instanceof Mesh && node.getTotalVertices() > 0;\n}\n\nfunction hasGeometryBeneath(node : TransformNode) : boolean\n{\n for(const child of node.getChildMeshes(false))\n {\n if(child.getTotalVertices() > 0)\n {\n return true;\n }\n }\n\n return false;\n}\n\nfunction createAggregate(\n node : TransformNode,\n shapeType : PhysicsShapeType,\n motion : ColliderMotion,\n measurement : HierarchyBounds | undefined\n) : void\n{\n let mass = motion.mass;\n\n if(motion.type === PhysicsMotionType.STATIC)\n {\n mass = 0;\n }\n\n const options : PhysicsAggregateParameters = { mass };\n\n if(measurement !== undefined)\n {\n options.center = measurement.center;\n\n if(shapeType === PhysicsShapeType.SPHERE)\n {\n // The largest half-extent, so the sphere contains the geometry rather than cutting through its\n // corners (design/physics.md#shapes).\n const { x, y, z } = measurement.extents;\n options.radius = Math.max(x, y, z) / 2;\n }\n else\n {\n options.extents = measurement.extents;\n }\n }\n\n const aggregate = new PhysicsAggregate(node, shapeType, options, node.getScene());\n\n aggregate.body.setMotionType(motion.type);\n\n // The node is the authority for an authored kinematic body: a game writes the node and the body follows it\n // exactly. Moving the body toward the node instead makes the body the authority, and the post-step sync\n // writes its lagging position back over the game's own write\n // (design/physics.md#the-authored-collider-property).\n if(motion.type === PhysicsMotionType.ANIMATED)\n {\n aggregate.body.setPrestepType(PhysicsPrestepType.TELEPORT);\n }\n}\n\n/**\n * A box or a sphere, sized from the node's own bounds when it has geometry and from everything under it when\n * it does not (design/physics.md#sizing-from-a-bare-transform-node).\n */\nfunction buildMeasuredBody(node : TransformNode, shapeType : PhysicsShapeType, motion : ColliderMotion) : void\n{\n if(hasOwnGeometry(node))\n {\n createAggregate(node, shapeType, motion, undefined);\n return;\n }\n\n if(!hasGeometryBeneath(node))\n {\n throw new Error(\n `Cannot build a collider on \"${ node.name }\": it has neither a mesh nor anything under it to size a `\n + 'body from.'\n );\n }\n\n createAggregate(node, shapeType, motion, computeHierarchyBounds(node));\n}\n\n/**\n * The triangle-mesh shape, built from the child a `collider_mesh` marks where there is one: authoring's way\n * of shipping a low-triangle stand-in for a detailed model (design/physics.md#the-authored-collider-property).\n */\nfunction buildTriangleMeshBody(node : TransformNode, motion : ColliderMotion) : void\n{\n let source = node;\n\n for(const child of node.getChildMeshes(false))\n {\n if(child.metadata?.collider_mesh === true && child instanceof Mesh)\n {\n child.isVisible = false;\n source = child;\n break;\n }\n }\n\n if(!hasOwnGeometry(source))\n {\n throw new Error(\n `Cannot build a \"mesh\" collider on \"${ source.name }\": a triangle mesh reads vertices, and this node `\n + 'has none of its own.'\n );\n }\n\n createAggregate(source, PhysicsShapeType.MESH, motion, undefined);\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Register the collider property handler.\n *\n * Supports the following properties on scene nodes:\n * - `collider` (string): the shape, one of \"box\", \"sphere\", \"mesh\", or \"none\"\n * - `collider_mesh` (boolean): on a child node, marks it as the collision source for a parent's \"mesh\" collider\n * - `collider_motion` (string): \"static\", \"dynamic\", or \"kinematic\". Defaults to \"static\".\n * - `collider_mass` (number): mass in kilograms, defaulting to 1. It sets the mass and nothing else; it does\n * not decide whether the body moves.\n */\nexport function registerColliderHandler(levelManager : Pick<LevelManager, 'registerPropertyHandler'>) : void\n{\n levelManager.registerPropertyHandler('collider', (node, value, level, context) =>\n {\n if(!level.scene)\n {\n throw new Error('Scene not available for collider handler');\n }\n\n const shape = readShape(node, value, context.logger);\n\n if(shape === undefined)\n {\n return;\n }\n\n // `none` builds nothing, which is what a node carrying no `collider` at all already gets: it is there\n // so an authored file can say out loud that a node is not meant to collide\n // (design/physics.md#the-authored-collider-property).\n if(shape === 'none')\n {\n return;\n }\n\n const motion = readColliderMotion(node, context.logger);\n\n if(shape === 'mesh')\n {\n buildTriangleMeshBody(node, motion);\n }\n else if(shape === 'sphere')\n {\n buildMeasuredBody(node, PhysicsShapeType.SPHERE, motion);\n }\n else\n {\n buildMeasuredBody(node, PhysicsShapeType.BOX, motion);\n }\n });\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// LOD (Level of Detail) Property Handler\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { Mesh } from '@babylonjs/core';\n\nimport type { LevelManager } from '../managers/level.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/** The numbers in a comma-separated list, with anything that is not one left out. */\nfunction parseDistances(value : string) : number[]\n{\n const distances : number[] = [];\n\n for(const part of value.split(','))\n {\n const distance = parseFloat(part.trim());\n\n if(!Number.isNaN(distance))\n {\n distances.push(distance);\n }\n }\n\n return distances;\n}\n\n/**\n * Register the LOD (Level of Detail) property handler.\n *\n * Supports the following property on scene nodes:\n * - `lod_distances` (string): Comma-separated distances for LOD levels\n *\n * `addLODLevel(distance, mesh)` means \"use this mesh beyond that distance\", so the node itself is the\n * highest detail and each child covers the band past the distance it was registered at.\n * Example: \"10,25,50\" with three children means:\n * - 0-10 units: the node's own mesh\n * - 10-25 units: first child mesh\n * - 25-50 units: second child mesh\n * - 50+ units: third child mesh\n *\n * A run with a child for every distance never hides the node. Hiding needs a distance with no child left\n * to fill it: \"10,25,50\" with two children renders the node, then both children, then nothing past 50.\n */\nexport function registerLodHandler(levelManager : Pick<LevelManager, 'registerPropertyHandler'>) : void\n{\n levelManager.registerPropertyHandler('lod_distances', (node, value, _level, context) =>\n {\n if(!(node instanceof Mesh))\n {\n return;\n }\n\n const distances = parseDistances(String(value));\n\n if(distances.length === 0)\n {\n context.logger.warn(\n `Unusable lod_distances on \"${ node.name }\": ${ JSON.stringify(value) }. No level added.`\n );\n return;\n }\n\n const children = node.getChildMeshes(true) as Mesh[];\n const levelCount = Math.min(distances.length, children.length);\n\n for(let index = 0; index < levelCount; index++)\n {\n node.addLODLevel(distances[index], children[index]);\n }\n\n // Hiding the node beyond the last distance is only expressible when a distance is left over with no\n // child to fill it. Registering null at a distance a child already holds loses: the sort is stable\n // and the child went in first, so the node would never stop rendering\n // (design/authoring.md#property-handlers).\n if(distances.length > children.length)\n {\n const lastDistance = distances[distances.length - 1];\n node.addLODLevel(lastDistance, null);\n }\n });\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Occlusion Property Handler\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { Mesh } from '@babylonjs/core';\n\nimport type { LevelManager } from '../managers/level.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Register the occlusion property handler.\n *\n * Supports the following property on scene nodes:\n * - `occlusion` (string): `optimistic` or `strict`, Babylon's own occlusion query types\n */\nexport function registerOcclusionHandler(levelManager : Pick<LevelManager, 'registerPropertyHandler'>) : void\n{\n levelManager.registerPropertyHandler('occlusion', (node, value, _level, context) =>\n {\n if(!(node instanceof Mesh))\n {\n return;\n }\n\n if(value === 'optimistic')\n {\n node.occlusionType = Mesh.OCCLUSION_TYPE_OPTIMISTIC;\n }\n else if(value === 'strict')\n {\n node.occlusionType = Mesh.OCCLUSION_TYPE_STRICT;\n }\n else\n {\n context.logger.warn(\n `Unknown occlusion on \"${ node.name }\": ${ JSON.stringify(value) }. `\n + 'The occlusion property takes \"optimistic\" or \"strict\".'\n );\n }\n });\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Metadata Utilities\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Collect properties with a common prefix into a single object.\n * Useful for gathering related properties from node metadata.\n *\n * For example, given metadata with:\n * - sound_volume: 0.5\n * - sound_loop: true\n * - sound_spatial: false\n *\n * Calling collectPrefixedProperties(metadata, 'sound') returns:\n * { volume: 0.5, loop: true, spatial: false }\n *\n * @param metadata - The node metadata to search\n * @param prefix - The prefix to match (without trailing underscore)\n * @returns Object with sub-properties (prefix and underscore removed from keys)\n */\nexport function collectPrefixedProperties(\n metadata : Record<string, unknown>,\n prefix : string\n) : Record<string, unknown>\n{\n const result : Record<string, unknown> = {};\n const prefixWithUnderscore = `${ prefix }_`;\n\n for(const [ key, value ] of Object.entries(metadata))\n {\n if(key.startsWith(prefixWithUnderscore))\n {\n const subKey = key.slice(prefixWithUnderscore.length);\n result[subKey] = value;\n }\n }\n\n return result;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Sound Property Handler\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { collectPrefixedProperties } from '../utils/metadata.ts';\nimport type { LevelManager } from '../managers/level.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Register the sound property handler.\n *\n * Supports the following properties on scene nodes:\n * - `sound` (string): Path to the sound file\n * - `sound_volume` (number): Volume 0-1, default 1\n * - `sound_loop` (boolean): Whether to loop, default true\n * - `sound_spatial` (boolean): Whether to use 3D spatial audio, default true\n * - `sound_distance` (number): Maximum distance for spatial audio, default 100\n * - `sound_autoplay` (boolean): Whether to autoplay, default true\n * - `sound_channel` (string): Audio channel name, default 'ambient'\n */\nexport function registerSoundHandler(levelManager : Pick<LevelManager, 'registerPropertyHandler'>) : void\n{\n // The handler answers with the promise rather than letting the load run on behind the walk: a node an\n // entity adopts carries its sound from before that entity existed, finished rather than pending\n // (design/authoring.md#when-handlers-run).\n levelManager.registerPropertyHandler('sound', async (node, value, level, context) =>\n {\n const soundPath = value as string;\n const scene = level.scene;\n\n if(!scene)\n {\n throw new Error('Scene not available for sound handler');\n }\n\n const extras = collectPrefixedProperties(\n node.metadata as Record<string, unknown>,\n 'sound'\n );\n\n const volume = (extras.volume as number) ?? 1;\n const loop = (extras.loop as boolean) ?? true;\n const spatial = (extras.spatial as boolean) ?? true;\n const maxDistance = (extras.distance as number) ?? 100;\n const autoplay = (extras.autoplay as boolean) ?? true;\n const channel = (extras.channel as string) ?? 'ambient';\n\n const audioManager = context.audioManager;\n\n if(!audioManager)\n {\n context.logger.warn(`No AudioManager available. Sound for \"${ node.name }\" skipped.`);\n return;\n }\n\n const sound = await audioManager.createSound(\n `${ node.name }_sound`,\n soundPath,\n channel,\n {\n loop,\n autoplay,\n volume,\n spatialEnabled: spatial,\n spatialMaxDistance: maxDistance,\n spatialDistanceModel: 'linear',\n }\n );\n\n if(spatial)\n {\n sound.spatial.attach(node);\n }\n });\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Trigger Property Handler\n//----------------------------------------------------------------------------------------------------------------------\n\nimport {\n AbstractMesh,\n ActionManager,\n ExecuteCodeAction,\n type IAction,\n type Node,\n type Observer,\n} from '@babylonjs/core';\n\nimport type { EventBus } from '@skewedaspect/sage-core';\n\nimport type { LevelManager } from '../managers/level.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Registers an intersection action (enter or exit) for a trigger mesh against a target mesh.\n */\nfunction registerIntersectionAction(\n actionManager : ActionManager,\n triggerName : string,\n bus : EventBus,\n targetMesh : AbstractMesh,\n actionType : 'enter' | 'exit'\n) : IAction | null\n{\n let triggerType = ActionManager.OnIntersectionExitTrigger;\n\n if(actionType === 'enter')\n {\n triggerType = ActionManager.OnIntersectionEnterTrigger;\n }\n\n const eventType = `trigger:${ actionType }` as const;\n\n return actionManager.registerAction(\n new ExecuteCodeAction(\n {\n trigger: triggerType,\n parameter: { mesh: targetMesh },\n },\n (evt) =>\n {\n // Babylon fires an intersection action with the trigger mesh as its source and the mesh that\n // crossed it as the additional data.\n bus.emit({\n type: eventType,\n senderID: 'trigger',\n payload: {\n trigger: triggerName,\n other: evt.additionalData as AbstractMesh,\n },\n });\n }\n )\n );\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Register the trigger property handler.\n *\n * Supports the following property on scene nodes:\n * - `trigger` (string): The trigger zone name\n *\n * When an object enters or exits the trigger zone, events are published:\n * - `trigger:enter` with payload { trigger: string, other: AbstractMesh }\n * - `trigger:exit` with payload { trigger: string, other: AbstractMesh }\n *\n * The trigger mesh is made invisible but retains collision detection.\n * Intersection checks are registered against all non-trigger meshes in the scene,\n * including meshes added after the handler runs (e.g. dynamically spawned entities).\n */\nexport function registerTriggerHandler(levelManager : Pick<LevelManager, 'registerPropertyHandler'>) : void\n{\n levelManager.registerPropertyHandler('trigger', (node, value, level, context) =>\n {\n const triggerName = value as string;\n const scene = level.scene;\n\n if(!scene)\n {\n throw new Error('Scene not available for trigger handler');\n }\n\n if(!(node instanceof AbstractMesh))\n {\n return;\n }\n\n const bus = context.bus;\n\n node.isVisible = false;\n node.checkCollisions = true;\n\n const existing = node.actionManager as ActionManager | null;\n let actionManager : ActionManager;\n\n if(existing)\n {\n actionManager = existing;\n }\n else\n {\n actionManager = new ActionManager(scene);\n node.actionManager = actionManager;\n }\n\n // Babylon tests every action on a zone's manager on every frame the zone is active, so a watched\n // mesh's pair has to come off when the mesh goes: spawn churn otherwise grows the per-frame work by\n // two actions per despawn, against meshes that no longer exist.\n const watched = new Map<AbstractMesh, Observer<Node>>();\n\n function watch(target : AbstractMesh) : void\n {\n if(target === node || target.metadata?.trigger)\n {\n return;\n }\n\n const actions : IAction[] = [];\n\n for(const actionType of [ 'enter', 'exit' ] as const)\n {\n const action = registerIntersectionAction(actionManager, triggerName, bus, target, actionType);\n\n if(action)\n {\n actions.push(action);\n }\n }\n\n const observer = target.onDisposeObservable.add(() =>\n {\n for(const action of actions)\n {\n actionManager.unregisterAction(action);\n }\n\n watched.delete(target);\n });\n\n if(observer)\n {\n watched.set(target, observer);\n }\n }\n\n for(const mesh of scene.meshes)\n {\n watch(mesh);\n }\n\n const newMeshObserver = scene.onNewMeshAddedObservable.add(watch);\n\n // A zone that goes takes its own watching with it: the meshes it was watching outlive it.\n node.onDisposeObservable.add(() =>\n {\n scene.onNewMeshAddedObservable.remove(newMeshObserver);\n\n for(const [ target, observer ] of watched)\n {\n target.onDisposeObservable.remove(observer);\n }\n\n watched.clear();\n });\n });\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Visible Property Handler\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { AbstractMesh, type TransformNode } from '@babylonjs/core';\nimport type { LevelManager } from '../managers/level.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Register the visible property handler.\n *\n * Supports the following property on scene nodes:\n * - `visible` (boolean | string | number): Controls mesh visibility.\n * Accepts true/false, 'true'/'false', or 1/0. Anything else, including an unlisted string, leaves the\n * mesh visible (design/authoring.md#the-built-in-properties).\n */\nexport function registerVisibleHandler(levelManager : Pick<LevelManager, 'registerPropertyHandler'>) : void\n{\n levelManager.registerPropertyHandler('visible', (node : TransformNode, value : unknown) =>\n {\n // Only meshes have isVisible property\n if(!(node instanceof AbstractMesh))\n {\n return;\n }\n\n let visible = true;\n\n if(typeof value === 'boolean')\n {\n visible = value;\n }\n else if(typeof value === 'string')\n {\n if(value.toLowerCase() === 'true' || value === '1')\n {\n visible = true;\n }\n else if(value.toLowerCase() === 'false')\n {\n visible = false;\n }\n }\n else if(typeof value === 'number')\n {\n visible = value !== 0;\n }\n\n node.isVisible = visible;\n });\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Property Handlers Index\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { LevelManager } from '../managers/level.ts';\n\nimport { registerColliderHandler } from './collider.ts';\nimport { registerLodHandler } from './lod.ts';\nimport { registerOcclusionHandler } from './occlusion.ts';\nimport { applyPostProcessing } from './postProcessing.ts';\nimport { registerSoundHandler } from './sound.ts';\nimport { registerTriggerHandler } from './trigger.ts';\nimport { registerVisibleHandler } from './visible.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport {\n applyPostProcessing,\n registerColliderHandler,\n registerLodHandler,\n registerOcclusionHandler,\n registerSoundHandler,\n registerTriggerHandler,\n registerVisibleHandler,\n};\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// The properties SAGE brings handlers for. A level warns when a scene carries one of these and nothing is\n// registered to act on it, since registering is the game's to do and forgetting is otherwise silent\n// (design/authoring.md#registering-a-handler).\nexport const BUILT_IN_PROPERTIES = [\n 'collider',\n 'lod_distances',\n 'occlusion',\n 'sound',\n 'trigger',\n 'visible',\n] as const;\n\n/**\n * Registers every built-in property handler with the given level manager, one per name in\n * `BUILT_IN_PROPERTIES`. A game that never calls this loads its levels with none of them.\n */\nexport function registerAllPropertyHandlers(levelManager : Pick<LevelManager, 'registerPropertyHandler'>) : void\n{\n registerColliderHandler(levelManager);\n registerLodHandler(levelManager);\n registerOcclusionHandler(levelManager);\n registerSoundHandler(levelManager);\n registerTriggerHandler(levelManager);\n registerVisibleHandler(levelManager);\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Abstract Level Class\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { Color3 } from '@babylonjs/core';\nimport { ClusteredLightContainer } from '@babylonjs/core/Lights/Clustered/clusteredLightContainer';\n\nimport type { Scene, TransformNode } from '@babylonjs/core';\nimport type { NodeEntityRegistry, NodeSimulation } from '../entities/nodeSimulation.ts';\nimport type { MeshLoader } from '../entities/nodeSimulationContext.ts';\nimport type { LoggerInterface } from '../interfaces/logger.ts';\nimport type {\n ColorConfig,\n HandlerContext,\n LevelConfig,\n LevelContext,\n LevelInstance,\n LevelLoadOptions,\n PropertyHandler,\n} from '../interfaces/level.ts';\n\nimport { OutlineManager } from '../managers/outline.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * The base a game subclasses when it builds a level's scene in code. A subclass writes one method,\n * `buildScene`, and reaches every dependency it has through `this.context`; the base drives the load,\n * publishes the level's own events, and owns the scene, the simulation built against it, and the teardown\n * (design/levels.md#the-base-class).\n *\n * The level manager is the factory: it constructs a subclass with the configuration and the context, so a\n * game registers a class and loads a level rather than constructing one itself.\n *\n * ```typescript\n * levelManager.registerLevelClass('my-level', MyCustomLevel);\n *\n * const level = await levelManager.loadLevel({ name: 'Level1', class: 'my-level' });\n * ```\n */\nexport abstract class Level<Registry extends NodeEntityRegistry = NodeEntityRegistry> implements LevelInstance\n{\n public readonly name : string;\n protected readonly _log : LoggerInterface;\n protected readonly _context : LevelContext<Registry>;\n protected _scene : Scene | null = null;\n protected _forRestore = false;\n\n /**\n * The simulation this level's scene was built against. A transition loads the next level before\n * disposing this one, so by the time this level despawns what it spawned, the engine already holds the\n * next level's simulation, minting the same IDs.\n */\n protected _simulation : NodeSimulation<Registry> | null = null;\n public clusteredLights : ClusteredLightContainer | null = null;\n public outlines : OutlineManager | null = null;\n\n //------------------------------------------------------------------------------------------------------------------\n // Constructor\n //------------------------------------------------------------------------------------------------------------------\n\n constructor(config : LevelConfig, context : LevelContext<Registry>)\n {\n this.name = config.name;\n this._context = context;\n this._log = context.logger.getLogger(`Level:${ config.name }`);\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Accessors\n //------------------------------------------------------------------------------------------------------------------\n\n get scene() : Scene | null\n {\n return this._scene;\n }\n\n get isLoaded() : boolean\n {\n return this._scene !== null;\n }\n\n // Every dependency this level has, by name, as the manager handed them\n // (design/levels.md#what-a-level-is-handed).\n protected get context() : LevelContext<Registry>\n {\n return this._context;\n }\n\n get propertyHandlers() : Map<string, PropertyHandler>\n {\n return this._context.propertyHandlers;\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Protected Methods\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * The simulation this level owns, or nothing for a level that never built one. The level manager binds\n * the engine to the current level's simulation (design/simulation.md#which-simulation-the-engine-holds).\n */\n get $simulation() : NodeSimulation<Registry> | null\n {\n return this._simulation;\n }\n\n /**\n * Takes over a simulation built to replace this level's own, as a save load does once it has restored\n * into a fresh one (design/simulation.md#saved-games).\n */\n $adoptSimulation(simulation : NodeSimulation<Registry>) : void\n {\n this._simulation = simulation;\n }\n\n /**\n * Builds this level's simulation for the scene it just created, named after the level, and keeps it. A\n * subclass calls this from `buildScene` before it spawns anything, since a NodeEntity constructs its node\n * in its simulation's scene (design/levels.md#the-scene-and-its-simulation).\n */\n protected rebuildSimulation(scene : Scene, meshSource ?: MeshLoader) : NodeSimulation<Registry>\n {\n this._simulation = this._context.rebuildSimulation(scene, meshSource);\n\n return this._simulation;\n }\n\n /**\n * Publishes one more `level:progress` at the number and the message a subclass names, which is how a\n * level reports its own progress between the 0 and the 100 the base publishes\n * (design/levels.md#the-base-class).\n */\n protected $emitProgress(progress : number, message : string) : void\n {\n this._context.bus.emit({\n type: 'level:progress',\n senderID: this.name,\n payload: {\n levelName: this.name,\n progress,\n message,\n },\n });\n }\n\n //------------------------------------------------------------------------------------------------------------------\n\n protected _toColor3(color : ColorConfig) : Color3\n {\n return new Color3(color.r, color.g, color.b);\n }\n\n /**\n * A node's metadata as one flat record: its own top-level keys with its glTF extras merged onto them, extras\n * winning where both name one. The `gltf` container itself is left out, since it is the loader's record and\n * never an authored property, so a node whose extras are empty and whose own keys are too normalizes to an\n * empty record and the walk skips it (design/authoring.md#the-walk).\n */\n protected _getNormalizedMetadata(node : TransformNode) : Record<string, unknown> | null\n {\n if(!node.metadata)\n {\n return null;\n }\n\n const own : Record<string, unknown> = { ...(node.metadata as Record<string, unknown>) };\n const gltf = own.gltf as { extras ?: Record<string, unknown> } | undefined;\n delete own.gltf;\n\n return { ...own, ...gltf?.extras };\n }\n\n /**\n * Run registered property handlers on a node.\n */\n protected async _runPropertyHandlers(node : TransformNode) : Promise<void>\n {\n // Babylon leaves a node's metadata null until something sets it, and a Level subclass building its\n // own scene reaches this with whatever nodes it has (design/authoring.md#property-handlers).\n const metadata = node.metadata as Record<string, unknown> | null | undefined;\n\n if(metadata === null || metadata === undefined)\n {\n return;\n }\n\n for(const [ property, handler ] of this.propertyHandlers)\n {\n if(property in metadata)\n {\n try\n {\n // eslint-disable-next-line no-await-in-loop\n await handler(node, metadata[property], this, this.#handlerContext());\n }\n catch (error)\n {\n this._log.error(\n `Error in property handler '${ property }' for node '${ node.name }':`,\n error\n );\n }\n }\n }\n }\n\n // A handler is handed the bus, the audio manager where there is one, and the logging utility's logger for\n // handlers, and nothing that reaches past the level (design/authoring.md#what-a-handler-is-handed).\n #handlerContext() : HandlerContext\n {\n return {\n bus: this._context.bus,\n audioManager: this._context.audioManager,\n logger: this._context.logger.getLogger('PropertyHandlers'),\n };\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Public Methods\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Builds the scene through `buildScene`, publishing `level:progress` at 0 and at 100 around it and\n * `level:complete` after, or `level:error` before rethrowing when building fails. A level already holding a\n * scene warns and answers with the one it has.\n */\n async load(options ?: LevelLoadOptions) : Promise<Scene>\n {\n if(this._scene)\n {\n this._log.warn(`Level ${ this.name } is already loaded`);\n return this._scene;\n }\n\n this._forRestore = options?.forRestore ?? false;\n\n this._log.info(`Loading level: ${ this.name }`);\n this._log.time(`level-${ this.name }-load`);\n\n try\n {\n this.$emitProgress(0, 'Starting level load...');\n\n this._scene = await this.buildScene();\n\n this.$emitProgress(100, 'Level loaded successfully');\n this._context.bus.emit({\n type: 'level:complete',\n senderID: this.name,\n payload: {\n levelName: this.name,\n message: 'Level loaded successfully',\n level: this,\n },\n });\n\n this._log.timeEnd(`level-${ this.name }-load`);\n this._log.info(`Level ${ this.name } loaded successfully`);\n\n return this._scene;\n }\n catch (error)\n {\n this._log.error(`Failed to load level ${ this.name }:`, error);\n this._context.bus.emit({\n type: 'level:error',\n senderID: this.name,\n payload: {\n levelName: this.name,\n message: 'Failed to load level',\n error,\n },\n });\n throw error;\n }\n }\n\n /**\n * Builds the scene: a subclass answers with the scene it built, reaching what it needs through `this.context`\n * (design/levels.md#the-base-class).\n */\n protected abstract buildScene() : Promise<Scene>;\n\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Called when this level becomes the active level during a transition.\n * Override in subclasses to perform activation logic (e.g., start music, show UI).\n * Default implementation is a no-op.\n */\n onActivate?() : Promise<void> | void;\n\n /**\n * Called when this level is being replaced during a transition.\n * Override in subclasses to perform cleanup logic (e.g., fade out, save state).\n * Default implementation is a no-op.\n */\n onDeactivate?() : Promise<void> | void;\n\n /**\n * Releases what the base owns: the outline manager, the clustered light container, and the scene. Nothing for\n * a level that never loaded one, and nothing the second time (design/architecture.md#teardown).\n */\n async dispose() : Promise<void>\n {\n if(this._scene)\n {\n this._log.info(`Disposing level: ${ this.name }`);\n\n if(this.outlines)\n {\n this.outlines.dispose();\n this.outlines = null;\n }\n\n if(this.clusteredLights)\n {\n this.clusteredLights.dispose();\n this.clusteredLights = null;\n }\n\n this._scene.dispose();\n this._scene = null;\n }\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Outline Manager\n//\n// Manages named SelectionOutlineLayer instances for entity highlighting.\n// Each layer tracks a Set<NodeEntity> and rebuilds its selection on any change.\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { Color3, type Scene, SelectionOutlineLayer } from '@babylonjs/core';\n\nimport type { NodeEntity } from '../entities/nodeEntity.ts';\nimport type { OutlineLayerConfig } from '../interfaces/level.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\ninterface OutlineLayerEntry\n{\n layer : SelectionOutlineLayer;\n entities : Set<NodeEntity>;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport class OutlineManager\n{\n private _scene : Scene;\n private _layers = new Map<string, OutlineLayerEntry>();\n\n constructor(scene : Scene, configs ?: Record<string, OutlineLayerConfig>)\n {\n this._scene = scene;\n\n if(configs)\n {\n for(const [ name, config ] of Object.entries(configs))\n {\n this.create(name, config);\n }\n }\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Public API\n //------------------------------------------------------------------------------------------------------------------\n\n get(name : string) : SelectionOutlineLayer | undefined\n {\n return this._layers.get(name)?.layer;\n }\n\n create(name : string, config ?: OutlineLayerConfig) : SelectionOutlineLayer\n {\n const existing = this._layers.get(name);\n if(existing)\n {\n return existing.layer;\n }\n\n const layer = new SelectionOutlineLayer(`sage-outline-${ name }`, this._scene);\n\n if(config?.color)\n {\n layer.outlineColor = new Color3(config.color.r, config.color.g, config.color.b);\n }\n\n if(config?.thickness !== undefined) { layer.outlineThickness = config.thickness; }\n if(config?.occlusionStrength !== undefined) { layer.occlusionStrength = config.occlusionStrength; }\n\n this._layers.set(name, { layer, entities: new Set() });\n return layer;\n }\n\n highlightEntity(layerName : string, entity : NodeEntity) : void\n {\n const entry = this._layers.get(layerName);\n if(!entry)\n {\n return;\n }\n\n entry.entities.add(entity);\n this._rebuildSelection(entry);\n }\n\n unhighlightEntity(layerName : string, entity : NodeEntity) : void\n {\n const entry = this._layers.get(layerName);\n if(!entry)\n {\n return;\n }\n\n entry.entities.delete(entity);\n this._rebuildSelection(entry);\n }\n\n unhighlightEntityAll(entity : NodeEntity) : void\n {\n for(const entry of this._layers.values())\n {\n if(entry.entities.delete(entity))\n {\n this._rebuildSelection(entry);\n }\n }\n }\n\n clear(layerName : string) : void\n {\n const entry = this._layers.get(layerName);\n if(!entry)\n {\n return;\n }\n\n entry.entities.clear();\n entry.layer.clearSelection();\n }\n\n dispose() : void\n {\n for(const entry of this._layers.values())\n {\n entry.entities.clear();\n entry.layer.dispose();\n }\n\n this._layers.clear();\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Private\n //------------------------------------------------------------------------------------------------------------------\n\n private _rebuildSelection(entry : OutlineLayerEntry) : void\n {\n entry.layer.clearSelection();\n\n for(const entity of entry.entities)\n {\n const meshes = entity.node.getChildMeshes();\n\n if(meshes.length > 0)\n {\n entry.layer.addSelection(meshes);\n }\n }\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// GameLevel Class - Default Level Implementation\n//----------------------------------------------------------------------------------------------------------------------\n\nimport {\n ArcRotateCamera,\n type Camera,\n type Color3,\n CubeTexture,\n DirectionalLight,\n FreeCamera,\n HDRCubeTexture,\n HemisphericLight,\n MeshBuilder,\n PBRMaterial,\n PointLight,\n Quaternion,\n RectAreaLight,\n Scene,\n SoundState,\n SpotLight,\n StandardMaterial,\n type StaticSound,\n Texture,\n TransformNode,\n UniversalCamera,\n Vector3,\n} from '@babylonjs/core';\nimport { GeospatialCamera } from '@babylonjs/core/Cameras/geospatialCamera';\nimport { ClusteredLightContainer } from '@babylonjs/core/Lights/Clustered/clusteredLightContainer';\n\nimport type {\n CameraDefinition,\n EntityDefinition,\n LevelConfig,\n LevelContext,\n LightDefinition,\n SpawnDefinition,\n} from '../interfaces/level.ts';\nimport { applyPostProcessing } from '../handlers/postProcessing.ts';\nimport { defaultGravity } from '../utils/physics.ts';\nimport { getCanonicalTransform, toQuatObject, toVec3Object, toVector3 } from '../utils/vectors.ts';\n\nimport type { StateBag } from '@skewedaspect/sage-core';\nimport { NodeEntity } from '../entities/nodeEntity.ts';\nimport type { NodeEntityRegistry, NodeSimulation } from '../entities/nodeSimulation.ts';\nimport { BUILT_IN_PROPERTIES } from '../handlers/index.ts';\nimport { Level } from './level.ts';\nimport { OutlineManager } from '../managers/outline.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Metadata collected from a spawn point node.\n * Transforms are normalized to canonical Babylon space — the source level's __root__\n * conversion is removed so these values are clean for any entity type.\n */\ninterface SpawnPointData\n{\n name : string;\n position : Vector3;\n rotation : Quaternion;\n scaling : Vector3;\n node : TransformNode;\n}\n\n/**\n * Metadata collected from an entity node\n */\ninterface EntityNodeData\n{\n type : string;\n node : TransformNode;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * The level a game gets when it describes a scene in configuration instead of writing a `Level` subclass: it\n * builds the scene its configuration names, in one fixed order, and spawns what the scene's own markers name\n * (design/levels.md#gamelevel).\n */\nexport class GameLevel<Registry extends NodeEntityRegistry = NodeEntityRegistry> extends Level<Registry>\n{\n /** The level configuration */\n protected _config : LevelConfig;\n\n /** Collected spawn points from the scene */\n protected _spawnPoints : SpawnPointData[] = [];\n\n /** Collected entity nodes from the scene */\n protected _entityNodes : EntityNodeData[] = [];\n\n /** Entities spawned by this level */\n protected _spawnedEntities : NodeEntity[] = [];\n\n /** Level-scoped sounds created from config */\n protected _levelSounds = new Map<string, StaticSound>();\n\n /** Sounds that were playing before deactivation (for resume on activate) */\n private _playingSoundsBeforeDeactivate = new Set<string>();\n\n //------------------------------------------------------------------------------------------------------------------\n // Constructor\n //------------------------------------------------------------------------------------------------------------------\n\n constructor(config : LevelConfig, context : LevelContext<Registry>)\n {\n super(config, context);\n this._config = config;\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Accessors\n //------------------------------------------------------------------------------------------------------------------\n\n get config() : LevelConfig\n {\n return this._config;\n }\n\n get spawnedEntities() : readonly NodeEntity[]\n {\n return this._spawnedEntities;\n }\n\n /**\n * The simulation this level built its scene against: the one its own spawns and despawns go through,\n * whichever simulation the engine holds at the time. Throws before `buildScene` has bound one.\n */\n protected get simulation() : NodeSimulation<Registry>\n {\n if(this._simulation === null)\n {\n throw new Error(`Level ${ this.name }: rebuildSimulation has not run against this level's scene yet.`);\n }\n\n return this._simulation;\n }\n\n // A marker names its class in the scene file, so the registry checks the name at run time\n // (design/simulation.md#spawn-and-despawn). What it names has to be a class whose entities have a node, since a\n // spawn point places one and an entity marker is adopted as one (design/authoring.md#markers).\n #spawnFromMarker(name : string, state : StateBag) : NodeEntity\n {\n const entity = this.simulation.spawnByName(name, state);\n\n if(!(entity instanceof NodeEntity))\n {\n this.simulation.despawn(entity.id);\n\n throw new Error(\n `GameLevel: the marker naming \"${ name }\" spawned an entity with no node; a marker names a NodeEntity `\n + 'class.'\n );\n }\n\n return entity;\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Protected Methods\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Build the scene by loading assets, processing node metadata, and spawning entities.\n */\n protected async buildScene() : Promise<Scene>\n {\n const { sceneEngine, assetManager } = this.context;\n\n if(this._config.preload && this._config.preload.length > 0)\n {\n this.$emitProgress(2, 'Preloading assets...');\n await assetManager.preload(this._config.preload);\n }\n\n // Assigned early so property handlers can access it during the node walk.\n const scene = sceneEngine.createScene();\n this._scene = scene;\n\n // A NodeEntity always constructs its node in the simulation's own scene, so the simulation the level\n // spawns through has to be bound to this scene before anything below can spawn.\n this.rebuildSimulation(scene, this._config.meshSource);\n\n // Before the scene file, so colliders import into a scene that already has physics.\n if(this._config.physics)\n {\n this.$emitProgress(5, 'Initializing physics...');\n await this._enablePhysics(scene);\n }\n\n if(this._config.scene)\n {\n this.$emitProgress(10, 'Loading scene file...');\n await this._loadSceneFile(scene);\n }\n\n // Before cameras, so the background is ready when one activates.\n if(this._config.environment)\n {\n this.$emitProgress(15, 'Setting up environment...');\n this._processEnvironment(scene);\n }\n\n // Before post-processing, which needs an active camera.\n this._processCameras(scene);\n\n this._processLights(scene);\n\n // After lights, since clustering moves them out of the scene once they exist.\n this._setupClustering(scene);\n\n if(this._config.postProcessing)\n {\n const renderer = this._config.postProcessing.renderer ?? 'pipeline';\n this._log.info(`Applying post-processing with ${ renderer } renderer`);\n await applyPostProcessing(scene, this._config.postProcessing, this._log);\n }\n\n this.outlines = new OutlineManager(scene, this._config.outlines);\n\n this.$emitProgress(50, 'Processing scene properties...');\n await this._processNodeProperties(scene);\n\n // A level loaded to restore a save spawns nothing of its own: the restored snapshot is the sole\n // source of entities, and adoption re-binds through the nodeName already in restored state\n // (design/simulation.md#saved-games).\n if(!this._forRestore)\n {\n this.$emitProgress(70, 'Spawning entities...');\n await this._processSpawnPoints();\n\n this.$emitProgress(85, 'Configuring entities...');\n this._processEntityNodes();\n }\n\n if(this._config.sounds)\n {\n this.$emitProgress(95, 'Loading sounds...');\n await this._processLevelSounds();\n }\n\n return scene;\n }\n\n /**\n * Import all meshes from the configured scene file (GLB/GLTF/Babylon) into the scene.\n */\n private async _loadSceneFile(scene : Scene) : Promise<void>\n {\n const scenePath = this._getScenePath();\n if(!scenePath)\n {\n return;\n }\n\n this._log.debug(`Loading scene file: ${ scenePath }`);\n\n // Set right-handed coordinate system before loading so loaders respect it\n if(this._isRightHanded())\n {\n scene.useRightHandedSystem = true;\n this._log.debug('Scene set to right-handed coordinate system');\n }\n\n try\n {\n await this.context.sceneEngine.importMeshes([], scenePath, scene);\n this._log.debug('Scene file loaded successfully');\n }\n catch (error)\n {\n this._log.error(`Failed to load scene file: ${ scenePath }`, error);\n throw error;\n }\n }\n\n /**\n * Extract the scene file path from the config, which can be a string or SceneConfig object.\n */\n private _getScenePath() : string | undefined\n {\n if(!this._config.scene)\n {\n return undefined;\n }\n\n if(typeof this._config.scene === 'string')\n {\n return this._config.scene;\n }\n\n return this._config.scene.path;\n }\n\n /**\n * Check if the scene config specifies a right-handed coordinate system.\n */\n private _isRightHanded() : boolean\n {\n if(!this._config.scene || typeof this._config.scene === 'string')\n {\n return false;\n }\n\n return this._config.scene.rightHanded === true;\n }\n\n /**\n * Set up the scene environment: IBL (image-based lighting) for PBR reflections and/or a visible skybox.\n */\n private _processEnvironment(scene : Scene) : void\n {\n const env = this._config.environment;\n if(!env) { return; }\n\n // Set up IBL for PBR reflections\n if(env.ibl)\n {\n scene.environmentTexture = this._createEnvironmentTexture(env.ibl, env.iblResolution ?? 256, scene);\n this._log.debug(`IBL set from: ${ env.ibl }`);\n }\n\n // Set up visible skybox\n if(env.skybox)\n {\n const ext = this._getFileExtension(env.skybox);\n\n if(ext === '.hdr' || ext === '.env')\n {\n // HDR/env skybox — use as IBL too if no separate IBL was specified\n if(!env.ibl)\n {\n const res = env.iblResolution ?? 256;\n scene.environmentTexture = this._createEnvironmentTexture(env.skybox, res, scene);\n }\n\n if(scene.environmentTexture)\n {\n scene.createDefaultSkybox(scene.environmentTexture, true, env.skyboxSize ?? 1000, 0, false);\n }\n }\n else\n {\n // JPG/PNG — create a textured sphere\n const size = env.skyboxSize ?? 1000;\n const skybox = MeshBuilder.CreateSphere('skybox', { diameter: size, segments: 32 }, scene);\n\n const mat = new StandardMaterial('skybox-mat', scene);\n mat.backFaceCulling = false;\n mat.disableLighting = true;\n mat.emissiveTexture = new Texture(env.skybox, scene);\n skybox.material = mat;\n skybox.infiniteDistance = true;\n }\n\n this._log.debug(`Skybox set from: ${ env.skybox }`);\n }\n\n // Apply rotation to environment texture\n if(env.rotation !== undefined && scene.environmentTexture)\n {\n (scene.environmentTexture as HDRCubeTexture).rotationY = env.rotation;\n }\n }\n\n /**\n * Create an environment texture from an HDR or env file.\n */\n private _createEnvironmentTexture(path : string, resolution : number, scene : Scene) : CubeTexture | HDRCubeTexture\n {\n const ext = this._getFileExtension(path);\n\n if(ext === '.env')\n {\n return CubeTexture.CreateFromPrefilteredData(path, scene);\n }\n\n return new HDRCubeTexture(path, scene, resolution);\n }\n\n private _getFileExtension(path : string) : string\n {\n const dotIndex = path.lastIndexOf('.');\n\n if(dotIndex >= 0)\n {\n return path.slice(dotIndex).toLowerCase();\n }\n\n return '';\n }\n\n //------------------------------------------------------------------------------------------------------------------\n\n // GameCanvas admits an OffscreenCanvas, which Camera.attachControl cannot take; only a real\n // HTMLCanvasElement attaches.\n private _attachableCanvas() : HTMLCanvasElement | null\n {\n const canvas = this.context.canvas;\n\n if(canvas === null)\n {\n return null;\n }\n\n if(typeof OffscreenCanvas !== 'undefined' && canvas instanceof OffscreenCanvas)\n {\n return null;\n }\n\n return canvas as HTMLCanvasElement;\n }\n\n /**\n * An entry whose key names an imported camera overrides it, and one whose key names none creates a camera\n * of the type it declares. With no camera configuration at all, the first imported camera activates.\n */\n private _processCameras(scene : Scene) : void\n {\n const cameraConfigs = this._config.cameras;\n const canvas = this._attachableCanvas();\n\n if(!cameraConfigs)\n {\n // With no camera configuration at all, the first imported camera activates.\n if(scene.cameras.length > 0)\n {\n scene.activeCamera = scene.cameras[0];\n\n if(canvas)\n {\n scene.activeCamera.attachControl(canvas, true);\n }\n\n this._log.debug(`Activated imported camera: \"${ scene.activeCamera.name }\"`);\n }\n\n return;\n }\n\n let activeCamera : Camera | null = null;\n\n for(const [ name, config ] of Object.entries(cameraConfigs))\n {\n const existing = scene.getCameraByName(name);\n\n if(existing)\n {\n this._applyCameraConfig(existing, config);\n this._log.debug(`Applied overrides to camera: \"${ name }\"`);\n\n if(!activeCamera || config.active)\n {\n activeCamera = existing;\n }\n }\n else if(config.type)\n {\n const camera = this._createCamera(name, config, scene);\n\n if(camera !== null)\n {\n this._log.debug(`Created ${ config.type } camera: \"${ name }\"`);\n\n if(!activeCamera || config.active)\n {\n activeCamera = camera;\n }\n }\n }\n else\n {\n this._log.warn(\n `Camera \"${ name }\" not found in scene and no type specified — skipping`\n );\n }\n }\n\n if(activeCamera)\n {\n scene.activeCamera = activeCamera;\n\n const activeDef = cameraConfigs[activeCamera.name];\n if(canvas && activeDef?.attachControl !== false)\n {\n activeCamera.attachControl(canvas, true);\n }\n }\n }\n\n /**\n * Create a camera of the type an entry declares, or nothing for an entry that cannot be built, which is\n * logged and skipped (design/levels.md#the-load-sequence).\n */\n private _createCamera(name : string, config : CameraDefinition, scene : Scene) : Camera | null\n {\n let pos = Vector3.Zero();\n if(config.position)\n {\n pos = toVector3(config.position);\n }\n\n let camera : Camera;\n\n switch (config.type)\n {\n case 'arcRotate':\n {\n let target = Vector3.Zero();\n if(config.target)\n {\n target = toVector3(config.target);\n }\n\n camera = new ArcRotateCamera(\n name,\n config.alpha ?? Math.PI / 2,\n config.beta ?? Math.PI / 3,\n config.radius ?? 10,\n target,\n scene\n );\n break;\n }\n\n case 'universal':\n {\n camera = new UniversalCamera(name, pos, scene);\n break;\n }\n\n case 'geospatial':\n {\n if(!config.planetRadius)\n {\n this._log.warn(`Geospatial camera \"${ name }\" names no planetRadius; skipped.`);\n return null;\n }\n\n // Every other field this camera takes is set once, by _applyCameraConfig, which runs\n // unconditionally on the camera this switch builds.\n camera = new GeospatialCamera(name, scene, {\n planetRadius: config.planetRadius,\n });\n break;\n }\n\n case 'free':\n default:\n {\n camera = new FreeCamera(name, pos, scene);\n break;\n }\n }\n\n this._applyCameraConfig(camera, config);\n return camera;\n }\n\n /**\n * Apply shared camera properties from a config to an existing camera.\n */\n private _applyCameraConfig(camera : Camera, config : CameraDefinition) : void\n {\n if(config.fov !== undefined) { camera.fov = config.fov; }\n if(config.position) { camera.position = toVector3(config.position); }\n if(config.minZ !== undefined) { camera.minZ = config.minZ; }\n if(config.maxZ !== undefined) { camera.maxZ = config.maxZ; }\n\n if(camera instanceof FreeCamera)\n {\n if(config.speed !== undefined) { camera.speed = config.speed; }\n if(config.rotation) { camera.rotation = toVector3(config.rotation); }\n }\n\n if(camera instanceof ArcRotateCamera)\n {\n // setTarget recomputes alpha, beta, and radius from the camera's position, so the entry's own\n // values are written after it (design/levels.md#the-load-sequence).\n if(config.target) { camera.setTarget(toVector3(config.target)); }\n if(config.alpha !== undefined) { camera.alpha = config.alpha; }\n if(config.beta !== undefined) { camera.beta = config.beta; }\n if(config.radius !== undefined) { camera.radius = config.radius; }\n if(config.lowerRadiusLimit !== undefined) { camera.lowerRadiusLimit = config.lowerRadiusLimit; }\n if(config.upperRadiusLimit !== undefined) { camera.upperRadiusLimit = config.upperRadiusLimit; }\n if(config.lowerBetaLimit !== undefined) { camera.lowerBetaLimit = config.lowerBetaLimit; }\n if(config.upperBetaLimit !== undefined) { camera.upperBetaLimit = config.upperBetaLimit; }\n if(config.wheelPrecision !== undefined) { camera.wheelPrecision = config.wheelPrecision; }\n }\n\n if(camera instanceof GeospatialCamera)\n {\n if(config.planetRadius !== undefined) { camera.limits.planetRadius = config.planetRadius; }\n if(config.yaw !== undefined) { camera.yaw = config.yaw; }\n if(config.pitch !== undefined) { camera.pitch = config.pitch; }\n if(config.radius !== undefined) { camera.radius = config.radius; }\n if(config.center) { camera.center = toVector3(config.center); }\n if(config.checkCollisions !== undefined) { camera.checkCollisions = config.checkCollisions; }\n if(config.yawMin !== undefined) { camera.limits.yawMin = config.yawMin; }\n if(config.yawMax !== undefined) { camera.limits.yawMax = config.yawMax; }\n if(config.radiusMin !== undefined) { camera.limits.radiusMin = config.radiusMin; }\n if(config.radiusMax !== undefined) { camera.limits.radiusMax = config.radiusMax; }\n if(config.pitchMin !== undefined) { camera.limits.pitchMin = config.pitchMin; }\n if(config.pitchMax !== undefined) { camera.limits.pitchMax = config.pitchMax; }\n }\n }\n\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Lights override and create by key the way cameras do. With no light configuration at all, imported\n * lights are left as they are.\n */\n private _processLights(scene : Scene) : void\n {\n const lightConfigs = this._config.lights;\n if(!lightConfigs) { return; }\n\n for(const [ name, config ] of Object.entries(lightConfigs))\n {\n const existing = scene.getLightByName(name);\n\n if(existing)\n {\n this._applyLightConfig(existing, config);\n this._log.debug(`Applied overrides to light: \"${ name }\"`);\n }\n else if(config.type)\n {\n this._createLight(name, config, scene);\n this._log.debug(`Created ${ config.type } light: \"${ name }\"`);\n }\n else\n {\n this._log.warn(\n `Light \"${ name }\" not found in scene and no type specified — skipping`\n );\n }\n }\n }\n\n /**\n * Create a light of the type an entry declares.\n */\n private _createLight(name : string, config : LightDefinition, scene : Scene) : void\n {\n let dir = new Vector3(0, -1, 0);\n if(config.direction)\n {\n dir = toVector3(config.direction);\n }\n\n let pos = Vector3.Zero();\n if(config.position)\n {\n pos = toVector3(config.position);\n }\n\n switch (config.type)\n {\n case 'hemispheric':\n {\n const light = new HemisphericLight(name, dir, scene);\n\n if(config.groundColor)\n {\n light.groundColor = this._toColor3(config.groundColor);\n }\n\n this._applyLightConfig(light, config);\n break;\n }\n\n case 'directional':\n {\n const light = new DirectionalLight(name, dir, scene);\n this._applyLightConfig(light, config);\n break;\n }\n\n case 'point':\n {\n const light = new PointLight(name, pos, scene);\n this._applyLightConfig(light, config);\n break;\n }\n\n case 'spot':\n {\n const light = new SpotLight(\n name,\n pos,\n dir,\n config.angle ?? Math.PI / 3,\n config.exponent ?? 2,\n scene\n );\n this._applyLightConfig(light, config);\n break;\n }\n\n case 'rectarea':\n {\n const light = new RectAreaLight(\n name,\n pos,\n config.width ?? 1,\n config.height ?? 1,\n scene\n );\n this._applyLightConfig(light, config);\n\n // RectAreaLight emits along local -Z and has no direction property. If a direction\n // is specified, parent the light to a rotated pivot node that orients -Z toward it.\n if(config.direction)\n {\n const pivot = new TransformNode(`${ name }_pivot`, scene);\n pivot.position = light.position.clone();\n light.position = Vector3.Zero();\n light.parent = pivot;\n\n // lookAt aligns +Z toward the target; we want -Z toward dir, so look opposite\n pivot.lookAt(pivot.position.subtract(dir));\n }\n\n break;\n }\n }\n }\n\n /**\n * Apply shared light properties from a config to an existing light.\n */\n private _applyLightConfig(\n light : { intensity : number; diffuse : Color3; specular : Color3 },\n config : LightDefinition\n ) : void\n {\n if(config.intensity !== undefined) { light.intensity = config.intensity; }\n if(config.diffuse) { light.diffuse = this._toColor3(config.diffuse); }\n if(config.specular) { light.specular = this._toColor3(config.specular); }\n }\n\n /**\n * Set up clustered lighting: create a container, move eligible lights into it,\n * and fix PBR material falloff for compatibility.\n */\n private _setupClustering(scene : Scene) : void\n {\n const clusterConfig = this._config.clustering;\n if(!clusterConfig?.enabled)\n {\n return;\n }\n\n // Collect eligible lights from the scene\n const eligibleLights = scene.lights.filter(\n (light) => ClusteredLightContainer.IsLightSupported(light)\n );\n\n if(eligibleLights.length === 0)\n {\n this._log.debug('Clustering enabled but no eligible lights found');\n return;\n }\n\n // Remove eligible lights from the scene before adding to container\n for(const light of eligibleLights)\n {\n scene.removeLight(light);\n }\n\n // Create the container with collected lights\n const container = new ClusteredLightContainer('sage-clustered', eligibleLights, scene);\n\n // Apply tuning parameters\n if(clusterConfig.horizontalTiles !== undefined) { container.horizontalTiles = clusterConfig.horizontalTiles; }\n if(clusterConfig.verticalTiles !== undefined) { container.verticalTiles = clusterConfig.verticalTiles; }\n if(clusterConfig.depthSlices !== undefined) { container.depthSlices = clusterConfig.depthSlices; }\n if(clusterConfig.maxRange !== undefined) { container.maxRange = clusterConfig.maxRange; }\n\n this.clusteredLights = container;\n\n this._log.debug(\n `Clustered lighting enabled: ${ eligibleLights.length } lights, `\n + `${ container.horizontalTiles }x${ container.verticalTiles } tiles, `\n + `${ container.depthSlices } depth slices`\n );\n\n // Fix PBR material falloff — physical falloff is incompatible with clustering\n for(const material of scene.materials)\n {\n if(material instanceof PBRMaterial)\n {\n material.useGLTFLightFalloff = true;\n }\n }\n }\n\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Configure and enable the Havok physics plugin with the level's gravity settings.\n */\n private async _enablePhysics(scene : Scene) : Promise<void>\n {\n const physicsConfig = this._config.physics;\n\n let gravity = defaultGravity();\n\n if(typeof physicsConfig === 'object' && physicsConfig.gravity)\n {\n gravity = new Vector3(\n physicsConfig.gravity.x,\n physicsConfig.gravity.y,\n physicsConfig.gravity.z\n );\n }\n\n // When large world rendering is active, enable Havok multi-region physics\n let floatingOriginRadius : number | undefined;\n if(this.context.largeWorldRendering)\n {\n floatingOriginRadius = 100000;\n }\n\n this._log.debug(`Enabling physics with gravity: ${ gravity.toString() }`);\n await this.context.sceneEngine.enablePhysics(scene, gravity, floatingOriginRadius);\n }\n\n /**\n * Walk all transform nodes and meshes, normalizing glTF metadata and dispatching to property handlers.\n */\n private async _processNodeProperties(scene : Scene) : Promise<void>\n {\n const nodes = scene.transformNodes.concat(scene.meshes as TransformNode[]);\n const carrying : TransformNode[] = [];\n\n // Normalize the whole scene before dispatching to any of it. A handler reaching sideways -- a\n // collider looking for the child marked as its collision source, say -- needs that node's\n // properties reachable by name whether or not the walk has arrived at it yet, and glTF puts them\n // under metadata.gltf.extras until it does (design/authoring.md#property-handlers).\n for(const node of nodes)\n {\n const metadata = this._getNormalizedMetadata(node);\n\n if(metadata && Object.keys(metadata).length > 0)\n {\n node.metadata = { ...node.metadata, ...metadata };\n carrying.push(node);\n }\n }\n\n for(const node of carrying)\n {\n this._warnUnhandledProperties(node);\n\n // eslint-disable-next-line no-await-in-loop\n await this._processNodeMetadata(node);\n }\n\n this._log.debug(\n `Processed ${ nodes.length } nodes, found ${ this._spawnPoints.length } spawn points `\n + `and ${ this._entityNodes.length } entity nodes`\n );\n }\n\n // Authoring a property nothing is registered for produces no body, no trigger, no sound and no error,\n // so the only sign is authoring that did nothing. Only SAGE's own names are worth saying: a glb carries\n // whatever else its exporter wrote (design/authoring.md#registering-a-handler).\n private _warnUnhandledProperties(node : TransformNode) : void\n {\n const metadata = node.metadata as Record<string, unknown>;\n\n for(const property of BUILT_IN_PROPERTIES)\n {\n if(property in metadata && !this.propertyHandlers.has(property))\n {\n this._log.warn(\n `Node '${ node.name }' carries '${ property }' and no handler is registered for it. `\n + 'Call registerAllPropertyHandlers on the level manager to install the built-in handlers.'\n );\n }\n }\n }\n\n /**\n * Check a node's metadata for spawn/entity markers and run any registered property handlers.\n */\n private async _processNodeMetadata(node : TransformNode) : Promise<void>\n {\n // Normalize the spawn marker into canonical Babylon space. glTF imports\n // wrap content under a conversion root (RH→LH), and decomposing a\n // mirrored matrix produces poisoned scaling/rotation. getCanonicalTransform\n // cancels the import root before decomposing, giving clean values that\n // work uniformly for primitives and GLB entities.\n if('spawn' in node.metadata)\n {\n const { position, rotation, scaling } = getCanonicalTransform(node);\n\n this._spawnPoints.push({\n name: node.metadata.spawn as string,\n position,\n rotation,\n scaling,\n node,\n });\n }\n\n // Check for entity property\n if('entity' in node.metadata)\n {\n this._entityNodes.push({\n type: node.metadata.entity as string,\n node,\n });\n }\n\n // Run registered property handlers\n await this._runPropertyHandlers(node);\n }\n\n /**\n * Match collected spawn points against the level config's spawn definitions and instantiate entities.\n */\n private async _processSpawnPoints() : Promise<void>\n {\n for(const spawnPoint of this._spawnPoints)\n {\n const spawnDef = this._config.spawns?.[spawnPoint.name];\n\n if(!spawnDef)\n {\n this._log.warn(`No spawn definition found for '${ spawnPoint.name }', skipping`);\n }\n else\n {\n // eslint-disable-next-line no-await-in-loop\n await this._processSpawnPoint(spawnPoint, spawnDef);\n }\n }\n }\n\n /**\n * Spawn an entity at the given spawn point and dispose of the placeholder node.\n */\n private async _processSpawnPoint(spawnPoint : SpawnPointData, spawnDef : SpawnDefinition) : Promise<void>\n {\n try\n {\n const entity = await this._spawnEntity(spawnDef, spawnPoint);\n this._spawnedEntities.push(entity);\n\n // Remove the placeholder node from the scene\n spawnPoint.node.dispose();\n\n this._log.debug(`Spawned entity '${ spawnDef.entity }' at spawn point '${ spawnPoint.name }'`);\n }\n catch (error)\n {\n this._log.error(`Failed to spawn entity at spawn point '${ spawnPoint.name }':`, error);\n }\n }\n\n /**\n * Spawn an entity through the simulation, inheriting position and rotation from the spawn point. The node\n * and its mesh are the simulation's own business, built by NodeEntity's constructor from the class name and\n * state -- the level does no mesh building of its own.\n *\n * A spawn point's scaling has nowhere to go: NodeEntityState declares position and rotation, not scaling,\n * and inventing a state field for it is not this level's call to make. It is dropped.\n */\n private async _spawnEntity(spawnDef : SpawnDefinition, spawnPoint : SpawnPointData) : Promise<NodeEntity>\n {\n const initialState : Record<string, unknown> = {\n ...spawnDef.config,\n tags: spawnDef.tags ?? [],\n position: toVec3Object(spawnPoint.position),\n rotation: toQuatObject(spawnPoint.rotation),\n };\n\n if(spawnDef.displayName !== undefined)\n {\n initialState.displayName = spawnDef.displayName;\n }\n\n if(spawnDef.debugCollider !== undefined)\n {\n initialState.debugCollider = spawnDef.debugCollider;\n }\n\n const state = initialState as unknown as StateBag;\n\n return this.#spawnFromMarker(spawnDef.entity, state);\n }\n\n /**\n * Match collected entity nodes against the level config's entity definitions and spawn entities adopting\n * their marked node.\n */\n private _processEntityNodes() : void\n {\n for(const entityNode of this._entityNodes)\n {\n const entityDef = this._config.entities?.[entityNode.type];\n\n if(!entityDef)\n {\n this._log.warn(`No entity definition found for '${ entityNode.type }', skipping`);\n }\n else\n {\n this._spawnEntityNode(entityDef, entityNode);\n }\n }\n }\n\n /**\n * Spawn an entity adopting an entity marker's node. `nodeName` is the marked node's own name, written by\n * the level rather than by configuration, since it is the binding key adoption resolves through\n * (design/authoring.md#the-binding-key).\n */\n private _spawnEntityNode(entityDef : EntityDefinition, entityNode : EntityNodeData) : void\n {\n try\n {\n const initialState : Record<string, unknown> = {\n ...entityDef.config,\n tags: entityDef.tags ?? [],\n nodeName: entityNode.node.name,\n };\n\n if(entityDef.displayName !== undefined)\n {\n initialState.displayName = entityDef.displayName;\n }\n\n if(entityDef.debugCollider !== undefined)\n {\n initialState.debugCollider = entityDef.debugCollider;\n }\n\n const state = initialState as unknown as StateBag;\n const entity = this.#spawnFromMarker(entityDef.entity, state);\n this._spawnedEntities.push(entity);\n\n this._log.debug(`Spawned entity '${ entityDef.entity }' adopting node '${ entityNode.node.name }'`);\n }\n catch (error)\n {\n this._log.error(`Failed to spawn entity adopting node '${ entityNode.node.name }':`, error);\n }\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Level Sounds\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Create the sounds the configuration names, keeping each by name so it follows the level: paused on\n * deactivation, played again on activation, disposed with the level (design/levels.md#level-sounds).\n */\n private async _processLevelSounds() : Promise<void>\n {\n const soundConfigs = this._config.sounds;\n if(!soundConfigs) { return; }\n\n const { audioManager } = this.context;\n if(!audioManager)\n {\n this._log.warn('No AudioManager configured. Level sounds will not be created.');\n return;\n }\n\n for(const [ name, config ] of Object.entries(soundConfigs))\n {\n try\n {\n // eslint-disable-next-line no-await-in-loop\n const sound = await audioManager.createSound(name, config.url, config.channel, {\n loop: config.loop ?? false,\n volume: config.volume ?? 1,\n });\n\n this._levelSounds.set(name, sound);\n\n if(config.autoplay)\n {\n sound.play();\n }\n\n this._log.debug(`Created level sound: \"${ name }\"`);\n }\n catch (error)\n {\n this._log.error(`Failed to create level sound \"${ name }\":`, error);\n }\n }\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Lifecycle\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Pause all playing level sounds when the level is deactivated.\n * Subclasses that override this must call `super.onDeactivate()`.\n */\n async onDeactivate() : Promise<void>\n {\n for(const [ name, sound ] of this._levelSounds)\n {\n if(sound.state === SoundState.Started)\n {\n this._playingSoundsBeforeDeactivate.add(name);\n sound.pause();\n }\n }\n }\n\n /**\n * Resume previously playing level sounds when the level is reactivated.\n * Subclasses that override this must call `super.onActivate()`.\n */\n async onActivate() : Promise<void>\n {\n for(const name of this._playingSoundsBeforeDeactivate)\n {\n const sound = this._levelSounds.get(name);\n if(sound)\n {\n sound.play();\n }\n }\n\n this._playingSoundsBeforeDeactivate.clear();\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Public Methods\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Dispose of this level's resources\n */\n override async dispose() : Promise<void>\n {\n // Despawn all spawned entities, from the simulation this level built them in -- the engine may\n // already hold the next level's.\n if(this._simulation !== null)\n {\n for(const entity of this._spawnedEntities)\n {\n this._simulation.despawn(entity.id);\n }\n }\n\n this._simulation = null;\n this._spawnedEntities = [];\n this._spawnPoints = [];\n this._entityNodes = [];\n\n // Dispose level sounds\n for(const sound of this._levelSounds.values())\n {\n sound.dispose();\n }\n this._levelSounds.clear();\n this._playingSoundsBeforeDeactivate.clear();\n\n await super.dispose();\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Level Manager\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { LoggerInterface } from '../interfaces/logger.ts';\nimport type {\n LevelConfig,\n LevelConstructor,\n LevelContext,\n LevelLoadOptions,\n PropertyHandler,\n} from '../interfaces/level.ts';\nimport type { Disposable } from '../interfaces/lifecycle.ts';\nimport type {\n LevelCompletePayload,\n LevelErrorPayload,\n LevelProgressPayload,\n} from '../events/payloads.ts';\n\nimport { Level } from '../classes/level.ts';\nimport { GameLevel } from '../classes/gameLevel.ts';\nimport type { EventBus } from '@skewedaspect/sage-core';\nimport type { GameEngine } from '../classes/gameEngine.ts';\nimport type { NodeEntityRegistry, NodeSimulation } from '../entities/nodeSimulation.ts';\n\n// Utils\nimport { LoggingUtility, NullBackend, SAGELogger } from '../utils/logger.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * The two options that make a transition something other than a plain move\n * (design/levels.md#transitions).\n */\nexport interface TransitionOptions extends LevelLoadOptions\n{\n /** Leaves the level being moved from loaded rather than disposing it. */\n keepAlive ?: boolean;\n\n /**\n * Builds the new level's scene and simulation and stops there, leaving the engine and the current level\n * alone until a later `activateLevel` binds what the preload built.\n */\n preloadOnly ?: boolean;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * The factory and the bookkeeping for levels: it registers configurations and `Level` subclasses, creates\n * level instances with the context each is handed, tracks which are loaded, and decides which one is current\n * (design/levels.md#the-level-manager). A game reaches levels through it rather than constructing one.\n *\n * ```typescript\n * levelManager.registerLevelConfig({ name: 'Level1', scene: 'level1.glb' });\n * levelManager.registerLevelClass('custom', MyCustomLevel);\n *\n * await levelManager.activateLevel('Level1');\n * ```\n */\nexport class LevelManager<Registry extends NodeEntityRegistry = NodeEntityRegistry> implements Disposable\n{\n private _bus : EventBus;\n private _gameEngine : GameEngine<Registry> | null = null;\n private _log : LoggerInterface;\n private _logger : LoggingUtility;\n\n /** Map of registered level configurations by name */\n private _levelConfigs = new Map<string, LevelConfig>();\n\n /** Map of registered custom level classes by name */\n private _levelClasses = new Map<string, LevelConstructor<Registry>>();\n\n /** Map of loaded level instances by name */\n private _loadedLevels = new Map<string, Level<Registry>>();\n\n /** Map of registered property handlers for scene node metadata */\n private _propertyHandlers = new Map<string, PropertyHandler>();\n\n /** The currently active level */\n private _currentLevel : Level<Registry> | null = null;\n\n /** The level currently inside load(), if any */\n private _loadingLevel : Level<Registry> | null = null;\n\n /** Unsubscribe functions for event subscriptions */\n private _eventUnsubscribers : (() => void)[] = [];\n\n //------------------------------------------------------------------------------------------------------------------\n\n /** The level the game is in, which is the one whose simulation the engine holds. */\n public get currentLevel() : Level<Registry> | null\n {\n return this._currentLevel;\n }\n\n /**\n * The level inside its own `load`, if any. `currentLevel` still names the outgoing level while an\n * incoming one builds its scene, so anything that needs the level the work is happening in -- a mesh\n * source building a mesh during spawn processing, say -- reads this instead (design/levels.md#loading).\n */\n public get loadingLevel() : Level<Registry> | null\n {\n return this._loadingLevel;\n }\n\n /** The handler map every level this manager loads dispatches its node walk through. */\n public get propertyHandlers() : Map<string, PropertyHandler>\n {\n return this._propertyHandlers;\n }\n\n //------------------------------------------------------------------------------------------------------------------\n\n constructor(\n bus : EventBus,\n logger ?: LoggingUtility\n )\n {\n this._bus = bus;\n this._logger = logger ?? new LoggingUtility('none', new NullBackend());\n this._log = logger?.getLogger('LevelManager') || new SAGELogger('LevelManager');\n\n this._eventUnsubscribers.push(\n this._bus.on('level:progress', (event) =>\n {\n this._handleProgress(event.payload);\n })\n );\n\n this._eventUnsubscribers.push(\n this._bus.on('level:complete', (event) =>\n {\n this._handleComplete(event.payload);\n })\n );\n\n this._eventUnsubscribers.push(\n this._bus.on('level:error', (event) =>\n {\n this._handleError(event.payload);\n })\n );\n\n this._log.info('LevelManager initialized');\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Internal API\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Hands the manager the engine every level context is read off. The engine takes its managers as\n * constructor arguments, so this manager exists before the engine does; `createGameEngine` hands it one\n * the moment the engine is built, before any level can load.\n */\n $setGameEngine(gameEngine : GameEngine<Registry>) : void\n {\n this._gameEngine = gameEngine;\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Private Methods\n //------------------------------------------------------------------------------------------------------------------\n\n private _handleProgress(payload : LevelProgressPayload) : void\n {\n this._log.debug(`Level ${ payload.levelName } progress: ${ payload.progress }%`);\n }\n\n private _handleComplete(payload : LevelCompletePayload) : void\n {\n this._log.info(`Level ${ payload.levelName } loaded successfully`);\n }\n\n private _handleError(payload : LevelErrorPayload) : void\n {\n this._log.error(`Level ${ payload.levelName } error: ${ payload.message }`);\n }\n\n // Every dependency a level has, by name, and no engine (design/levels.md#what-a-level-is-handed).\n private _createContext(config : LevelConfig) : LevelContext<Registry>\n {\n const engine = this._gameEngine;\n\n if(!engine)\n {\n throw new Error('LevelManager: gameEngine not set. Call $setGameEngine() first.');\n }\n\n return {\n bus: this._bus,\n sceneEngine: engine.engines.sceneEngine,\n assetManager: engine.managers.assetManager,\n audioManager: engine.managers.audioManager,\n rebuildSimulation: (scene, meshSource) => engine.rebuildSimulation(scene, config.name, meshSource),\n canvas: engine.canvas,\n largeWorldRendering: engine.largeWorldRendering,\n propertyHandlers: this._propertyHandlers,\n logger: this._logger,\n };\n }\n\n // A configuration naming a class is built with the class registered under that name; one naming none is\n // built as a GameLevel (design/levels.md#configurations-and-classes).\n private _createLevelInstance(config : LevelConfig) : Level<Registry>\n {\n const context = this._createContext(config);\n\n if(config.class)\n {\n const LevelClass = this._levelClasses.get(config.class);\n if(!LevelClass)\n {\n throw new Error(`Level class '${ config.class }' is not registered.`);\n }\n\n this._log.debug(`Creating level '${ config.name }' using class '${ config.class }'`);\n return new LevelClass(config, context);\n }\n\n this._log.debug(`Creating level '${ config.name }' using GameLevel`);\n return new GameLevel<Registry>(config, context);\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Configuration Registration API\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Registers a level configuration under its own name. Registering over a name already registered warns\n * and replaces it (design/levels.md#configurations-and-classes).\n */\n public registerLevelConfig(config : LevelConfig) : void\n {\n if(this._levelConfigs.has(config.name))\n {\n this._log.warn(`Level config '${ config.name }' is already registered. Overwriting.`);\n }\n\n this._levelConfigs.set(config.name, config);\n this._log.info(`Registered level config: ${ config.name }`);\n }\n\n /**\n * Registers a `Level` subclass under a name, which a configuration's own `class` field then names.\n * Registering over a name already registered warns and replaces it\n * (design/levels.md#configurations-and-classes).\n */\n public registerLevelClass(name : string, levelClass : LevelConstructor<Registry>) : void\n {\n if(this._levelClasses.has(name))\n {\n this._log.warn(`Level class '${ name }' is already registered. Overwriting.`);\n }\n\n this._levelClasses.set(name, levelClass);\n this._log.info(`Registered level class: ${ name }`);\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Property Handler API\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Registers a handler for one node property, called for every node whose metadata carries that property.\n * The map belongs to the manager, so a handler registered once runs for every level it loads afterwards\n * (design/authoring.md#registering-a-handler).\n */\n public registerPropertyHandler(property : string, handler : PropertyHandler) : void\n {\n if(this._propertyHandlers.has(property))\n {\n this._log.warn(`Property handler '${ property }' is already registered. Overwriting.`);\n }\n\n this._propertyHandlers.set(property, handler);\n this._log.debug(`Registered property handler: ${ property }`);\n }\n\n /** Drops the handler registered under one property name. */\n public unregisterPropertyHandler(property : string) : void\n {\n this._propertyHandlers.delete(property);\n }\n\n /** Drops every registered handler. */\n public clearAllPropertyHandlers() : void\n {\n this._propertyHandlers.clear();\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Level Loading API\n //------------------------------------------------------------------------------------------------------------------\n\n /** The level loaded under this name, or nothing when none is loaded under it. */\n public getLevel(name : string) : Level<Registry> | null\n {\n return this._loadedLevels.get(name) || null;\n }\n\n /** The configuration registered under this name, or nothing when none is. */\n public getLevelConfig(name : string) : LevelConfig | null\n {\n return this._levelConfigs.get(name) || null;\n }\n\n /**\n * Loads the level a registered name points at, or the one a configuration carries, registering that\n * configuration as it goes. A level already loaded answers with the instance already loaded, whatever\n * options the call carries (design/levels.md#loading).\n */\n public async loadLevel(\n configOrName : string | LevelConfig,\n options ?: LevelLoadOptions\n ) : Promise<Level<Registry>>\n {\n let config : LevelConfig;\n\n if(typeof configOrName === 'string')\n {\n const registered = this._levelConfigs.get(configOrName);\n if(!registered)\n {\n throw new Error(`Level config '${ configOrName }' is not registered.`);\n }\n\n config = registered;\n }\n else\n {\n config = configOrName;\n this.registerLevelConfig(config);\n }\n\n const existing = this._loadedLevels.get(config.name);\n if(existing)\n {\n this._log.warn(`Level '${ config.name }' is already loaded`);\n return existing;\n }\n\n this._log.debug(`Loading level: ${ config.name }`);\n const level = this._createLevelInstance(config);\n\n // Loading builds the level's own simulation and binds nothing: only activation binds, and a preloaded\n // level stays inert until then (design/simulation.md#which-simulation-the-engine-holds).\n this._loadingLevel = level;\n try\n {\n await level.load(options);\n }\n catch (error)\n {\n // A load that fails part way has already built a scene and possibly spawned into a simulation;\n // neither is reachable through this manager once the level is not stored, so release both here.\n await level.dispose();\n throw error;\n }\n finally\n {\n this._loadingLevel = null;\n }\n\n this._loadedLevels.set(config.name, level);\n\n return level;\n }\n\n /** Binds the engine to a level's own simulation. A level that built none leaves the engine as it is. */\n private _bindSimulation(level : Level<Registry>) : void\n {\n if(this._gameEngine === null || level.$simulation === null)\n {\n return;\n }\n\n this._gameEngine.bindSimulation(level.$simulation);\n }\n\n /**\n * Hands the current level a simulation built to replace its own, and binds the engine to it. A save load\n * restores into a fresh simulation, and the level that owns the scene has to follow it.\n */\n $bindSimulation(simulation : NodeSimulation<Registry>) : void\n {\n if(this._currentLevel !== null)\n {\n this._currentLevel.$adoptSimulation(simulation);\n }\n\n if(this._gameEngine !== null)\n {\n this._gameEngine.bindSimulation(simulation);\n }\n }\n\n /**\n * Makes a level current, loading it first when it is not loaded, and binds the engine to that level's\n * simulation. The outgoing level's `onDeactivate` and the incoming one's `onActivate` are both awaited\n * (design/levels.md#activation-and-unloading).\n */\n public async activateLevel(configOrName : string | LevelConfig) : Promise<Level<Registry>>\n {\n const level = await this.loadLevel(configOrName);\n\n // Re-activating the level already current rebinds the engine and nothing else: firing the hooks\n // would pause and resume a level that never stopped (design/levels.md#activation-and-unloading).\n if(this._currentLevel === level)\n {\n this._bindSimulation(level);\n return level;\n }\n\n await this._deactivate(this._currentLevel);\n\n this._currentLevel = level;\n this._bindSimulation(level);\n\n if(level.onActivate)\n {\n await level.onActivate();\n }\n\n this._log.info(`Activated level: ${ level.name }`);\n return level;\n }\n\n /** A level's own deactivation, for the two places that leave a level: an activation and a transition. */\n private async _deactivate(level : Level<Registry> | null) : Promise<void>\n {\n if(level?.onDeactivate)\n {\n await level.onDeactivate();\n }\n }\n\n /**\n * Disposes a level and forgets it, clearing the current level when it was the one\n * (design/levels.md#activation-and-unloading).\n */\n public async unloadLevel(name : string) : Promise<void>\n {\n const level = this._loadedLevels.get(name);\n if(level)\n {\n // Unbind the engine with it: a disposed level's simulation left on the engine is one the render\n // loop keeps stepping (design/levels.md#activation-and-unloading).\n if(this._currentLevel === level)\n {\n this._currentLevel = null;\n\n if(this._gameEngine !== null)\n {\n this._gameEngine.bindSimulation(null);\n }\n }\n\n await level.dispose();\n this._loadedLevels.delete(name);\n\n this._log.info(`Unloaded level: ${ name }`);\n }\n }\n\n /** Unloads whichever level is current, and warns when none is. */\n public async unloadCurrentLevel() : Promise<void>\n {\n if(this._currentLevel)\n {\n await this.unloadLevel(this._currentLevel.name);\n }\n else\n {\n this._log.warn('No current level to unload');\n }\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Transitions\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Moves the game from the level that is current to the one named, loading the new level before the old\n * one is disposed and publishing the transition's own events as it goes\n * (design/levels.md#transitions).\n */\n public async transition(levelName : string, options ?: TransitionOptions) : Promise<void>\n {\n const oldLevel = this._currentLevel;\n\n // What the engine is put back on when the transition fails: the old level stays current, so it keeps\n // the simulation it owns (design/simulation.md#which-simulation-the-engine-holds).\n const previousSimulation = this._gameEngine?.simulation ?? null;\n\n // Whether this attempt ran the old level's own onDeactivate, so a later restore knows whether to run\n // its onActivate back (design/levels.md#transitions).\n let deactivatedOldLevel = false;\n\n try\n {\n this._bus.emit({\n type: 'level:transition:start',\n senderID: 'levelManager',\n payload: {\n from: oldLevel?.name,\n to: levelName,\n },\n });\n\n this._bus.emit({\n type: 'level:transition:progress',\n senderID: 'levelManager',\n payload: {\n stage: 'loading',\n levelName,\n },\n });\n\n // A transition to the level already current is a restart, and a restore needs a level with none\n // of its own entities: both mean the loaded instance has to go before the load, or loadLevel\n // answers with the scene the game has been playing in (design/levels.md#transitions).\n const loadedTarget = this._loadedLevels.get(levelName) ?? null;\n const rebuilds = loadedTarget !== null && (options?.forRestore === true || loadedTarget === oldLevel);\n const rebuildsOldLevel = rebuilds && loadedTarget === oldLevel;\n\n if(rebuildsOldLevel)\n {\n // The level being left is the one about to be disposed and built again, so this is the last\n // moment its own deactivation has a level to run on.\n await this._deactivate(oldLevel);\n }\n\n if(rebuilds)\n {\n await this.unloadLevel(levelName);\n }\n\n const newLevel = await this.loadLevel(levelName, options);\n\n if(options?.preloadOnly === true)\n {\n return;\n }\n\n if(!rebuildsOldLevel)\n {\n // Past the point where the transition can still be called off: a preload returns above and a\n // failed load throws before here, and both leave the old level current and playing what it\n // was playing, with nothing behind them to resume it (design/levels.md#transitions).\n await this._deactivate(oldLevel);\n deactivatedOldLevel = true;\n }\n\n // A rebuild already disposed the old level, and the level loaded above carries the same name, so\n // unloading by name here would dispose the new one instead.\n if(oldLevel !== null && !rebuildsOldLevel && options?.keepAlive !== true)\n {\n await this.unloadLevel(oldLevel.name);\n }\n\n this._currentLevel = newLevel;\n this._bindSimulation(newLevel);\n\n if(newLevel.onActivate)\n {\n await newLevel.onActivate();\n }\n\n this._bus.emit({\n type: 'level:transition:complete',\n senderID: 'levelManager',\n payload: { levelName },\n });\n }\n catch (error)\n {\n // Nothing was activated, so the level that was current is restored as current, with its own\n // simulation rebound -- but only while that level is still loaded. A restart's reload and a\n // non-keepAlive transition's onActivate both throw after that level was already disposed, and\n // past that point there is no loaded level to put back (design/levels.md#transitions).\n if(oldLevel !== null && oldLevel.isLoaded)\n {\n this._currentLevel = oldLevel;\n\n if(this._gameEngine !== null)\n {\n this._gameEngine.bindSimulation(previousSimulation);\n }\n\n // A keepAlive transition leaves this level deactivated but loaded: resume what its\n // deactivation paused (design/levels.md#transitions).\n if(deactivatedOldLevel && oldLevel.onActivate)\n {\n await oldLevel.onActivate();\n }\n }\n else\n {\n this._currentLevel = null;\n\n if(this._gameEngine !== null)\n {\n this._gameEngine.bindSimulation(null);\n }\n }\n\n this._bus.emit({\n type: 'level:transition:error',\n senderID: 'levelManager',\n payload: {\n from: oldLevel?.name,\n to: levelName,\n error,\n },\n });\n\n throw error;\n }\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Lifecycle\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Unsubscribes the manager from the level events it listens to, disposes every loaded level, and forgets\n * the loaded levels, the registered configurations, and the current one (design/levels.md#teardown).\n */\n async dispose() : Promise<void>\n {\n for(const unsubscribe of this._eventUnsubscribers)\n {\n unsubscribe();\n }\n\n this._eventUnsubscribers = [];\n\n for(const level of this._loadedLevels.values())\n {\n // eslint-disable-next-line no-await-in-loop\n await level.dispose();\n }\n\n this._loadedLevels.clear();\n this._levelConfigs.clear();\n this._currentLevel = null;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Audio Manager\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { AudioBus, IStaticSoundOptions, StaticSound } from '@babylonjs/core';\n\n// Engines\nimport type { AudioEngine } from '../engines/audio.ts';\n\n// Interfaces\nimport type { Disposable } from '../interfaces/lifecycle.ts';\nimport type { LoggerInterface } from '../interfaces/logger.ts';\n\n// Utils\nimport type { LoggingUtility } from '../utils/logger.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\ninterface ChannelState\n{\n bus : AudioBus;\n volume : number;\n muted : boolean;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport class AudioManager implements Disposable\n{\n #audioEngine : AudioEngine;\n #channels = new Map<string, ChannelState>();\n #masterMuted = false;\n #masterVolumeBeforeMute = 1;\n #log : LoggerInterface;\n\n constructor(audioEngine : AudioEngine, logger : LoggingUtility)\n {\n this.#audioEngine = audioEngine;\n this.#log = logger.getLogger('AudioManager');\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Lifecycle\n //------------------------------------------------------------------------------------------------------------------\n\n async initialize(channels : string[]) : Promise<void>\n {\n this.#log.info(`Initializing audio channels: ${ channels.join(', ') }`);\n\n for(const name of channels)\n {\n // eslint-disable-next-line no-await-in-loop\n const bus = await this.#audioEngine.createBus(name);\n this.#channels.set(name, { bus, volume: 1, muted: false });\n }\n\n this.#log.info('Audio channels initialized.');\n }\n\n async dispose() : Promise<void>\n {\n this.#log.info('Tearing down audio manager...');\n\n for(const state of this.#channels.values())\n {\n state.bus.dispose();\n }\n\n this.#channels.clear();\n this.#log.info('Audio manager torn down.');\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Sound Creation\n //------------------------------------------------------------------------------------------------------------------\n\n async createSound(\n name : string,\n url : string,\n channel ?: string,\n options ?: Partial<IStaticSoundOptions>\n ) : Promise<StaticSound>\n {\n let bus : AudioBus | undefined;\n\n if(channel)\n {\n const state = this.#channels.get(channel);\n if(!state)\n {\n throw new Error(\n `AudioManager: no channel named \"${ channel }\" is declared; sound \"${ name }\" cannot be created.`\n );\n }\n\n bus = state.bus;\n }\n\n return this.#audioEngine.createSound(name, url, bus, options);\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Master Volume\n //------------------------------------------------------------------------------------------------------------------\n\n get masterVolume() : number\n {\n if(this.#masterMuted)\n {\n return this.#masterVolumeBeforeMute;\n }\n\n return this.#audioEngine.getMasterVolume();\n }\n\n set masterVolume(volume : number)\n {\n if(!this.#masterMuted)\n {\n this.#audioEngine.setMasterVolume(volume);\n }\n else\n {\n // Store for when we unmute\n this.#masterVolumeBeforeMute = volume;\n }\n }\n\n get masterMuted() : boolean\n {\n return this.#masterMuted;\n }\n\n set masterMuted(muted : boolean)\n {\n if(muted === this.#masterMuted) { return; }\n\n this.#masterMuted = muted;\n\n if(muted)\n {\n this.#masterVolumeBeforeMute = this.#audioEngine.getMasterVolume();\n this.#audioEngine.setMasterVolume(0);\n }\n else\n {\n this.#audioEngine.setMasterVolume(this.#masterVolumeBeforeMute);\n }\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Channel Volume\n //------------------------------------------------------------------------------------------------------------------\n\n setChannelVolume(channel : string, volume : number) : void\n {\n const state = this.#requireChannel(channel);\n\n state.volume = volume;\n\n if(!state.muted)\n {\n this.#audioEngine.setBusVolume(state.bus, volume);\n }\n }\n\n getChannelVolume(channel : string) : number\n {\n return this.#requireChannel(channel).volume;\n }\n\n setChannelMuted(channel : string, muted : boolean) : void\n {\n const state = this.#requireChannel(channel);\n\n if(muted === state.muted) { return; }\n\n state.muted = muted;\n\n if(muted)\n {\n this.#audioEngine.setBusVolume(state.bus, 0);\n }\n else\n {\n this.#audioEngine.setBusVolume(state.bus, state.volume);\n }\n }\n\n isChannelMuted(channel : string) : boolean\n {\n return this.#requireChannel(channel).muted;\n }\n\n get channels() : string[]\n {\n return Array.from(this.#channels.keys());\n }\n\n #requireChannel(channel : string) : ChannelState\n {\n const state = this.#channels.get(channel);\n if(!state)\n {\n throw new Error(`AudioManager: no channel named \"${ channel }\" is declared.`);\n }\n\n return state;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Simulation Save Manager\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { GameSimulationSnapshot, Unsubscribe } from '@skewedaspect/sage-core';\n\nimport type { GameEngine } from '../classes/gameEngine.ts';\nimport type { NodeEntityRegistry, NodeSimulation } from '../entities/nodeSimulation.ts';\nimport type { Disposable } from '../interfaces/lifecycle.ts';\nimport type { LevelLoadOptions } from '../interfaces/level.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nconst saveFormatVersion = 1;\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport interface LevelLoader<Registry extends NodeEntityRegistry = NodeEntityRegistry>\n{\n readonly currentLevel : { name : string } | null;\n transition(levelName : string, options ?: LevelLoadOptions) : Promise<void>;\n $bindSimulation(simulation : NodeSimulation<Registry>) : void;\n}\n\nexport interface SaveEnvelope\n{\n saveFormatVersion : number;\n engineVersion : string;\n level : string;\n timestamp : number;\n custom : Record<string, unknown>;\n snapshot : GameSimulationSnapshot;\n}\n\nexport type BeforeSaveHook = () => Record<string, unknown>;\n\nexport interface LoadResult<Registry extends NodeEntityRegistry = NodeEntityRegistry>\n{\n simulation : NodeSimulation<Registry>;\n envelope : SaveEnvelope;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport class SimulationSaveManager<Registry extends NodeEntityRegistry = NodeEntityRegistry> implements Disposable\n{\n readonly #levelLoader : LevelLoader<Registry>;\n readonly #engineVersion : string;\n readonly #now : () => number;\n\n readonly #beforeSaveHooks : BeforeSaveHook[] = [];\n\n #gameEngine : GameEngine<Registry> | null = null;\n\n constructor(levelLoader : LevelLoader<Registry>, engineVersion : string, now : () => number = Date.now)\n {\n this.#levelLoader = levelLoader;\n this.#engineVersion = engineVersion;\n this.#now = now;\n }\n\n /**\n * Late-bind the GameEngine after construction. Called by createGameEngine() after the GameEngine\n * instance is created; load() needs it to reach the simulation a transition just rebuilt.\n */\n $setGameEngine(gameEngine : GameEngine<Registry>) : void\n {\n this.#gameEngine = gameEngine;\n }\n\n // Any number of hooks, in registration order, each answering with the fields it owns; the answer is what removes\n // the one just registered (design/engine.md#lifecycle-hooks).\n onBeforeSave(hook : BeforeSaveHook) : Unsubscribe\n {\n this.#beforeSaveHooks.push(hook);\n\n return () : void =>\n {\n const index = this.#beforeSaveHooks.indexOf(hook);\n\n if(index >= 0)\n {\n this.#beforeSaveHooks.splice(index, 1);\n }\n };\n }\n\n // Saves the simulation the engine holds, the one a load restores into (design/simulation.md#saved-games).\n save() : SaveEnvelope\n {\n const simulation = this.#engineSimulation();\n const custom : Record<string, unknown> = {};\n\n for(const hook of this.#beforeSaveHooks)\n {\n for(const [ field, value ] of Object.entries(hook()))\n {\n if(Object.hasOwn(custom, field))\n {\n throw new Error(\n `SimulationSaveManager: two onBeforeSave hooks answered with the field \"${ field }\".`\n );\n }\n\n custom[field] = value;\n }\n }\n\n return {\n saveFormatVersion,\n engineVersion: this.#engineVersion,\n level: this.#levelLoader.currentLevel?.name ?? '',\n timestamp: this.#now(),\n custom,\n snapshot: simulation.snapshot(),\n };\n }\n\n async load(envelope : SaveEnvelope) : Promise<LoadResult<Registry>>\n {\n if(envelope.saveFormatVersion !== saveFormatVersion)\n {\n throw new Error(\n `The save format version \"${ envelope.saveFormatVersion }\" is not one this engine knows how to load.`\n );\n }\n\n this.#requireEngine();\n\n await this.#levelLoader.transition(envelope.level, { forRestore: true });\n\n // The transition just rebuilt the level's scene and, with it, a fresh NodeSimulation bound to that\n // scene (GameEngine.rebuildSimulation) -- buildFromSnapshot has to run on that one, not on whatever\n // simulation was current before the transition, or the restored entities carry a disposed scene.\n const freshSimulation = this.#engineSimulation();\n\n const restored = freshSimulation.buildFromSnapshot(envelope.snapshot);\n\n // The level that owns the scene has to follow the restored simulation, or its own spawns and\n // despawns keep going to the one the transition built\n // (design/simulation.md#which-simulation-the-engine-holds).\n this.#levelLoader.$bindSimulation(restored);\n\n return { simulation: restored, envelope };\n }\n\n async dispose() : Promise<void>\n {\n this.#beforeSaveHooks.length = 0;\n }\n\n #requireEngine() : GameEngine<Registry>\n {\n if(this.#gameEngine === null)\n {\n throw new Error('SimulationSaveManager: gameEngine not set. Call $setGameEngine() first.');\n }\n\n return this.#gameEngine;\n }\n\n #engineSimulation() : NodeSimulation<Registry>\n {\n const simulation = this.#requireEngine().simulation;\n\n if(simulation === null)\n {\n throw new Error('SimulationSaveManager: the engine holds no simulation.');\n }\n\n return simulation;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Collider Debug Manager\n//\n// Wraps BabylonJS PhysicsViewer to provide per-node collider debug visualization with color support.\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { type AbstractMesh, Color3, StandardMaterial, type TransformNode } from '@babylonjs/core';\nimport { PhysicsViewer } from '@babylonjs/core/Debug/physicsViewer';\n\nimport type { Disposable } from '../interfaces/lifecycle.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport type ColliderDebugConfig = boolean | string | { color : string } | { nodes : Record<string, string | false> };\n\n//----------------------------------------------------------------------------------------------------------------------\n\ninterface ShownEntry\n{\n debugMesh : AbstractMesh;\n colorMaterial ?: StandardMaterial;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport class ColliderDebugManager implements Disposable\n{\n private _viewer : PhysicsViewer | null = null;\n private _shown = new Map<TransformNode, ShownEntry>();\n private _materials = new Map<string, StandardMaterial>();\n\n //------------------------------------------------------------------------------------------------------------------\n // Public API\n //------------------------------------------------------------------------------------------------------------------\n\n showColliderForNode(node : TransformNode, color ?: string) : void\n {\n if(!node.physicsBody || this._shown.has(node))\n {\n return;\n }\n\n const viewer = this._getViewer(node);\n const debugMesh = viewer.showBody(node.physicsBody);\n\n if(!debugMesh)\n {\n return;\n }\n\n const entry : ShownEntry = { debugMesh };\n\n if(color)\n {\n const material = this._getColorMaterial(color, node);\n debugMesh.material = material;\n entry.colorMaterial = material;\n }\n\n this._shown.set(node, entry);\n }\n\n hideColliderForNode(node : TransformNode) : void\n {\n const entry = this._shown.get(node);\n if(!entry || !this._viewer)\n {\n return;\n }\n\n if(node.physicsBody)\n {\n this._viewer.hideBody(node.physicsBody);\n }\n\n this._shown.delete(node);\n }\n\n hideAll() : void\n {\n for(const [ node ] of this._shown)\n {\n this.hideColliderForNode(node);\n }\n }\n\n isShown(node : TransformNode) : boolean\n {\n return this._shown.has(node);\n }\n\n async dispose() : Promise<void>\n {\n if(this._viewer)\n {\n this.hideAll();\n this._viewer.dispose();\n this._viewer = null;\n }\n\n this._shown.clear();\n\n for(const mat of this._materials.values())\n {\n mat.dispose();\n }\n\n this._materials.clear();\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Private\n //------------------------------------------------------------------------------------------------------------------\n\n private _getViewer(node : TransformNode) : PhysicsViewer\n {\n if(!this._viewer)\n {\n this._viewer = new PhysicsViewer(node.getScene());\n }\n\n return this._viewer;\n }\n\n private _getColorMaterial(color : string, node : TransformNode) : StandardMaterial\n {\n let material = this._materials.get(color);\n if(material)\n {\n return material;\n }\n\n material = new StandardMaterial(`sage-collider-debug-${ color }`, node.getScene());\n material.emissiveColor = Color3.FromHexString(color);\n material.disableLighting = true;\n material.wireframe = true;\n this._materials.set(color, material);\n\n return material;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Graphics Utility\n//----------------------------------------------------------------------------------------------------------------------\n\nimport {\n AbstractEngine,\n Engine,\n type EngineOptions,\n NullEngine,\n type NullEngineOptions,\n WebGPUEngine,\n type WebGPUEngineOptions,\n} from '@babylonjs/core';\n\n// Interfaces\nimport type { BabylonEngineOptions, GameCanvas, RenderEngineOptions } from '../interfaces/game.ts';\nimport type { LoggerInterface } from '../interfaces/logger.ts';\n\n// Utilities\nimport { hasWebGPU } from './capabilities.ts';\nimport { SAGELogger } from './logger.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Whether a WebGPU engine can be built here.\n *\n * `hasWebGPU()` only reports that the browser exposes the API. Headless and software-rendering browsers\n * expose it and then hand back no adapter, and Babylon's WebGPU engine claims its window listeners in the\n * constructor, before `initAsync()` fails -- so an engine built on the weaker answer and then dropped holds\n * them for the life of the page. `dispose()` cannot take them back: it reaches for a GPU device that a\n * failure this early never created.\n */\nasync function _canBuildWebGPUEngine() : Promise<boolean>\n{\n if(!hasWebGPU())\n {\n return false;\n }\n\n return WebGPUEngine.IsSupportedAsync;\n}\n\n/**\n * Builds and initializes a WebGPU engine.\n */\nasync function _buildWebGPUEngine(\n canvas : HTMLCanvasElement | OffscreenCanvas,\n options : WebGPUEngineOptions\n) : Promise<WebGPUEngine>\n{\n const engine = new WebGPUEngine(canvas, options);\n await engine.initAsync();\n return engine;\n}\n\n/**\n * Builds a WebGL engine.\n */\nfunction _buildWebGLEngine(canvas : GameCanvas, options : BabylonEngineOptions) : Engine\n{\n return new Engine(canvas, options.antialias, options.options, options.adaptToDeviceRatio);\n}\n\n/**\n * Builds a Null engine, which is used when no rendering is required.\n */\nfunction _buildNullEngine(options : NullEngineOptions) : NullEngine\n{\n return new NullEngine(options as NullEngineOptions);\n}\n\n//------------------------------------------------------------------------------------------------------------------\n\n/**\n * Creates an appropriate engine based on the provided canvas and options.\n * Passing null for canvas creates a NullEngine. Otherwise auto-detects WebGPU with WebGL fallback,\n * unless a specific engine type is forced via `options.engine`.\n *\n * @param canvas - Pass null to get a NullEngine (headless/testing)\n */\nexport async function createEngine(\n canvas : GameCanvas | null,\n options : RenderEngineOptions,\n largeWorldRendering = false,\n log : LoggerInterface = new SAGELogger('Boot')\n) : Promise<AbstractEngine>\n{\n // Check if we should use a null engine (no canvas provided)\n if(canvas === null)\n {\n log.debug('Using Null Engine');\n return _buildNullEngine(options as NullEngineOptions);\n }\n\n // Inject large world rendering into engine options if requested. We have to build new objects\n // here because BabylonJS declares useLargeWorldRendering as readonly on its option types.\n if(largeWorldRendering)\n {\n if('options' in options)\n {\n const babylonOpts = options as BabylonEngineOptions;\n options = {\n ...babylonOpts,\n options: { ...babylonOpts.options, useLargeWorldRendering: true },\n } as RenderEngineOptions;\n }\n else\n {\n // WebGPUEngineOptions -- useLargeWorldRendering lives at the top level\n options = { ...options, useLargeWorldRendering: true } as RenderEngineOptions;\n }\n }\n\n // Extract the forceEngine option if available\n const forceEngine = options.engine || 'auto';\n\n // Force WebGPU engine if specified and available\n if(forceEngine === 'webgpu')\n {\n if(await _canBuildWebGPUEngine())\n {\n try\n {\n log.debug('Using forced WebGPU engine');\n return await _buildWebGPUEngine(canvas, options);\n }\n catch (error)\n {\n log.error('Forced WebGPU initialization failed:', error);\n throw new Error(\n 'Forced WebGPU failed to initialize. If WebGPU is required, check browser compatibility.',\n { cause: error }\n );\n }\n }\n else\n {\n throw new Error('WebGPU was forced but is not available in this browser.');\n }\n }\n\n // Force WebGL engine if specified\n if(forceEngine === 'webgl')\n {\n log.debug('Using forced WebGL engine');\n return _buildWebGLEngine(canvas, options as BabylonEngineOptions);\n }\n\n // Auto detection (default behavior)\n if(await _canBuildWebGPUEngine())\n {\n try\n {\n log.debug('Using WebGPU');\n\n return await _buildWebGPUEngine(canvas, options);\n }\n catch (error)\n {\n log.warn('WebGPU initialization failed, falling back to WebGL:', error);\n }\n }\n else\n {\n log.warn('WebGPU not supported, falling back to WebGL.');\n }\n\n log.debug('Using WebGL');\n\n return _buildWebGLEngine(canvas, options as EngineOptions);\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n","//----------------------------------------------------------------------------------------------------------------------\n// GameTimer - Pause-aware timer utilities\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * A pollable cooldown handle.\n */\nexport interface CooldownHandle\n{\n readonly ready : boolean;\n reset() : void;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\ninterface DelayEntry\n{\n id : number;\n remaining : number;\n callback : () => void;\n}\n\ninterface IntervalEntry\n{\n id : number;\n periodMs : number;\n elapsed : number;\n callback : () => void;\n}\n\ninterface CooldownEntry\n{\n id : number;\n periodMs : number;\n remaining : number;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Pause-aware game timer providing delay, interval, and cooldown primitives.\n *\n * All times are in game-time milliseconds. When the game is paused, `tick()` is simply not called, so all timers\n * freeze automatically.\n */\nexport class GameTimer\n{\n private _nextId = 0;\n private _delays : DelayEntry[] = [];\n private _intervals : IntervalEntry[] = [];\n private _cooldowns : CooldownEntry[] = [];\n\n //------------------------------------------------------------------------------------------------------------------\n // Public API\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * One-shot timer. Fires the callback after `ms` milliseconds of game time.\n *\n * @returns A cancel function that prevents the callback from firing.\n */\n delay(ms : number, callback : () => void) : () => void\n {\n if(ms < 0)\n {\n throw new RangeError(`delay() requires ms >= 0, got ${ ms }`);\n }\n\n const id = this._nextId++;\n this._delays.push({ id, remaining: ms, callback });\n\n return () =>\n {\n this._delays = this._delays.filter((entry) => entry.id !== id);\n };\n }\n\n /**\n * Repeating timer. Fires the callback every `ms` milliseconds of game time.\n *\n * @returns A cancel function that stops the interval.\n */\n interval(ms : number, callback : () => void) : () => void\n {\n if(ms <= 0)\n {\n throw new RangeError(`interval() requires ms > 0, got ${ ms }`);\n }\n\n const id = this._nextId++;\n this._intervals.push({ id, periodMs: ms, elapsed: 0, callback });\n\n return () =>\n {\n this._intervals = this._intervals.filter((entry) => entry.id !== id);\n };\n }\n\n /**\n * Pollable cooldown. Starts ready; after calling `reset()`, becomes not-ready until `ms` elapses.\n */\n cooldown(ms : number) : CooldownHandle\n {\n const id = this._nextId++;\n const entry : CooldownEntry = { id, periodMs: ms, remaining: 0 };\n this._cooldowns.push(entry);\n\n return {\n get ready() : boolean\n {\n return entry.remaining <= 0;\n },\n reset() : void\n {\n entry.remaining = entry.periodMs;\n },\n };\n }\n\n /**\n * Cancel all active delays and intervals, and ready every cooldown.\n */\n cancelAll() : void\n {\n this._delays = [];\n this._intervals = [];\n\n // A cooldown outlives this call: only its remaining time resets, so one taken before this call still\n // counts down after it.\n for(const entry of this._cooldowns)\n {\n entry.remaining = 0;\n }\n }\n\n /**\n * Advance all timers by `dtMs` milliseconds. Called once per frame by the game loop.\n */\n tick(dtMs : number) : void\n {\n // Process delays\n const firedDelays : number[] = [];\n for(const entry of this._delays)\n {\n entry.remaining -= dtMs;\n if(entry.remaining <= 0)\n {\n entry.callback();\n firedDelays.push(entry.id);\n }\n }\n\n if(firedDelays.length > 0)\n {\n this._delays = this._delays.filter((entry) => !firedDelays.includes(entry.id));\n }\n\n // Process intervals\n for(const entry of this._intervals)\n {\n entry.elapsed += dtMs;\n while(entry.elapsed >= entry.periodMs)\n {\n entry.callback();\n entry.elapsed -= entry.periodMs;\n }\n }\n\n // Process cooldowns\n for(const entry of this._cooldowns)\n {\n if(entry.remaining > 0)\n {\n entry.remaining = Math.max(0, entry.remaining - dtMs);\n }\n }\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Built-in Debug Console Commands\n//----------------------------------------------------------------------------------------------------------------------\n\n/* eslint-disable no-console */\n\nimport type { Vector3 } from '@babylonjs/core';\nimport { NamedBehavior } from '@skewedaspect/sage-core';\nimport type { EventSubscription, GameEntity, Unsubscribe } from '@skewedaspect/sage-core';\n\n// Classes\nimport type { DebugConsole } from '../classes/debugConsole.ts';\nimport type { GameEngine } from '../classes/gameEngine.ts';\n\n// Entities\nimport { NodeEntity } from '../entities/nodeEntity.ts';\nimport type { NodeEntityState } from '../entities/nodeEntity.ts';\nimport type { NodeEntityRegistry, NodeSimulation } from '../entities/nodeSimulation.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nfunction requireSimulation<Registry extends NodeEntityRegistry>(\n ge : GameEngine<Registry>\n) : NodeSimulation<Registry> | undefined\n{\n if(ge.simulation === null)\n {\n console.warn('[sage] No simulation yet -- nothing has given the engine a scene to build one against.');\n return undefined;\n }\n\n return ge.simulation;\n}\n\nfunction resolveEntity<Registry extends NodeEntityRegistry>(\n idOrEntity : string | GameEntity,\n ge : GameEngine<Registry>\n) : GameEntity | undefined\n{\n if(typeof idOrEntity === 'string')\n {\n return requireSimulation(ge)?.getEntity(idOrEntity);\n }\n\n return idOrEntity;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport function registerBuiltins<Registry extends NodeEntityRegistry>(\n dc : DebugConsole,\n ge : GameEngine<Registry>\n) : void\n{\n const gm = ge.managers.gameManager;\n const lm = ge.managers.levelManager;\n\n const activeLoggers : Unsubscribe[] = [];\n\n //------------------------------------------------------------------------------------------------------------------\n // Exposed references\n //------------------------------------------------------------------------------------------------------------------\n\n dc.expose('engine', ge);\n dc.expose('events', ge.bus);\n // The current scene's own engine, since physics belongs to a scene rather than to the game\n // (design/physics.md#the-physics-engine). Null on a scene that never asked for it.\n dc.expose('physics', { get: () => lm.currentLevel?.scene?.getPhysicsEngine() ?? null });\n dc.expose('colliders', ge.debug.colliders);\n\n dc.expose('entities', new Proxy({} as Record<string, unknown>, {\n get(_target : Record<string, unknown>, prop : string | symbol) : unknown\n {\n if(typeof prop === 'symbol')\n {\n return undefined;\n }\n\n const entity = requireSimulation(ge)?.getEntity(prop);\n if(!entity)\n {\n console.warn(`[sage] Entity not found: ${ prop }`);\n }\n\n return entity;\n },\n }));\n\n dc.expose('level', { get: () => lm.currentLevel });\n dc.expose('scene', { get: () => lm.currentLevel?.scene ?? null });\n\n //------------------------------------------------------------------------------------------------------------------\n // Commands\n //------------------------------------------------------------------------------------------------------------------\n\n dc.expose('tp', (idOrEntity : string | GameEntity, x : number, y : number, z : number) =>\n {\n const entity = resolveEntity(idOrEntity, ge);\n if(!entity)\n {\n console.warn(`[sage] Entity not found: ${ idOrEntity }`);\n return;\n }\n\n if(!(entity instanceof NodeEntity))\n {\n console.warn(`[sage] Entity \"${ entity.id }\" has no node`);\n return;\n }\n\n // Written through state, which is the node's own position: a computed field, so the write lands on the\n // node as it is made, for every entity alike (design/debugging.md#the-built-in-commands).\n const spatial = entity.state as NodeEntityState;\n\n spatial.position = { x, y, z };\n\n console.log(`[sage] Teleported \"${ entity.id }\" to (${ x }, ${ y }, ${ z })`);\n });\n\n dc.expose('spawn', (name : string, x : number, y : number, z : number) =>\n {\n const simulation = requireSimulation(ge);\n if(!simulation)\n {\n return undefined;\n }\n\n // A name typed at the console is checked by the registry at run time (design/simulation.md#spawn-and-despawn).\n const entity = simulation.spawnByName(name, { position: { x, y, z } });\n console.log(`[sage] Spawned \"${ name }\" (${ entity.id }) at (${ x }, ${ y }, ${ z })`);\n return entity;\n });\n\n dc.expose('list', (name ?: string, tag ?: string) : GameEntity[] =>\n {\n const simulation = requireSimulation(ge);\n if(!simulation)\n {\n return [];\n }\n\n let entities : readonly GameEntity[] = simulation.entities;\n\n if(name !== undefined)\n {\n entities = entities.filter((ent) => ent.name === name);\n }\n\n if(tag !== undefined)\n {\n entities = entities.filter((ent) => (ent.state as { tags : string[] }).tags.includes(tag));\n }\n\n const rows = entities.map((ent) =>\n {\n let nodeName = '(no node)';\n\n if(ent instanceof NodeEntity)\n {\n nodeName = ent.node.name;\n }\n\n const row : Record<string, unknown> = {\n id: ent.id,\n name: ent.name,\n node: nodeName,\n };\n\n if(ent.hasBehavior(NamedBehavior))\n {\n row.displayName = ent.state.displayName;\n }\n\n return row;\n });\n\n console.table(rows);\n return [ ...entities ];\n });\n\n dc.expose('inspect', (idOrEntity : string | GameEntity) =>\n {\n const entity = resolveEntity(idOrEntity, ge);\n if(!entity)\n {\n console.warn(`[sage] Entity not found: ${ idOrEntity }`);\n return undefined;\n }\n\n let nodeName : string | null = null;\n let position : Vector3 | null = null;\n\n if(entity instanceof NodeEntity)\n {\n nodeName = entity.node.name;\n position = entity.node.position;\n }\n\n const info = {\n id: entity.id,\n state: entity.state,\n node: nodeName,\n position,\n };\n\n console.log('[sage] Entity info:', info);\n return info;\n });\n\n dc.expose('logEvents', (pattern ?: string) =>\n {\n if(pattern === undefined)\n {\n throw new Error('logEvents takes an event type or a pattern, such as \"level:*\".');\n }\n\n // The console takes any string; a pattern outside the map hears nothing.\n const resolvedPattern = pattern as EventSubscription;\n\n const unsub = ge.bus.on(resolvedPattern, (event) =>\n {\n console.log(`[sage] Event: ${ event.type }`, event.payload);\n });\n\n activeLoggers.push(unsub);\n console.log(`[sage] Logging events matching \"${ resolvedPattern }\"`);\n return unsub;\n });\n\n dc.expose('stopLogging', () =>\n {\n for(const unsub of activeLoggers)\n {\n unsub();\n }\n\n console.log(`[sage] Stopped ${ activeLoggers.length } event logger(s)`);\n activeLoggers.length = 0;\n });\n\n dc.expose('pause', () =>\n {\n gm.pause('debug console');\n console.log('[sage] Game paused');\n });\n\n dc.expose('resume', () =>\n {\n gm.resume('debug console');\n console.log('[sage] Game resumed');\n });\n\n dc.expose('slow', (factor : number) =>\n {\n gm.timeScale = factor;\n console.log(`[sage] Time scale set to ${ factor }`);\n });\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Logger Interfaces\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Defines the available logging levels as string literals.\n */\nexport const LogLevels = [\n 'trace',\n 'debug',\n 'info',\n 'warn',\n 'error',\n 'none',\n] as const;\n\n/**\n * Type for LogLevel values\n */\nexport type LogLevel = typeof LogLevels[number];\n\n/**\n * Interface for a logging backend implementation.\n * Backends handle the actual output of log messages.\n */\nexport interface LoggingBackend\n{\n /**\n * Log a message at TRACE level\n *\n * @param category - Typically the class or module name\n */\n trace(category : string, message : string, ...args : unknown[]) : void;\n\n /**\n * Log a message at DEBUG level\n *\n * @param category - Typically the class or module name\n */\n debug(category : string, message : string, ...args : unknown[]) : void;\n\n /**\n * Log a message at INFO level\n *\n * @param category - Typically the class or module name\n */\n info(category : string, message : string, ...args : unknown[]) : void;\n\n /**\n * Log a message at WARN level\n *\n * @param category - Typically the class or module name\n */\n warn(category : string, message : string, ...args : unknown[]) : void;\n\n /**\n * Log a message at ERROR level\n *\n * @param category - Typically the class or module name\n */\n error(category : string, message : string, ...args : unknown[]) : void;\n\n /**\n * Start a timer with the specified label.\n *\n * @param category - Typically the class or module name\n * @param label - Must be unique; used to match with timeEnd()\n */\n time(category : string, label : string) : void;\n\n /**\n * End a timer with the specified label and log the time elapsed.\n *\n * @param category - Typically the class or module name\n * @param label - Must match a previous time() call\n */\n timeEnd(category : string, label : string) : void;\n}\n\n/**\n * Interface for a logger instance.\n * Each instance is typically associated with a specific category/component.\n */\nexport interface LoggerInterface\n{\n /** Log a message at TRACE level */\n trace(message : string, ...args : unknown[]) : void;\n\n /** Log a message at DEBUG level */\n debug(message : string, ...args : unknown[]) : void;\n\n /** Log a message at INFO level */\n info(message : string, ...args : unknown[]) : void;\n\n /** Log a message at WARN level */\n warn(message : string, ...args : unknown[]) : void;\n\n /** Log a message at ERROR level */\n error(message : string, ...args : unknown[]) : void;\n\n /** Start a timer with the given label */\n time(label : string) : void;\n\n /** End a timer and log the elapsed time */\n timeEnd(label : string) : void;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// defineEntity\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type {\n Action, BehaviorClass, ComposedOf, GameEntityType, StateBag, StateFor, ValidTuple,\n} from '@skewedaspect/sage-core';\n\nimport { NodeEntity } from './nodeEntity.ts';\n\nimport type { MeshConfig } from './meshConfig.ts';\nimport type { NodeSimulationContext } from './nodeSimulationContext.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport interface GameEntityDefinition<Behaviors extends readonly BehaviorClass[]>\n{\n type : string;\n tags ?: string[];\n defaultState ?: Partial<StateFor<Behaviors>>;\n behaviors : Behaviors;\n mesh ?: MeshConfig;\n actions ?: readonly Action[];\n}\n\n// The class a definition answers with: NodeEntity's statics, the definition's own behaviors tuple, and instances\n// typed by that tuple, so a subclass reads its state and operations with no declaration of its own. The construct\n// signature is abstract because the constructor is protected: a game extends the class or hands it to the entity\n// registry, and never constructs one directly (design/entities.md#creation-and-restoration).\nexport type GameEntityDefinitionClass<Behaviors extends readonly BehaviorClass[]>\n = Omit<typeof NodeEntity, 'prototype'>\n & {\n readonly behaviors : Behaviors;\n readonly prototype : NodeEntity<Behaviors>;\n }\n & (abstract new (\n id : string,\n context : NodeSimulationContext,\n providedState : object,\n runCreateHooks : boolean,\n name ?: string\n ) => NodeEntity<Behaviors>);\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// A supplied field replaces the definition's own default whole (design/entities.md#state); tags merge as a\n// deduplicated union instead, since a spawn's own extra tags are additions to a definition's baseline, not a\n// replacement for it (design/authoring.md#composition-and-the-entity-registry).\nfunction foldCreateState<Behaviors extends readonly BehaviorClass[]>(\n definition : GameEntityDefinition<Behaviors>,\n supplied : Partial<StateFor<Behaviors>> | undefined\n) : Partial<StateFor<Behaviors>>\n{\n const suppliedState = (supplied ?? {}) as StateBag;\n const definitionTags = definition.tags ?? [];\n const suppliedTags = (suppliedState['tags'] as string[] | undefined) ?? [];\n\n return {\n ...definition.defaultState,\n ...suppliedState,\n tags: [ ...new Set([ ...definitionTags, ...suppliedTags ]) ],\n } as Partial<StateFor<Behaviors>>;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport function defineEntity<Behaviors extends readonly BehaviorClass[]>(\n definition : GameEntityDefinition<Behaviors> & { behaviors : ValidTuple<Behaviors, NodeEntity<Behaviors>> }\n) : GameEntityDefinitionClass<Behaviors>\n{\n class DefinedEntity extends NodeEntity<Behaviors>\n {\n static override readonly behaviors = definition.behaviors;\n static override readonly meshConfig = definition.mesh;\n static override readonly actions = definition.actions;\n\n static override create<Type extends GameEntityType>(\n this : Type & { behaviors : ValidTuple<Type['behaviors'], Type['prototype']> },\n id : string,\n context : NodeSimulationContext,\n state ?: Partial<StateFor<Type['behaviors']>>,\n name ?: string\n ) : ComposedOf<Type>\n {\n const folded = foldCreateState(definition, state as Partial<StateFor<Behaviors>> | undefined);\n\n return super.create(id, context, folded as Partial<StateFor<Type['behaviors']>>, name);\n }\n }\n\n // The class is named after the definition's type, so a stack trace and a debugger read the registry key\n // rather than this local name (design/authoring.md#what-a-definition-is).\n Object.defineProperty(DefinedEntity, 'name', { value: definition.type });\n\n return DefinedEntity as unknown as GameEntityDefinitionClass<Behaviors>;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Standard Mesh Source\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { Color3, MeshBuilder, PBRMaterial, StandardMaterial } from '@babylonjs/core';\n\nimport type { AbstractMesh, AssetContainer, Mesh, Scene } from '@babylonjs/core';\nimport type { GameEntityType } from '@skewedaspect/sage-core';\n\nimport type { ColorConfig } from '../interfaces/level.ts';\nimport type { MeshLoader } from './nodeSimulationContext.ts';\n\nimport type { MeshConfig, MeshMaterialConfig } from './meshConfig.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// What the standard mesh source needs from the asset manager: loading a glb (or a fragment naming one of its\n// meshes), and cloning a fragment's cached source mesh into a fresh, independent one in a scene it names.\nexport interface MeshAssetSource\n{\n load(path : string) : Promise<AssetContainer | AbstractMesh>;\n clone(path : string, into : Scene) : Mesh;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nfunction toColor3(color : ColorConfig) : Color3\n{\n return new Color3(color.r, color.g, color.b);\n}\n\nfunction buildPrimitiveMesh(scene : Scene, source : string, params : Record<string, unknown> | undefined) : Mesh\n{\n const builderName = `Create${ source.charAt(0).toUpperCase() }${ source.slice(1) }`;\n const builders = MeshBuilder as unknown as Record<string, (name : string, options : object, scene : Scene) => Mesh>;\n const builder = builders[builderName];\n\n if(typeof builder !== 'function')\n {\n throw new Error(`No Babylon mesh builder provides the primitive shape \"${ source }\".`);\n }\n\n return builder(source, params ?? {}, scene);\n}\n\nfunction applyMaterial(scene : Scene, mesh : Mesh, config : MeshMaterialConfig) : void\n{\n if(config.type === 'pbr')\n {\n const material = new PBRMaterial(`${ mesh.name }-material`, scene);\n\n if(config.color !== undefined)\n {\n material.albedoColor = toColor3(config.color);\n }\n\n if(config.emissive !== undefined)\n {\n material.emissiveColor = toColor3(config.emissive);\n }\n\n if(config.metallic !== undefined)\n {\n material.metallic = config.metallic;\n }\n\n if(config.roughness !== undefined)\n {\n material.roughness = config.roughness;\n }\n\n mesh.material = material;\n }\n else\n {\n const material = new StandardMaterial(`${ mesh.name }-material`, scene);\n\n if(config.color !== undefined)\n {\n material.diffuseColor = toColor3(config.color);\n }\n\n if(config.emissive !== undefined)\n {\n material.emissiveColor = toColor3(config.emissive);\n }\n\n mesh.material = material;\n }\n}\n\nasync function buildMesh(scene : Scene, config : MeshConfig, assets : MeshAssetSource) : Promise<Mesh>\n{\n let mesh : Mesh;\n\n if(config.source.includes('#'))\n {\n await assets.load(config.source);\n mesh = assets.clone(config.source, scene);\n }\n else\n {\n mesh = buildPrimitiveMesh(scene, config.source, config.params);\n }\n\n mesh.isVisible = config.visible ?? true;\n\n if(config.material !== undefined)\n {\n applyMaterial(scene, mesh, config.material);\n }\n\n return mesh;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport function buildStandardMeshSource(\n entityClasses : Readonly<Record<string, GameEntityType>>,\n assets : MeshAssetSource\n) : MeshLoader\n{\n return async (scene, name) : Promise<AbstractMesh | undefined> =>\n {\n const entityClass = entityClasses[name] as { meshConfig ?: MeshConfig } | undefined;\n const config = entityClass?.meshConfig;\n\n if(config === undefined)\n {\n return undefined;\n }\n\n return buildMesh(scene, config, assets);\n };\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// SoundBehavior\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { Behavior } from '@skewedaspect/sage-core';\n\nimport type { NodeBehaviorHooks, NodeEntity } from '../entities/nodeEntity.ts';\n\nimport type { StaticSound } from '@babylonjs/core';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport interface SoundConfig\n{\n url : string;\n volume ?: number;\n loop ?: boolean;\n spatial ?: boolean;\n maxDistance ?: number;\n channel ?: string;\n}\n\nexport interface SoundState\n{\n sounds : Record<string, SoundConfig>;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport class SoundBehavior extends Behavior<SoundState, object, NodeEntity> implements NodeBehaviorHooks\n{\n static readonly ops = [ 'play', 'stop' ] as const;\n\n readonly defaults : SoundState = { sounds: {} };\n\n readonly #sounds = new Map<string, StaticSound>();\n #destroyed = false;\n\n onMeshLoaded() : void\n {\n const entity = this.entity;\n const audio = entity.audio;\n\n if(audio === undefined)\n {\n return;\n }\n\n for(const [ name, config ] of Object.entries(this.state.sounds))\n {\n audio.createSound(name, config.url, config.channel, {\n volume: config.volume ?? 1,\n loop: config.loop ?? false,\n }).then((sound) =>\n {\n this.#onSoundBuilt(name, config, entity, sound);\n });\n }\n }\n\n onDestroy() : void\n {\n this.#destroyed = true;\n\n for(const sound of this.#sounds.values())\n {\n sound.stop();\n sound.dispose();\n }\n\n this.#sounds.clear();\n }\n\n play(name : string) : void\n {\n this.#find(name)?.play();\n }\n\n stop(name : string) : void\n {\n this.#find(name)?.stop();\n }\n\n // A subsystem that is missing says so; one that is still starting stays quiet (design/audio.md#operations).\n #requireAudio() : void\n {\n if(this.entity.audio === undefined)\n {\n throw new Error(\n `SoundBehavior: entity \"${ this.entity.id }\" carries no AudioManager; the game never named audio `\n + 'channels.'\n );\n }\n }\n\n // A sound whose entity is already gone by the time it finishes building is disposed on arrival rather\n // than kept -- destruction is not undone by a late arrival, unlike a mesh\n // (design/audio.md#pooling-and-destruction).\n #onSoundBuilt(name : string, config : SoundConfig, entity : NodeEntity, sound : StaticSound) : void\n {\n if(this.#destroyed)\n {\n sound.dispose();\n return;\n }\n\n if(config.spatial ?? false)\n {\n sound.spatial.attach(entity.node);\n sound.spatial.maxDistance = config.maxDistance ?? 100;\n }\n\n this.#sounds.set(name, sound);\n\n if(config.loop ?? false)\n {\n sound.play();\n }\n }\n\n #find(name : string) : StaticSound | undefined\n {\n this.#requireAudio();\n\n if(!Object.hasOwn(this.state.sounds, name))\n {\n throw new Error(`SoundBehavior: no sound named \"${ name }\" is declared.`);\n }\n\n return this.#sounds.get(name);\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// HavokPhysicsBodyBehavior\n//\n// Every field except `shape` and `parts` is a getter and a setter over the body once there is one, so state is the\n// body rather than a copy of it (design/physics.md#state-computed-from-the-body).\n//----------------------------------------------------------------------------------------------------------------------\n\nimport {\n Mesh, PhysicsAggregate, PhysicsMotionType, PhysicsPrestepType, PhysicsShapeBox, PhysicsShapeContainer,\n PhysicsShapeCylinder, PhysicsShapeSphere, PhysicsShapeType, Quaternion, Vector3,\n} from '@babylonjs/core';\nimport { Behavior } from '@skewedaspect/sage-core';\n\nimport { computeHierarchyBounds } from '../utils/bounds.ts';\nimport { quaternionView, toQuaternion, toVector3, vectorView } from '../utils/vectors.ts';\n\nimport type {\n HavokPlugin, PhysicsAggregateParameters, PhysicsBody, PhysicsMaterial, PhysicsShape, Scene, TransformNode,\n} from '@babylonjs/core';\nimport type {\n NodeBehaviorHooks, NodeEntity, NodeEntityState, QuaternionState, Vector3State,\n} from '../entities/nodeEntity.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/** What a body's outline can be (design/physics.md#shapes). */\nexport type BodyShape = 'box' | 'sphere' | 'cylinder' | 'convex' | 'mesh' | 'compound';\n\n/** Who decides where a body goes (design/physics.md#static-dynamic-and-kinematic). */\nexport type BodyMotion = 'static' | 'dynamic' | 'kinematic';\n\n/** One child of a compound shape, placed relative to the entity's node. */\nexport interface CompoundPart\n{\n shape : 'box' | 'sphere' | 'cylinder';\n position : Vector3State;\n rotation : QuaternionState;\n\n /** Full size: a box's width, height, depth; a sphere's diameter in x; a cylinder's diameter in x, height in y. */\n extents : Vector3State;\n}\n\nexport interface PhysicsBodyState\n{\n shape : BodyShape;\n\n /** The children of a `compound` shape. Unused by every other shape. */\n parts : CompoundPart[];\n\n motion : BodyMotion;\n\n /** In kilograms. */\n mass : number;\n\n friction : number;\n restitution : number;\n position : Vector3State;\n rotation : QuaternionState;\n\n /** Metres per second. */\n velocity : Vector3State;\n\n /** Radians per second. */\n angularVelocity : Vector3State;\n /** Whether the body is out of the physics world (design/physics.md#a-suspended-body). */\n suspended : boolean;\n}\n\n// What a shape is sized from: the full size of the geometry's box, and where the middle of that box sits in\n// the node's own frame.\ninterface BodyMeasurement\n{\n extents : Vector3;\n center : Vector3;\n}\n\ntype ShapeParameters = Omit<PhysicsAggregateParameters, 'mass' | 'friction' | 'restitution'>;\n\n//----------------------------------------------------------------------------------------------------------------------\n// Shapes and motion\n//----------------------------------------------------------------------------------------------------------------------\n\nfunction shapeTypeFor(shape : BodyShape) : PhysicsShapeType\n{\n if(shape === 'sphere')\n {\n return PhysicsShapeType.SPHERE;\n }\n\n if(shape === 'cylinder')\n {\n return PhysicsShapeType.CYLINDER;\n }\n\n if(shape === 'convex')\n {\n return PhysicsShapeType.CONVEX_HULL;\n }\n\n if(shape === 'mesh')\n {\n return PhysicsShapeType.MESH;\n }\n\n return PhysicsShapeType.BOX;\n}\n\nfunction shapeParametersFor(shape : BodyShape, measurement : BodyMeasurement) : ShapeParameters\n{\n const { extents, center } = measurement;\n\n if(shape === 'sphere')\n {\n // The largest half-extent, so the sphere contains the geometry rather than cutting through its\n // corners (design/physics.md#shapes).\n return { radius: Math.max(extents.x, extents.y, extents.z) / 2, center };\n }\n\n if(shape === 'cylinder')\n {\n return {\n radius: extents.x / 2,\n pointA: new Vector3(center.x, center.y - (extents.y / 2), center.z),\n pointB: new Vector3(center.x, center.y + (extents.y / 2), center.z),\n };\n }\n\n return { extents, center };\n}\n\nfunction buildPartShape(part : CompoundPart, scene : Scene) : PhysicsShape\n{\n const { extents } = part;\n\n if(part.shape === 'sphere')\n {\n return new PhysicsShapeSphere(Vector3.Zero(), extents.x / 2, scene);\n }\n\n if(part.shape === 'cylinder')\n {\n const pointA = new Vector3(0, -extents.y / 2, 0);\n const pointB = new Vector3(0, extents.y / 2, 0);\n\n return new PhysicsShapeCylinder(pointA, pointB, extents.x / 2, scene);\n }\n\n return new PhysicsShapeBox(Vector3.Zero(), Quaternion.Identity(), toVector3(extents), scene);\n}\n\n// Babylon spells the kinematic motion type `ANIMATED`; the design's word is kinematic on both sides, and\n// this is the line that maps one to the other (design/physics.md#static-dynamic-and-kinematic).\nfunction motionTypeFor(motion : BodyMotion) : PhysicsMotionType\n{\n if(motion === 'static')\n {\n return PhysicsMotionType.STATIC;\n }\n\n if(motion === 'kinematic')\n {\n return PhysicsMotionType.ANIMATED;\n }\n\n return PhysicsMotionType.DYNAMIC;\n}\n\nfunction motionFor(motionType : PhysicsMotionType) : BodyMotion\n{\n if(motionType === PhysicsMotionType.STATIC)\n {\n return 'static';\n }\n\n if(motionType === PhysicsMotionType.ANIMATED)\n {\n return 'kinematic';\n }\n\n return 'dynamic';\n}\n\n// A kinematic body is told where it should be and reaches it during the physics step, carrying the velocity\n// that motion implies; every other body is placed outright (design/physics.md#static-dynamic-and-kinematic).\nfunction prestepFor(motion : BodyMotion) : PhysicsPrestepType\n{\n if(motion === 'kinematic')\n {\n return PhysicsPrestepType.ACTION;\n }\n\n return PhysicsPrestepType.TELEPORT;\n}\n\nfunction hasGeometry(node : TransformNode) : boolean\n{\n for(const child of node.getChildMeshes(false))\n {\n if(child.getTotalVertices() > 0)\n {\n return true;\n }\n }\n\n return false;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport class HavokPhysicsBodyBehavior\n extends Behavior<PhysicsBodyState, object, NodeEntity>\n implements NodeBehaviorHooks\n{\n static readonly ops = [ 'applyImpulse' ] as const;\n\n // `position` and `rotation` are the fields NodeEntity already declares, shared rather than declared\n // beside them, so the defaults here are NodeEntity's own (design/physics.md#state).\n readonly defaults : PhysicsBodyState = {\n shape: 'box',\n parts: [],\n motion: 'dynamic',\n mass: 1,\n friction: 0.5,\n restitution: 0,\n position: { x: 0, y: 0, z: 0 },\n rotation: { x: 0, y: 0, z: 0, w: 1 }, // eslint-disable-line id-length\n velocity: { x: 0, y: 0, z: 0 },\n angularVelocity: { x: 0, y: 0, z: 0 },\n suspended: false,\n };\n\n #aggregate : PhysicsAggregate | undefined;\n #plugin : HavokPlugin | undefined;\n #suspended = false;\n\n // What the body resumes as, and the collision masks it gets back (design/physics.md#a-suspended-body).\n #resumeMotion : PhysicsMotionType = PhysicsMotionType.DYNAMIC;\n #savedMasks = new Map<PhysicsShape, { membership : number; collide : number }>();\n\n // A shape handed to an aggregate ready-made is not disposed with it, so a compound's container and its\n // children are kept here to be disposed by hand (design/physics.md#lifecycle).\n #builtShapes : PhysicsShape[] = [];\n\n //------------------------------------------------------------------------------------------------------------------\n // Hooks\n //------------------------------------------------------------------------------------------------------------------\n\n onMeshLoaded() : void\n {\n const entity = this.entity;\n const scene = entity.node.getScene();\n const engine = scene.getPhysicsEngine();\n\n if(engine === null)\n {\n throw new Error(\n `HavokPhysicsBodyBehavior: entity \"${ entity.id }\" wants a physics body, and the scene it was `\n + 'spawned into has no physics engine.'\n );\n }\n\n // What the level authored wins: it was there first, and the level's geometry is built against it\n // (design/physics.md#bodies-no-entity-owns).\n if(entity.node.physicsBody)\n {\n throw new Error(\n `HavokPhysicsBodyBehavior: entity \"${ entity.id }\" adopted a node that already carries a physics `\n + 'body, built for it by the level. Drop the node\\'s `collider` property to let the behavior own it.'\n );\n }\n\n // A body's transform is its node's own, and a parented node's own transform is not where the node is in\n // the world (design/physics.md#where-a-body-goes).\n if((entity.state as NodeEntityState).parentID !== undefined)\n {\n throw new Error(\n `HavokPhysicsBodyBehavior: entity \"${ entity.id }\" carries a parent ID, and a body is never built for `\n + 'an entity parented to another.'\n );\n }\n\n this.#plugin = engine.getPhysicsPlugin() as HavokPlugin;\n this.#aggregate = this.#buildAggregate(entity, scene);\n\n this.#seedBody();\n\n // Built from restored state, or from a spawn that says so, the body starts out of the physics world\n // (design/physics.md#a-suspended-body).\n if(this.state.suspended === true)\n {\n this.#suspend();\n }\n\n this.#installAccessors();\n }\n\n onDestroy() : void\n {\n // Every field computed from the body was asked for the last time before this hook ran, by the entity's\n // own destroy() (design/physics.md#lifecycle).\n this.#aggregate?.dispose();\n this.#aggregate = undefined;\n this.#plugin = undefined;\n\n for(const shape of this.#builtShapes)\n {\n shape.dispose();\n }\n\n this.#builtShapes = [];\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Operations\n //------------------------------------------------------------------------------------------------------------------\n\n // A shove is a verb, answered immediately, rather than a fact one entity records in another's state\n // (design/physics.md#state). A body that has not been built yet has nothing to shove\n // (design/physics.md#a-missing-subsystem-throws).\n applyImpulse(impulse : Vector3State, location ?: Vector3State) : void\n {\n if(this.#aggregate === undefined)\n {\n return;\n }\n\n const body = this.#aggregate.body;\n\n // Through the middle it only moves the body; against a corner it turns it too\n // (design/physics.md#state).\n let where = body.getObjectCenterWorld();\n if(location !== undefined)\n {\n where = toVector3(location);\n }\n\n body.applyImpulse(toVector3(impulse), where);\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Building the body\n //------------------------------------------------------------------------------------------------------------------\n\n // The body goes on the entity's node, never on its mesh (design/physics.md#where-a-body-goes).\n #buildAggregate(entity : NodeEntity, scene : Scene) : PhysicsAggregate\n {\n const node = entity.node;\n const shape = this.state.shape;\n\n const mass = this.state.mass;\n\n if(shape === 'compound')\n {\n return new PhysicsAggregate(node, this.#buildCompound(scene), { mass }, scene);\n }\n\n if(shape === 'convex' || shape === 'mesh')\n {\n const mesh = entity.mesh;\n\n if(!(mesh instanceof Mesh) || mesh.getTotalVertices() === 0)\n {\n throw new Error(\n `HavokPhysicsBodyBehavior: entity \"${ entity.id }\" asked for a \"${ shape }\" body, which reads `\n + 'vertices, and it has no mesh geometry to read.'\n );\n }\n\n return new PhysicsAggregate(node, shapeTypeFor(shape), { mass, mesh }, scene);\n }\n\n const parameters = shapeParametersFor(shape, this.#measure(entity));\n\n return new PhysicsAggregate(node, shapeTypeFor(shape), { mass, ...parameters }, scene);\n }\n\n #measure(entity : NodeEntity) : BodyMeasurement\n {\n const mesh = entity.mesh;\n\n // The box's own middle, in the node's frame, so geometry that does not sit centred on the mesh's origin\n // gets a shape over it rather than beside it (design/physics.md#shapes).\n if(mesh !== undefined && mesh.getTotalVertices() > 0)\n {\n const box = mesh.getBoundingInfo().boundingBox;\n\n return { extents: box.extendSize.scale(2), center: box.center.clone() };\n }\n\n // Babylon reads a bare TransformNode's own bounding info as a unit cube, so a node with no mesh is\n // measured across everything hanging under it, offset and all\n // (design/physics.md#sizing-from-a-bare-transform-node).\n if(!hasGeometry(entity.node))\n {\n throw new Error(\n `HavokPhysicsBodyBehavior: entity \"${ entity.id }\" has neither a mesh nor anything under its node `\n + 'to size a body from.'\n );\n }\n\n return computeHierarchyBounds(entity.node);\n }\n\n // One Havok shape per part, each placed in the container at its part's offset and rotation, so the body\n // keeps the concave outline the parts describe rather than the hull around them\n // (design/physics.md#shapes).\n #buildCompound(scene : Scene) : PhysicsShapeContainer\n {\n const container = new PhysicsShapeContainer(scene);\n\n for(const part of this.state.parts)\n {\n const child = buildPartShape(part, scene);\n\n container.addChild(child, toVector3(part.position), toQuaternion(part.rotation));\n this.#builtShapes.push(child);\n }\n\n this.#builtShapes.push(container);\n\n return container;\n }\n\n // The aggregate picks a motion type from the mass it was handed; the design decides motion from its own\n // field instead, so what the body carries is written here from state, mass aside -- that one the\n // aggregate took (design/physics.md#static-dynamic-and-kinematic).\n #seedBody() : void\n {\n const body = this.#body();\n\n body.setMotionType(motionTypeFor(this.state.motion));\n body.setLinearVelocity(toVector3(this.state.velocity));\n body.setAngularVelocity(toVector3(this.state.angularVelocity));\n\n this.#writeMaterial({ friction: this.state.friction, restitution: this.state.restitution });\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // The state the body backs\n //------------------------------------------------------------------------------------------------------------------\n\n // Every field but `shape` and `parts` becomes a view onto the body: reading asks it and writing tells it\n // (design/physics.md#state-computed-from-the-body, design/entities.md#computed-state).\n #installAccessors() : void\n {\n // The four transform fields each answer with one view of the body's own, so a write to a component\n // reaches the body through the same write the whole field takes\n // (design/physics.md#state-computed-from-the-body).\n const writePosition = (value : Vector3State) : void => { this.#writeTransform(value, this.state.rotation); };\n const writeRotation = (value : QuaternionState) : void => { this.#writeTransform(this.state.position, value); };\n const writeVelocity = (value : Vector3State) : void => { this.#body().setLinearVelocity(toVector3(value)); };\n\n const writeAngularVelocity = (value : Vector3State) : void =>\n {\n this.#body().setAngularVelocity(toVector3(value));\n };\n\n const position = vectorView(() => this.#body().transformNode.position, writePosition);\n const rotation = quaternionView(() => this.#rotation(), writeRotation);\n const velocity = vectorView(() => this.#body().getLinearVelocity(), writeVelocity);\n const angularVelocity = vectorView(() => this.#body().getAngularVelocity(), writeAngularVelocity);\n\n this.computed('position', { get: () => position, set: writePosition });\n\n this.computed('rotation', { get: () => rotation, set: writeRotation });\n\n this.computed('velocity', { get: () => velocity, set: writeVelocity });\n\n this.computed('angularVelocity', { get: () => angularVelocity, set: writeAngularVelocity });\n\n // While suspended the body is held static, so the motion a game sees and writes is what it resumes as\n // (design/physics.md#a-suspended-body).\n this.computed('motion', {\n get: () =>\n {\n if(this.#suspended)\n {\n return motionFor(this.#resumeMotion);\n }\n\n return motionFor(this.#body().getMotionType());\n },\n set: (value) =>\n {\n if(this.#suspended)\n {\n this.#resumeMotion = motionTypeFor(value);\n return;\n }\n\n this.#body().setMotionType(motionTypeFor(value));\n },\n });\n\n this.computed('suspended', {\n get: () => this.#suspended,\n set: (value) => { this.#writeSuspended(value); },\n });\n\n this.computed('mass', {\n get: () => this.#body().getMassProperties().mass ?? 0,\n set: (value) => { this.#body().setMassProperties({ mass: value }); },\n });\n\n this.computed('friction', {\n get: () => this.#material().friction ?? 0,\n set: (value) => { this.#writeMaterial({ friction: value, restitution: this.state.restitution }); },\n });\n\n this.computed('restitution', {\n get: () => this.#material().restitution ?? 0,\n set: (value) => { this.#writeMaterial({ friction: this.state.friction, restitution: value }); },\n });\n }\n\n #writeTransform(position : Vector3State, rotation : QuaternionState) : void\n {\n const body = this.#body();\n const node = body.transformNode;\n\n node.position = toVector3(position);\n node.rotationQuaternion = toQuaternion(rotation);\n\n // A suspended body is not told where its node went; it catches up when it is put back\n // (design/physics.md#a-suspended-body).\n if(this.#suspended)\n {\n return;\n }\n\n // Havok reads a body's transform from its node during a prestep, and a body's default is not to\n // prestep at all, so the write is carried over by hand and the default put back.\n body.setPrestepType(prestepFor(motionFor(body.getMotionType())));\n this.#havok().setPhysicsBodyTransformation(body, node);\n body.setPrestepType(PhysicsPrestepType.DISABLED);\n }\n\n #writeSuspended(value : boolean) : void\n {\n if(value === this.#suspended)\n {\n return;\n }\n\n if(value)\n {\n this.#suspend();\n return;\n }\n\n this.#resume();\n }\n\n // Out of the physics world: held static, so nothing integrates it and the plugin stops writing it into the\n // node, and masked out of every collision and query (design/physics.md#a-suspended-body).\n #suspend() : void\n {\n const body = this.#body();\n\n this.#resumeMotion = body.getMotionType();\n body.setMotionType(PhysicsMotionType.STATIC);\n\n for(const shape of this.#shapes())\n {\n this.#savedMasks.set(shape, { membership: shape.filterMembershipMask, collide: shape.filterCollideMask });\n shape.filterMembershipMask = 0;\n shape.filterCollideMask = 0;\n }\n\n this.#suspended = true;\n }\n\n // Back where the node now stands, as the motion it had or was given meanwhile, carrying no velocity\n // (design/physics.md#a-suspended-body).\n #resume() : void\n {\n const body = this.#body();\n\n for(const shape of this.#shapes())\n {\n const saved = this.#savedMasks.get(shape);\n\n if(saved !== undefined)\n {\n shape.filterMembershipMask = saved.membership;\n shape.filterCollideMask = saved.collide;\n }\n }\n\n this.#savedMasks.clear();\n this.#suspended = false;\n\n body.setMotionType(this.#resumeMotion);\n body.setPrestepType(PhysicsPrestepType.TELEPORT);\n this.#havok().setPhysicsBodyTransformation(body, body.transformNode);\n body.setPrestepType(PhysicsPrestepType.DISABLED);\n body.setLinearVelocity(Vector3.Zero());\n body.setAngularVelocity(Vector3.Zero());\n }\n\n // The aggregate's own shape and, for a compound, every part under it.\n #shapes() : PhysicsShape[]\n {\n const shapes : PhysicsShape[] = [ ...this.#builtShapes ];\n const own = this.#body().shape;\n\n if(own !== null && !shapes.includes(own))\n {\n shapes.push(own);\n }\n\n return shapes;\n }\n\n // A compound's children are what Havok collides against, so they carry the material the container does.\n #writeMaterial(material : PhysicsMaterial) : void\n {\n const shape = this.#body().shape;\n\n if(shape !== null)\n {\n shape.material = material;\n }\n\n for(const child of this.#builtShapes)\n {\n child.material = material;\n }\n }\n\n #rotation() : Quaternion\n {\n const rotation = this.#body().transformNode.rotationQuaternion;\n\n if(rotation === null)\n {\n return Quaternion.Identity();\n }\n\n return rotation;\n }\n\n #material() : PhysicsMaterial\n {\n const shape = this.#body().shape;\n\n if(shape === null)\n {\n return {};\n }\n\n return shape.material;\n }\n\n #body() : PhysicsBody\n {\n if(this.#aggregate === undefined)\n {\n throw new Error(\n `HavokPhysicsBodyBehavior: entity \"${ this.entity.id }\" has no physics body to answer for.`\n );\n }\n\n return this.#aggregate.body;\n }\n\n #havok() : HavokPlugin\n {\n if(this.#plugin === undefined)\n {\n throw new Error(\n `HavokPhysicsBodyBehavior: entity \"${ this.entity.id }\" has no physics plugin to answer for.`\n );\n }\n\n return this.#plugin;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// CharacterControllerBehavior\n//\n// The capsule is Babylon's PhysicsCharacterController, which works in capsule centres; `position` is the character's\n// foot, and the conversion between the two lives here (design/character.md#state).\n//----------------------------------------------------------------------------------------------------------------------\n\nimport {\n CharacterSupportedState, PhysicsCharacterController, PhysicsShapeCapsule, PhysicsShapeSphere, ProximityCastResult,\n Quaternion, ShapeCastResult, Vector3,\n} from '@babylonjs/core';\nimport { Behavior } from '@skewedaspect/sage-core';\n\nimport { defaultGravity } from '../utils/physics.ts';\nimport { toVec3Object } from '../utils/vectors.ts';\n\nimport type { CharacterSurfaceInfo, HavokPlugin, PhysicsBody, Scene } from '@babylonjs/core';\nimport type { NodeBehaviorHooks, NodeEntity, Vector3State } from '../entities/nodeEntity.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/** A capsule height and a speed under a name (design/character.md#stances). */\nexport interface StanceConfig\n{\n height : number;\n radius : number;\n\n /** Metres per second. */\n speed : number;\n}\n\nexport interface CharacterControllerState\n{\n /** How hard a game is asking to move right, -1 to 1. */\n moveX : number;\n\n /** How hard a game is asking to move forward, -1 to 1. */\n moveZ : number;\n\n wantsJump : boolean;\n wantsSprint : boolean;\n\n /** The stance a game is asking for, by name. */\n wantsStance : string;\n\n stances : Record<string, StanceConfig>;\n\n /** Take the tallest stance's capsule from the mesh instead. */\n measureCapsule : boolean;\n\n /** What sprinting multiplies the current stance's speed by. */\n sprintScale : number;\n\n /** Upward metres per second a jump starts with. */\n jumpSpeed : number;\n\n /** How long a jump request survives, in fixed steps. */\n jumpBufferSteps : number;\n\n /** How long after leaving the ground a jump is still allowed, in fixed steps. */\n coyoteSteps : number;\n\n /** Metres per second. */\n velocity : Vector3State;\n\n /** The character's foot, not the middle of its capsule (design/character.md#state). */\n position : Vector3State;\n\n /** True only when the last step's support query answered `SUPPORTED`. */\n grounded : boolean;\n\n /** The stance the character is actually in, which names an entry in `stances`. */\n stance : string;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nconst DOWN = new Vector3(0, -1, 0);\n\n// What `stance` reads before a capsule exists to answer for it. The stance the capsule is actually built in\n// is the tallest one (design/character.md#stances).\nconst FIRST_STANCE = 'standing';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nfunction stanceNamesByHeight(stances : Record<string, StanceConfig>) : string[]\n{\n const names = Object.keys(stances);\n\n names.sort((left, right) => stances[right].height - stances[left].height);\n\n return names;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport class CharacterControllerBehavior\n extends Behavior<CharacterControllerState, object, NodeEntity>\n implements NodeBehaviorHooks\n{\n readonly defaults : CharacterControllerState = {\n moveX: 0,\n moveZ: 0,\n wantsJump: false,\n wantsSprint: false,\n wantsStance: 'standing',\n stances: {\n standing: { height: 1.8, radius: 0.3, speed: 3 },\n crouching: { height: 0.9, radius: 0.3, speed: 1.5 },\n },\n measureCapsule: false,\n sprintScale: 2,\n jumpSpeed: 5,\n jumpBufferSteps: 6,\n coyoteSteps: 6,\n velocity: { x: 0, y: 0, z: 0 },\n position: { x: 0, y: 0, z: 0 },\n grounded: false,\n stance: FIRST_STANCE,\n };\n\n #controller : PhysicsCharacterController | undefined;\n #plugin : HavokPlugin | undefined;\n\n // The controller's own body. `shapeProximity` reports a shape it starts inside and has to be told to skip\n // this one (design/character.md#the-capsule); a shape cast never reports the shape it starts inside, so\n // the ground cast filters this one out of what it answers with instead of asking Havok to skip it\n // (design/character.md#standing-on-something-that-moves).\n #capsuleBody : PhysicsBody | undefined;\n\n #probe : PhysicsShapeSphere | undefined;\n #probeRadius = 0;\n\n #stanceShapes = new Map<string, { shape : PhysicsShapeCapsule; height : number; radius : number }>();\n\n #gravity = defaultGravity();\n\n // Measuring happens when a character is created and not when one is restored (design/character.md#the-capsule),\n // and onCreate is the one hook restore does not run (design/behaviors.md#hooks).\n #created = false;\n\n #stance = FIRST_STANCE;\n #grounded = false;\n\n // What the character was standing on when the last step ended, which a join between two surfaces is decided by\n // (design/character.md#standing-on-something-that-moves).\n #ground : PhysicsBody | undefined;\n\n // What was added for the surface last step, so the character's own velocity can be read back out of what the\n // sweep actually achieved.\n #carried = Vector3.Zero();\n\n // What the live capsule was last built or resized to, which is what a write to the current entry is\n // compared against.\n #builtHeight = 0;\n #builtRadius = 0;\n\n #jumpBuffer = 0;\n #stepsSinceSupported = Number.MAX_SAFE_INTEGER;\n\n // A jump is still reported supported on the step after it starts, because the capsule has not cleared the\n // surface yet. Holding the rise across that step is what keeps the jump from being cancelled by its own floor.\n #rising = false;\n\n //------------------------------------------------------------------------------------------------------------------\n // Hooks\n //------------------------------------------------------------------------------------------------------------------\n\n onCreate() : void\n {\n this.#created = true;\n }\n\n onMeshLoaded() : void\n {\n const entity = this.entity;\n const scene = entity.node.getScene();\n const engine = scene.getPhysicsEngine();\n\n if(engine === null)\n {\n throw new Error(\n `CharacterControllerBehavior: entity \"${ entity.id }\" wants a character controller, and the scene `\n + 'it was spawned into has no physics engine.'\n );\n }\n\n if(this.#created && this.state.measureCapsule)\n {\n this.#measureCapsule(entity);\n }\n\n this.#plugin = engine.getPhysicsPlugin() as HavokPlugin;\n this.#gravity = engine.gravity.clone();\n this.#stance = this.#tallest();\n\n this.#checkStance(this.#stance);\n\n this.#buildController(entity, scene);\n this.#installAnswers();\n }\n\n onUpdate(duration : number) : void\n {\n const controller = this.#controller;\n\n if(controller === undefined)\n {\n return;\n }\n\n const surface = controller.checkSupport(duration, DOWN);\n\n this.#grounded = surface.supportedState === CharacterSupportedState.SUPPORTED;\n\n if(this.#grounded)\n {\n this.#stepsSinceSupported = 0;\n }\n else\n {\n this.#stepsSinceSupported += 1;\n }\n\n // The stance is resolved before the ground is asked about, since the probe the ground cast sweeps is\n // sized to the current stance's radius (design/character.md#the-step).\n this.#resolveStance();\n this.#ground = this.#findGround();\n\n if(this.#ownVelocity().y <= 0)\n {\n this.#rising = false;\n }\n\n const velocity = this.desiredVelocity(duration, surface);\n\n // Overriding desiredVelocity is how a game adds air control or a dash\n // (design/character.md#what-a-game-overrides); none of that implies giving up the jump, so the jump,\n // its buffer and its coyote window stay here.\n if(this.#takesAJump(surface))\n {\n this.#rising = true;\n velocity.y = this.state.jumpSpeed;\n }\n\n this.#carried = this.#surfaceVelocity();\n velocity.addInPlace(this.#carried);\n\n controller.setVelocity(velocity);\n controller.integrate(duration, surface, this.#gravity);\n\n // Written as a world position, since that is the space the capsule was swept in; the node under a parent\n // keeps its own transform in that parent's space, which is what `position` reads\n // (design/character.md#the-step).\n this.entity.node.setAbsolutePosition(this.#footOf(controller.getPosition()));\n this.state.velocity = toVec3Object(controller.getVelocity());\n }\n\n onDestroy() : void\n {\n // Disposing the controller takes its body and the node it built with it (design/character.md#lifecycle).\n this.#controller?.dispose();\n this.#controller = undefined;\n this.#capsuleBody = undefined;\n\n this.#probe?.dispose();\n this.#probe = undefined;\n\n for(const held of this.#stanceShapes.values())\n {\n held.shape.dispose();\n }\n\n this.#stanceShapes.clear();\n\n this.#plugin = undefined;\n this.#ground = undefined;\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // What a game overrides\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * The velocity the character wants this step, in world space, before the surface it is standing on is added\n * (design/character.md#what-a-game-overrides). The jump is not this method's: an override answers for\n * intent, and the behavior takes the jump around it.\n */\n protected desiredVelocity(duration : number, surface : CharacterSurfaceInfo) : Vector3\n {\n const supported = surface.supportedState === CharacterSupportedState.SUPPORTED;\n const velocity = this.#groundVelocityWanted();\n\n if(supported && !this.#rising)\n {\n velocity.y = 0;\n\n return velocity;\n }\n\n velocity.y = this.#ownVelocity().y + (this.#gravity.y * duration);\n\n return velocity;\n }\n\n /**\n * Whether that stance's capsule fits where the character is standing\n * (design/character.md#what-a-game-overrides).\n */\n protected stanceFits(stance : string) : boolean\n {\n const wanted = this.state.stances[stance];\n\n if(wanted === undefined)\n {\n return false;\n }\n\n // A sweep cannot answer this: a wall beside a character asking to widen already overlaps where the\n // sweep would begin, and a sweep reports what it runs into rather than what it starts inside.\n return !this.#capsuleOverlaps(stance, wanted);\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Building the capsule\n //------------------------------------------------------------------------------------------------------------------\n\n #isACapsule(config : StanceConfig | undefined) : boolean\n {\n if(config === undefined)\n {\n return false;\n }\n\n return config.height >= config.radius * 2;\n }\n\n // A capsule is two half-spheres around a segment, and a stance with no segment left is not one. Quietly\n // building the shortest capsule that radius allows is a shape nobody chose (design/character.md#state).\n // An entry nobody enters has shaped nothing, so this is asked where a stance is taken and nowhere else.\n #checkStance(stance : string) : void\n {\n const config = this.state.stances[stance];\n\n if(config === undefined || this.#isACapsule(config))\n {\n return;\n }\n\n throw new Error(\n `CharacterControllerBehavior: entity \"${ this.entity.id }\" declares the stance \"${ stance }\" at a `\n + `height of ${ config.height } and a radius of ${ config.radius }, which is shorter than twice its `\n + 'radius and is not a capsule.'\n );\n }\n\n #buildController(entity : NodeEntity, scene : Scene) : void\n {\n const height = this.#currentHeight();\n const foot = this.#spawnFoot(entity);\n const centre = new Vector3(foot.x, foot.y + (height / 2), foot.z);\n\n // The controller builds a transform node of its own and an ANIMATED body on it\n // (design/character.md#character). It is the node the scene did not have a moment ago, and that body is\n // what a query cast from inside the capsule has to be told to ignore.\n const before = new Set(scene.transformNodes);\n\n this.#controller = new PhysicsCharacterController(\n centre,\n { capsuleHeight: height, capsuleRadius: this.#currentRadius() },\n scene\n );\n\n this.#builtHeight = height;\n this.#builtRadius = this.#currentRadius();\n\n for(const node of scene.transformNodes)\n {\n if(!before.has(node) && node.physicsBody !== null)\n {\n this.#capsuleBody = node.physicsBody;\n }\n }\n }\n\n // The controller sweeps in world space, and a node may sit under a parent, so where it stands in the scene is\n // its world position rather than its own (design/character.md#the-capsule).\n #spawnFoot(entity : NodeEntity) : Vector3\n {\n return entity.node.getAbsolutePosition().clone();\n }\n\n #measureCapsule(entity : NodeEntity) : void\n {\n const mesh = entity.mesh;\n\n if(mesh === undefined || mesh.getTotalVertices() === 0)\n {\n throw new Error(\n `CharacterControllerBehavior: entity \"${ entity.id }\" asked to measure its capsule from a mesh, `\n + 'and has nothing to measure.'\n );\n }\n\n const half = mesh.getBoundingInfo().boundingBox.extendSize;\n\n const tallest = this.#stanceConfig(this.#tallest());\n\n tallest.height = half.y * 2;\n\n // The larger of x and z, because a character is rarely as deep as it is wide (design/character.md#the-capsule).\n tallest.radius = Math.max(half.x, half.z);\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // The step\n //------------------------------------------------------------------------------------------------------------------\n\n #resolveStance() : void\n {\n this.#checkStance(this.state.wantsStance);\n\n let next = this.#stance;\n\n if(this.stanceFits(this.state.wantsStance))\n {\n next = this.state.wantsStance;\n }\n else\n {\n for(const name of stanceNamesByHeight(this.state.stances))\n {\n if(this.#isACapsule(this.state.stances[name]) && this.stanceFits(name))\n {\n next = name;\n break;\n }\n }\n }\n\n this.#stance = next;\n\n const config = this.#stanceConfig(next);\n\n // A game changes what a stance means by writing that entry, so what the capsule follows is the numbers\n // rather than the name (design/character.md#the-capsule).\n if(config.height === this.#builtHeight && config.radius === this.#builtRadius)\n {\n return;\n }\n\n this.#checkStance(next);\n\n // Resizing holds the foot still and lets the head move (design/character.md#the-capsule).\n this.#character().setShapeOptions({ capsuleHeight: config.height, capsuleRadius: config.radius }, true);\n\n this.#builtHeight = config.height;\n this.#builtRadius = config.radius;\n }\n\n #groundVelocityWanted() : Vector3\n {\n const asked = new Vector3(this.state.moveX, 0, this.state.moveZ);\n\n if(asked.lengthSquared() > 1)\n {\n asked.normalize();\n }\n\n let speed = this.state.stances[this.#stance].speed;\n\n if(this.state.wantsSprint)\n {\n speed = speed * this.state.sprintScale;\n }\n\n asked.scaleInPlace(speed);\n\n // moveX and moveZ are in the character's own frame (design/character.md#state).\n const facing = this.entity.node.rotationQuaternion ?? Quaternion.Identity();\n\n return asked.rotateByQuaternionToRef(facing, new Vector3());\n }\n\n // A request that cannot be spent is held rather than thrown away (design/character.md#jump).\n #takesAJump(surface : CharacterSurfaceInfo) : boolean\n {\n if(this.state.wantsJump)\n {\n // Six is six steps on which the jump still works, counting the step it was asked on -- the same\n // count the coyote window keeps (design/character.md#jump). The asking step is always one of them,\n // because a jump is taken on the step it is asked for when one is allowed.\n this.#jumpBuffer = Math.max(this.state.jumpBufferSteps, 1);\n }\n\n if(this.#jumpBuffer <= 0)\n {\n return false;\n }\n\n if(!this.#jumpAllowed(surface))\n {\n this.#jumpBuffer -= 1;\n\n return false;\n }\n\n this.#jumpBuffer = 0;\n\n // The character is on its way up, so the coyote window it just left is spent.\n this.#stepsSinceSupported = Number.MAX_SAFE_INTEGER;\n\n return true;\n }\n\n #jumpAllowed(surface : CharacterSurfaceInfo) : boolean\n {\n // Sliding is touching ground the character cannot stand on, and a jump from it would climb a slope the\n // slope is meant to refuse (design/character.md#grounded-and-stance-are-answers).\n if(surface.supportedState === CharacterSupportedState.SLIDING)\n {\n return false;\n }\n\n if(this.#stance !== this.#tallest())\n {\n return false;\n }\n\n return this.#grounded || this.#stepsSinceSupported <= this.state.coyoteSteps;\n }\n\n #ownVelocity() : Vector3\n {\n return this.#character().getVelocity()\n .subtract(this.#carried);\n }\n\n #surfaceVelocity() : Vector3\n {\n const ground = this.#ground;\n\n if(ground === undefined)\n {\n return Vector3.Zero();\n }\n\n return ground.getLinearVelocity();\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // What the character is standing on\n //------------------------------------------------------------------------------------------------------------------\n\n // A short shape cast straight down from the foot answers what is there and whose it is. The character keeps the\n // body it was already standing on while that body is still under it, so stepping onto a platform commits once\n // instead of flickering between it and the floor it left (design/character.md#standing-on-something-that-moves).\n #findGround() : PhysicsBody | undefined\n {\n if(!this.#grounded)\n {\n return undefined;\n }\n\n const foot = this.#foot();\n const to = new Vector3(foot.x, foot.y - this.#castLength(), foot.z);\n const nearest = this.#castProbe(foot, to);\n const remembered = this.#ground;\n\n if(remembered === undefined || remembered === nearest || remembered.isDisposed)\n {\n return nearest;\n }\n\n // Two surfaces can be under one foot at a join. Casting again past the nearer one is what asks whether the\n // one the character was standing on is still there.\n if(nearest !== undefined && this.#castProbe(foot, to, nearest) === remembered)\n {\n return remembered;\n }\n\n return nearest;\n }\n\n // The candidate capsule is built at the character's own foot, so its bottom sits where the character's does\n // and the floor underneath is not what refuses a stance.\n #capsuleOverlaps(stance : string, config : StanceConfig) : boolean\n {\n const plugin = this.#havok();\n const foot = this.#foot();\n const input = new ProximityCastResult();\n const hit = new ProximityCastResult();\n\n plugin.shapeProximity(\n {\n shape: this.#stanceShape(stance, config),\n position: new Vector3(foot.x, foot.y + (config.height / 2), foot.z),\n rotation: Quaternion.Identity(),\n maxDistance: 0,\n shouldHitTriggers: false,\n ignoreBody: this.#capsuleBody,\n },\n input,\n hit\n );\n\n return hit.hasHit;\n }\n\n #stanceShape(stance : string, config : StanceConfig) : PhysicsShapeCapsule\n {\n const held = this.#stanceShapes.get(stance);\n\n if(held !== undefined && held.height === config.height && held.radius === config.radius)\n {\n return held.shape;\n }\n\n held?.shape.dispose();\n\n const half = (config.height / 2) - config.radius;\n const shape = new PhysicsShapeCapsule(\n new Vector3(0, half, 0),\n new Vector3(0, -half, 0),\n config.radius,\n this.entity.node.getScene()\n );\n\n this.#stanceShapes.set(stance, { shape, height: config.height, radius: config.radius });\n\n return shape;\n }\n\n // The cast reaches a little past the gap a settled capsule holds itself at, which is the controller's own\n // contact distance rather than a number chosen here (design/character.md#standing-on-something-that-moves).\n #castLength() : number\n {\n const controller = this.#character();\n\n return controller.keepDistance + controller.keepContactTolerance;\n }\n\n #castProbe(from : Vector3, to : Vector3, ignore ?: PhysicsBody) : PhysicsBody | undefined\n {\n const plugin = this.#havok();\n\n const input = new ShapeCastResult();\n const hit = new ShapeCastResult();\n\n plugin.shapeCast(\n {\n shape: this.#probeShape(this.#currentRadius()),\n rotation: Quaternion.Identity(),\n startPosition: from,\n endPosition: to,\n shouldHitTriggers: false,\n ignoreBody: ignore,\n },\n input,\n hit\n );\n\n // The capsule is filtered out here on every cast alike, whether or not this call also asked Havok to\n // skip something else (design/character.md#standing-on-something-that-moves).\n if(!hit.hasHit || hit.body === undefined || hit.body === this.#capsuleBody)\n {\n return undefined;\n }\n\n return hit.body;\n }\n\n #probeShape(radius : number) : PhysicsShapeSphere\n {\n if(this.#probe === undefined || this.#probeRadius !== radius)\n {\n this.#probe?.dispose();\n this.#probe = new PhysicsShapeSphere(\n Vector3.Zero(),\n radius,\n this.entity.node.getScene()\n );\n this.#probeRadius = radius;\n }\n\n return this.#probe;\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Answers\n //------------------------------------------------------------------------------------------------------------------\n\n // Both are recomputed at the top of every step, so a write could not survive to mean anything. It is discarded\n // rather than refused because a getter with no setter throws in strict mode\n // (design/character.md#grounded-and-stance-are-answers).\n // The last step's answers, held as ordinary values once there is no step left to recompute them. State stays\n // total, and neither field reaches a controller that is gone.\n // Both are computed from what the last step worked out, with nothing under them, and destroying the entity\n // leaves each holding that last answer (design/character.md#grounded-and-stance-are-answers).\n #installAnswers() : void\n {\n this.computed('grounded', {\n get: () => this.#grounded,\n set: () => { /* an answer, not a field */ },\n });\n\n this.computed('stance', {\n get: () => this.#stance,\n set: () => { /* an answer, not a field */ },\n });\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // The capsule, in the two frames it is measured in\n //------------------------------------------------------------------------------------------------------------------\n\n #currentHeight() : number\n {\n return this.#stanceConfig(this.#stance).height;\n }\n\n #currentRadius() : number\n {\n return this.#stanceConfig(this.#stance).radius;\n }\n\n // `stances` is state and a game may write anything into it, this included (design/character.md#state).\n #stanceConfig(stance : string) : StanceConfig\n {\n const config = this.state.stances[stance];\n\n if(config === undefined)\n {\n throw new Error(\n `CharacterControllerBehavior: entity \"${ this.entity.id }\" is in the stance \"${ stance }\", and `\n + 'its `stances` holds no entry under that name.'\n );\n }\n\n return config;\n }\n\n #tallest() : string\n {\n const names = stanceNamesByHeight(this.state.stances);\n\n if(names.length === 0)\n {\n throw new Error(\n `CharacterControllerBehavior: entity \"${ this.entity.id }\" has no stances to be in, and a `\n + 'character is always in one.'\n );\n }\n\n return names[0];\n }\n\n #foot() : Vector3\n {\n return this.#footOf(this.#character().getPosition());\n }\n\n #footOf(centre : Vector3) : Vector3\n {\n return new Vector3(centre.x, centre.y - this.#character().footOffset, centre.z);\n }\n\n // Havok answers both queries, and it is here from the moment the capsule is until the moment it is not, so\n // asking either of them outside that window is a question with no answer rather than a \"no\"\n // (design/physics.md#a-missing-subsystem-throws).\n #havok() : HavokPlugin\n {\n if(this.#plugin === undefined)\n {\n throw new Error(\n `CharacterControllerBehavior: entity \"${ this.entity.id }\" has no capsule to answer for.`\n );\n }\n\n return this.#plugin;\n }\n\n #character() : PhysicsCharacterController\n {\n if(this.#controller === undefined)\n {\n throw new Error(\n `CharacterControllerBehavior: entity \"${ this.entity.id }\" has no capsule to answer for.`\n );\n }\n\n return this.#controller;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Input Capture\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { BindingInput, DeviceType } from './binding.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport interface CaptureOptions\n{\n deviceTypes ?: readonly DeviceType[];\n sourceTypes ?: readonly string[];\n signal ?: AbortSignal;\n}\n\nexport interface CandidateInput\n{\n device : { type : DeviceType; index : number };\n sourceType : string;\n sourceKey : string;\n value : boolean | number;\n}\n\n// Waits for the next deliberate input and answers with the definition that would bind it (input.md#input-capture).\nexport class InputCapture\n{\n readonly #deviceTypes ?: readonly DeviceType[];\n readonly #sourceTypes ?: readonly string[];\n\n #resolveWait : ((input : BindingInput) => void) | undefined;\n #rejectWait : ((reason : unknown) => void) | undefined;\n\n constructor(options : CaptureOptions = {})\n {\n this.#deviceTypes = options.deviceTypes;\n this.#sourceTypes = options.sourceTypes;\n\n options.signal?.addEventListener('abort', () =>\n {\n this.#rejectWait?.(options.signal?.reason ?? new Error('The capture was aborted.'));\n this.#resolveWait = undefined;\n this.#rejectWait = undefined;\n });\n }\n\n // A rebinding screen feeds every candidate input it sees here as it arrives.\n feed(candidate : CandidateInput) : void\n {\n if(this.#resolveWait === undefined)\n {\n return;\n }\n\n if(!this.#isDeliberate(candidate.value))\n {\n return;\n }\n\n if(this.#deviceTypes !== undefined && !this.#deviceTypes.includes(candidate.device.type))\n {\n return;\n }\n\n if(this.#sourceTypes !== undefined && !this.#sourceTypes.includes(candidate.sourceType))\n {\n return;\n }\n\n this.#resolveWait({\n device: candidate.device,\n sourceType: candidate.sourceType,\n sourceKey: candidate.sourceKey,\n });\n\n this.#resolveWait = undefined;\n this.#rejectWait = undefined;\n }\n\n wait() : Promise<BindingInput>\n {\n return new Promise((resolve, reject) =>\n {\n this.#resolveWait = resolve;\n this.#rejectWait = reject;\n });\n }\n\n #isDeliberate(value : boolean | number) : boolean\n {\n if(value === true)\n {\n return true;\n }\n\n if(typeof value === 'number' && value !== 0)\n {\n return true;\n }\n\n return false;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Configuration\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { Action } from '@skewedaspect/sage-core';\n\nimport type { ActionContextRegistry } from './actionContext.ts';\nimport type { ActionRegistry } from './actionRegistry.ts';\nimport type { Binding, BindingDefinition } from './binding.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport interface ContextDefinition\n{\n name : string;\n exclusive : boolean;\n}\n\n// The whole set of actions, bindings, and contexts, JSON-compatible (input.md#bindings).\nexport interface Configuration\n{\n actions : readonly Action[];\n bindings : readonly BindingDefinition[];\n contexts : readonly ContextDefinition[];\n}\n\nexport function exportConfiguration(\n actions : readonly Action[],\n bindings : readonly Binding<unknown>[],\n contexts : readonly ContextDefinition[]\n) : Configuration\n{\n return {\n actions: [ ...actions ],\n bindings: bindings.map((binding) => binding.toDefinition()),\n contexts: [ ...contexts ],\n };\n}\n\n// Registers every action and every context a configuration names. Binding definitions are handed back\n// for the caller to resolve, since resolution needs a device reader the configuration itself does not carry.\nexport function importConfiguration(\n config : Configuration,\n actions : ActionRegistry,\n contexts : ActionContextRegistry\n) : readonly BindingDefinition[]\n{\n for(const action of config.actions)\n {\n actions.register(action);\n }\n\n for(const context of config.contexts)\n {\n contexts.registerContext(context.name, context.exclusive);\n }\n\n return [ ...config.bindings ];\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// SkewedAspect Game Engine\n//----------------------------------------------------------------------------------------------------------------------\n\n// Interfaces\nimport type { GameCanvas, RenderEngineOptions, SageOptions } from './interfaces/game.ts';\n\n// Base Classes\nimport { DebugConsole } from './classes/debugConsole.ts';\nimport { GameEngine, type SimulationFactory } from './classes/gameEngine.ts';\n\n// Entities\nimport { NodeSimulation } from './entities/nodeSimulation.ts';\nimport type { NodeEntityRegistry } from './entities/nodeSimulation.ts';\nimport { NodePool } from './entities/nodePool.ts';\nimport type { MeshLoader } from './entities/nodeSimulationContext.ts';\n\nimport { EventBus } from '@skewedaspect/sage-core';\nimport type { ValidEntityRegistry } from '@skewedaspect/sage-core';\nimport type { Scene } from '@babylonjs/core';\n\n// Engines\nimport { AudioEngine } from './engines/audio.ts';\nimport { SceneEngine } from './engines/scene.ts';\n\n// Managers\nimport { AssetManager } from './managers/asset.ts';\nimport { GameManager } from './managers/game.ts';\nimport { InputManager } from './managers/input.ts';\nimport { LevelManager } from './managers/level.ts';\nimport { AudioManager } from './managers/audio.ts';\nimport { SimulationSaveManager } from './managers/simulationSave.ts';\nimport { ColliderDebugManager } from './managers/colliderDebug.ts';\n\n// Utils\nimport { createEngine } from './utils/graphics.ts';\nimport { LoggingUtility } from './utils/logger.ts';\nimport { GameTimer } from './utils/timer.ts';\nimport { VERSION } from './utils/version.ts';\n\n// Debug\nimport { registerBuiltins } from './debug/builtins.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Creates and initializes a fully-wired GameEngine with rendering, physics, and entity management. The\n * engine owns one InputManager (`engine.managers.inputManager`): its action registry, its action context\n * registry, device state capture, and the routing loop that runs before each frame's fixed steps\n * (docs/design/input.md). A game declares its own actions, contexts, and bindings onto it.\n *\n * @param entityClasses - The entity registry, keyed by class name, that the simulation spawns from\n */\nexport async function createGameEngine<Registry extends NodeEntityRegistry = NodeEntityRegistry>(\n canvas : GameCanvas,\n entityClasses : Registry & ValidEntityRegistry<Registry>,\n options : SageOptions = {}\n) : Promise<GameEngine<Registry>>\n{\n // Initialize logging\n const logger = new LoggingUtility(options.logLevel || 'debug');\n const engineLogger = logger.getLogger('SAGE');\n\n engineLogger.info(`Initializing SAGE Game Engine v${ VERSION }...`);\n\n // Initialize BabylonJS stuff\n engineLogger.debug('Creating rendering engine...');\n\n const renderOptions : RenderEngineOptions = {\n antialias: true,\n adaptToDeviceRatio: true,\n enableAllFeatures: true,\n ...options.renderOptions,\n };\n\n const engine = await createEngine(canvas, renderOptions, options.largeWorldRendering ?? false, engineLogger);\n\n // Initialize internals\n engineLogger.debug('Creating event bus...');\n const bus = new EventBus();\n\n // Initialize SAGE Engines\n engineLogger.debug('Creating scene engine...');\n const sceneEngine = new SceneEngine(engine, logger, options.havokBinary);\n\n // Initialize SAGE Managers\n engineLogger.debug('Creating managers...');\n const assetManager = new AssetManager(bus, sceneEngine, logger);\n const levelManager = new LevelManager<Registry>(bus, logger);\n const saveManager = new SimulationSaveManager<Registry>(levelManager, VERSION);\n const gameManager = new GameManager<Registry>(engine, bus, levelManager, logger);\n const inputManager = new InputManager(logger);\n gameManager.setInputManager(inputManager);\n\n // Initialize audio system (opt-in)\n let audioEngine : AudioEngine | undefined;\n let audioManager : AudioManager | undefined;\n\n if(options.audioChannels && options.audioChannels.length > 0)\n {\n engineLogger.debug('Creating audio engine...');\n audioEngine = new AudioEngine(logger);\n await audioEngine.initialize();\n\n engineLogger.debug('Creating audio manager...');\n audioManager = new AudioManager(audioEngine, logger);\n await audioManager.initialize(options.audioChannels);\n }\n\n // A factory rather than an instance: no scene exists yet at boot, so there is nothing to build a\n // NodeSimulation against until a level (GameLevel.buildScene) or the game itself calls rebuildSimulation.\n const fixedStepDuration = options.fixedStepDuration ?? 1 / 60;\n const noMesh : MeshLoader = async () => undefined;\n\n engineLogger.debug('Creating collider debug manager...');\n const colliderDebugManager = new ColliderDebugManager();\n\n const buildSimulation : SimulationFactory<Registry> = (\n scene : Scene,\n simulationName : string,\n meshSource ?: MeshLoader\n ) => new NodeSimulation<Registry>({\n id: simulationName,\n bus,\n fixedStepDuration,\n entityClasses,\n scene,\n pool: new NodePool(),\n loadMesh: meshSource ?? noMesh,\n\n audio: audioManager,\n colliders: colliderDebugManager,\n logger: logger.getLogger('NodeSimulation'),\n });\n\n // Initialize debug tools\n let debugConsole : DebugConsole | null = null;\n\n if(options.debug !== false)\n {\n engineLogger.debug('Creating debug console...');\n let namespaceName = 'sage';\n\n if(typeof options.debug === 'object' && options.debug.namespace !== undefined)\n {\n namespaceName = options.debug.namespace;\n }\n\n debugConsole = new DebugConsole(namespaceName);\n }\n\n // Initialize timer\n engineLogger.debug('Creating game timer...');\n const timer = new GameTimer();\n gameManager.registerFrameCallback((dt) => timer.tick(dt * 1000));\n\n engineLogger.info(`SAGE Game Engine v${ VERSION } initialized successfully.`);\n\n const ge = new GameEngine<Registry>(\n canvas,\n engine,\n bus,\n logger,\n timer,\n buildSimulation,\n\n // Engines\n {\n sceneEngine,\n audioEngine,\n },\n\n // Managers\n {\n assetManager,\n gameManager,\n inputManager,\n levelManager,\n saveManager,\n audioManager,\n },\n\n options.largeWorldRendering ?? false,\n\n // Debug tools\n {\n colliders: colliderDebugManager,\n console: debugConsole,\n expose: (name : string, value : unknown) =>\n {\n debugConsole?.expose(name, value);\n },\n }\n );\n\n // Late-bind: these managers are constructed before GameEngine (because GameEngine takes them as\n // constructor args), but they need a back-reference at runtime. This must happen before any level\n // loads or entity attaches, so do it immediately after construction. Do not reorder.\n levelManager.$setGameEngine(ge);\n gameManager.$setGameEngine(ge);\n saveManager.$setGameEngine(ge);\n\n // Register debug console built-ins\n if(debugConsole)\n {\n registerBuiltins(debugConsole, ge);\n }\n\n return ge;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n// Exports\n//----------------------------------------------------------------------------------------------------------------------\n\nexport * from './interfaces/game.ts';\nexport * from './interfaces/logger.ts';\n\n// Export base classes\nexport { AssetManager, AudioEngine, GameEngine, GameManager, LevelManager, SceneEngine };\nexport type { DebugTools, Engines, GameHook, Managers, SimulationFactory } from './classes/gameEngine.ts';\n\nexport * from './classes/level.ts';\nexport * from './classes/gameLevel.ts';\nexport type {\n AssetCompletePayload, AssetProgressPayload, GamePausedPayload, GameResumedPayload, LevelCompletePayload,\n LevelErrorPayload, LevelProgressPayload, LevelTransitionCompletePayload, LevelTransitionErrorPayload,\n LevelTransitionProgressPayload, LevelTransitionStartPayload, TriggerEnterPayload, TriggerExitPayload,\n} from './events/payloads.ts';\nexport * from './handlers/index.ts';\nexport * from './interfaces/level.ts';\nexport * from './interfaces/lifecycle.ts';\nexport * from './utils/logger.ts';\nexport * from './utils/timer.ts';\nexport * from './utils/version.ts';\n\n// Export the entity layer -- sage-core's own base classes, so a game can author entities and behaviors\n// without a direct sage-core dependency. A `declare module` against either package augments `EventMap`: a\n// re-exported interface is the interface itself.\nexport {\n Behavior, EventBus, GameEntity, GameRandom, GameSimulation, NamedBehavior, StateMachineBehavior, defineStates,\n dilationLimit, drainPassLimit, operationCallDepthLimit,\n} from '@skewedaspect/sage-core';\nexport type {\n BehaviorClass, ComposedOf, Computation, DeepReadonly, EventCallback, EventMap, EventSubscription, EventsMatching,\n GameEntityClass, GameEntityRegistry, GameEntitySnapshot, GameEntityState, GameEntityType, GameEvent,\n GameSimulationOptions, GameSimulationSnapshot, GameSimulationView, IDAllocator, NamedState, NarrowedTo,\n OpsFor, OpsOf, RandomSeed, RandomState, SimulationFor, StateBag, StateDefinition, StateFor, StateSliceOf,\n StateValue, Subscriber, Unsubscribe, ValidEntityRegistry, ValidTuple,\n} from '@skewedaspect/sage-core';\nexport { NodeEntity } from './entities/nodeEntity.ts';\nexport type { NodeBehaviorHooks, NodeEntityState, QuaternionState, Vector3State } from './entities/nodeEntity.ts';\nexport { NodePool } from './entities/nodePool.ts';\nexport { poolSizeLimit } from './entities/constants.ts';\nexport { NodeSimulation } from './entities/nodeSimulation.ts';\nexport type { NodeEntityRegistry, NodeSimulationOptions } from './entities/nodeSimulation.ts';\nexport type { MeshLoader, NodeSet, NodeSimulationContext } from './entities/nodeSimulationContext.ts';\nexport type { EntityPickFilter, EntityPickResult } from './entities/entityPicking.ts';\n\n// Export the authoring surface: entity definitions compiling onto the entity registry, and the standard mesh\n// source built from it (docs/design/authoring.md).\nexport { defineEntity } from './entities/defineEntity.ts';\nexport type { GameEntityDefinition, GameEntityDefinitionClass } from './entities/defineEntity.ts';\nexport { buildStandardMeshSource } from './entities/standardMeshSource.ts';\nexport type { MeshAssetSource } from './entities/standardMeshSource.ts';\nexport type { MeshConfig, MeshMaterialConfig } from './entities/meshConfig.ts';\n\n// Export the provided behaviors (docs/design/audio.md, docs/design/physics.md,\n// docs/design/character.md).\nexport { SoundBehavior } from './behaviors/sound.ts';\nexport type { SoundConfig, SoundState } from './behaviors/sound.ts';\nexport { HavokPhysicsBodyBehavior } from './behaviors/physicsBody.ts';\nexport type { BodyMotion, BodyShape, CompoundPart, PhysicsBodyState } from './behaviors/physicsBody.ts';\nexport { CharacterControllerBehavior } from './behaviors/characterController.ts';\nexport type { CharacterControllerState, StanceConfig } from './behaviors/characterController.ts';\n\n// Export the input system: the action registry, action contexts, the four binding kinds, device readers,\n// binding resolution and delivery, capture, and configuration import/export (docs/design/input.md,\n// docs/design/events.md#delivery). `engine.managers.inputManager` is the one instance a game declares its\n// own actions, contexts, and bindings onto; these exports are the pieces it builds them from.\nexport { ActionRegistry } from './input/actionRegistry.ts';\nexport { ActionContextRegistry } from './input/actionContext.ts';\nexport type {\n Binding, BindingDefinition, BindingFire, BindingInput, BindingKind, DeviceID, DeviceType,\n} from './input/binding.ts';\nexport { schemeOf } from './input/scheme.ts';\nexport type { InputScheme } from './input/scheme.ts';\nexport type { DeviceReader } from './input/deviceReader.ts';\nexport type { GamepadState, KeyboardState, MouseState } from './input/deviceState.ts';\nexport { KeyboardReader } from './input/deviceReaders/keyboard.ts';\nexport { MouseReader } from './input/deviceReaders/mouse.ts';\nexport type { MouseSourceType } from './input/deviceReaders/mouse.ts';\nexport { GamepadReader } from './input/deviceReaders/gamepad.ts';\nexport type { GamepadSourceType } from './input/deviceReaders/gamepad.ts';\nexport { TriggerBinding } from './input/bindings/trigger.ts';\nexport type { EdgeMode, TriggerOptions } from './input/bindings/trigger.ts';\nexport { ToggleBinding } from './input/bindings/toggle.ts';\nexport type { ToggleOptions } from './input/bindings/toggle.ts';\nexport { ValueBinding } from './input/bindings/value.ts';\nexport type { ValueOptions } from './input/bindings/value.ts';\nexport { DeltaBinding } from './input/bindings/delta.ts';\nexport type { DeltaOptions } from './input/bindings/delta.ts';\nexport { resolveBinding } from './input/resolveBinding.ts';\nexport { readerFor } from './input/readerFor.ts';\nexport { deliverAction } from './input/deliverAction.ts';\nexport { InputCapture } from './input/inputCapture.ts';\nexport type { CandidateInput, CaptureOptions } from './input/inputCapture.ts';\nexport { exportConfiguration, importConfiguration } from './input/configuration.ts';\nexport type { Configuration, ContextDefinition } from './input/configuration.ts';\nexport type { Action, AnalogAction, DigitalAction } from '@skewedaspect/sage-core';\n\n// Export manager types\nexport type { FrameCallback } from './managers/game.ts';\nexport { InputManager, ROUTING_LOOP_SENDER_ID } from './managers/input.ts';\nexport type { DeviceChangedPayload, SchemeChangedPayload } from './managers/input.ts';\nexport type { TransitionOptions } from './managers/level.ts';\nexport { AudioManager } from './managers/audio.ts';\nexport { ColliderDebugManager } from './managers/colliderDebug.ts';\nexport type { ColliderDebugConfig } from './managers/colliderDebug.ts';\nexport { OutlineManager } from './managers/outline.ts';\nexport { SimulationSaveManager } from './managers/simulationSave.ts';\nexport type { BeforeSaveHook, LoadResult, SaveEnvelope } from './managers/simulationSave.ts';\n\n// Export debug tools\nexport { DebugConsole } from './classes/debugConsole.ts';\nexport { registerBuiltins } from './debug/builtins.ts';\n\n// Export utilities\nexport { computeHierarchyBounds } from './utils/bounds.ts';\nexport { isBrowser } from './utils/capabilities.ts';\nexport type { HierarchyBounds } from './utils/bounds.ts';\nexport {\n getCanonicalTransform,\n toQuatObject, toQuaternion, toVec3Object, toVector3,\n} from './utils/vectors.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n"],"mappings":"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"}
1
+ {"version":3,"file":"sage.es.js","names":[],"sources":["../src/utils/capabilities.ts","../src/classes/debugConsole.ts","../src/utils/version.ts","../src/classes/gameEngine.ts","../src/utils/vectors.ts","../src/entities/nodeEntity.ts","../src/entities/entityPicking.ts","../src/classes/loggers/consoleBackend.ts","../src/classes/loggers/nullBackend.ts","../src/utils/logger.ts","../src/entities/nodeSimulation.ts","../src/entities/constants.ts","../src/entities/nodePool.ts","../src/engines/audio.ts","../src/utils/physics.ts","../src/engines/scene.ts","../src/managers/asset.ts","../src/managers/game.ts","../src/input/actionContext.ts","../src/input/actionRegistry.ts","../src/input/deliverAction.ts","../src/input/deviceReaders/gamepad.ts","../src/input/deviceReaders/keyboard.ts","../src/input/deviceReaders/mouse.ts","../src/input/readerFor.ts","../src/input/bindings/trigger.ts","../src/input/bindings/toggle.ts","../src/input/bindings/value.ts","../src/input/bindings/delta.ts","../src/input/resolveBinding.ts","../src/input/scheme.ts","../src/managers/input.ts","../src/handlers/postProcessingPipeline.ts","../src/handlers/postProcessing.ts","../src/utils/bounds.ts","../src/handlers/collider.ts","../src/handlers/lod.ts","../src/handlers/occlusion.ts","../src/utils/metadata.ts","../src/handlers/sound.ts","../src/handlers/trigger.ts","../src/handlers/visible.ts","../src/handlers/index.ts","../src/classes/level.ts","../src/managers/outline.ts","../src/classes/gameLevel.ts","../src/managers/level.ts","../src/managers/audio.ts","../src/managers/simulationSave.ts","../src/managers/colliderDebug.ts","../src/utils/graphics.ts","../src/utils/timer.ts","../src/debug/builtins.ts","../src/interfaces/logger.ts","../src/entities/defineEntity.ts","../src/entities/standardMeshSource.ts","../src/behaviors/sound.ts","../src/behaviors/physicsBody.ts","../src/behaviors/characterController.ts","../src/input/inputCapture.ts","../src/input/configuration.ts","../src/sage.ts"],"sourcesContent":["//----------------------------------------------------------------------------------------------------------------------\n// Utilities to test for capability of the current environment\n//----------------------------------------------------------------------------------------------------------------------\n\nexport function isBrowser() : boolean\n{\n return typeof window !== 'undefined' && typeof window.document !== 'undefined';\n}\n\nexport function hasWebGPU() : boolean\n{\n return isBrowser()\n && !!window.navigator.gpu;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Debug Console\n//\n// Manages a namespace object on `window` that exposes game engine internals to the browser dev console.\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { isBrowser } from '../utils/capabilities.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/** What `expose` takes to define a property that resolves fresh on every read. */\nexport interface DynamicGetter\n{\n get : () => unknown;\n}\n\ninterface ExposedEntry\n{\n getter ?: () => unknown;\n value ?: unknown;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport class DebugConsole\n{\n #namespaceName : string;\n #entries = new Map<string, ExposedEntry>();\n #disposed = false;\n\n constructor(namespaceName = 'sage')\n {\n this.#namespaceName = namespaceName;\n this.#installNamespace();\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Public API\n //------------------------------------------------------------------------------------------------------------------\n\n /** Defines a property that resolves fresh on every read (design/debugging.md#debug-tooling). */\n expose(name : string, getter : DynamicGetter) : void;\n\n /** Puts a value, a callable command included, on the namespace (design/debugging.md#debug-tooling). */\n expose(name : string, value : unknown) : void;\n\n expose(name : string, value : unknown) : void\n {\n if(this.#disposed)\n {\n return;\n }\n\n if(this.#isDynamicGetter(value))\n {\n this.#entries.set(name, { getter: value.get });\n this.#defineGetter(name, value.get);\n }\n else\n {\n this.#entries.set(name, { value });\n this.#defineValue(name, value);\n }\n }\n\n setNamespace(name : string) : void\n {\n if(this.#disposed || name === this.#namespaceName)\n {\n return;\n }\n\n // Checked before the old namespace comes down, so a collision leaves the console where it was\n this.#assertNamespaceFree(name);\n\n this.#removeNamespace();\n this.#namespaceName = name;\n this.#installNamespace();\n\n for(const [ entryName, entry ] of this.#entries)\n {\n if(entry.getter)\n {\n this.#defineGetter(entryName, entry.getter);\n }\n else\n {\n this.#defineValue(entryName, entry.value);\n }\n }\n }\n\n get namespaceName() : string\n {\n return this.#namespaceName;\n }\n\n dispose() : void\n {\n if(this.#disposed)\n {\n return;\n }\n\n this.#removeNamespace();\n this.#entries.clear();\n this.#disposed = true;\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Private\n //------------------------------------------------------------------------------------------------------------------\n\n #isDynamicGetter(value : unknown) : value is DynamicGetter\n {\n return typeof value === 'object'\n && value !== null\n && 'get' in value\n && typeof (value as DynamicGetter).get === 'function';\n }\n\n #getNamespace() : Record<string, unknown> | undefined\n {\n if(!isBrowser())\n {\n return undefined;\n }\n\n return Reflect.get(window, this.#namespaceName) as Record<string, unknown> | undefined;\n }\n\n #assertNamespaceFree(name : string) : void\n {\n if(!isBrowser())\n {\n return;\n }\n\n if(Reflect.has(window, name))\n {\n throw new Error(\n `window.${ name } is already occupied, and the debug console will not overwrite it. `\n + 'Pick a different namespace with the debug.namespace option.'\n );\n }\n }\n\n #installNamespace() : void\n {\n if(!isBrowser())\n {\n return;\n }\n\n this.#assertNamespaceFree(this.#namespaceName);\n\n Reflect.set(window, this.#namespaceName, {});\n }\n\n #removeNamespace() : void\n {\n if(!isBrowser())\n {\n return;\n }\n\n Reflect.deleteProperty(window, this.#namespaceName);\n }\n\n #defineGetter(name : string, getter : () => unknown) : void\n {\n const ns = this.#getNamespace();\n\n if(!ns)\n {\n return;\n }\n\n Object.defineProperty(ns, name, {\n get: getter,\n enumerable: true,\n configurable: true,\n });\n }\n\n #defineValue(name : string, value : unknown) : void\n {\n const ns = this.#getNamespace();\n\n if(!ns)\n {\n return;\n }\n\n Object.defineProperty(ns, name, {\n value,\n writable: true,\n enumerable: true,\n configurable: true,\n });\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Version utility for SAGE\n//----------------------------------------------------------------------------------------------------------------------\n\ndeclare const __APP_VERSION__ : string;\n\nexport const VERSION = typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : '0.0.0-dev';\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Game Engine\n//\n// WARNING: This module must be imported as `import type` everywhere except sage.ts. A regular import\n// would create a circular runtime dependency (managers import GameEngine, GameEngine imports managers).\n// The `import type` is erased at compile time, keeping the runtime graph acyclic.\n//----------------------------------------------------------------------------------------------------------------------\n\n// Interfaces\nimport type { AbstractEngine, Scene } from '@babylonjs/core';\nimport type { EventBus, Unsubscribe } from '@skewedaspect/sage-core';\nimport type { GameCanvas } from '../interfaces/game.ts';\nimport type { Disposable } from '../interfaces/lifecycle.ts';\nimport type { LoggerInterface } from '../interfaces/logger.ts';\n\n// Entities\nimport type { NodeEntityRegistry, NodeSimulation } from '../entities/nodeSimulation.ts';\nimport type { MeshLoader } from '../entities/nodeSimulationContext.ts';\n\n// Engines\nimport type { SceneEngine } from '../engines/scene.ts';\nimport type { AudioEngine } from '../engines/audio.ts';\n\n// Managers\nimport type { AssetManager } from '../managers/asset.ts';\nimport type { ColliderDebugManager } from '../managers/colliderDebug.ts';\nimport type { DebugConsole } from './debugConsole.ts';\nimport type { GameManager } from '../managers/game.ts';\nimport type { InputManager } from '../managers/input.ts';\nimport type { LevelManager } from '../managers/level.ts';\nimport type { SimulationSaveManager } from '../managers/simulationSave.ts';\nimport type { AudioManager } from '../managers/audio.ts';\n\n// Utils\nimport type { LoggingUtility } from '../utils/logger.ts';\nimport type { GameTimer } from '../utils/timer.ts';\nimport { VERSION } from '../utils/version.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Type definition for game lifecycle hook functions\n */\n\nexport type GameHook<Registry extends NodeEntityRegistry = NodeEntityRegistry>\n = (gameEngine : GameEngine<Registry>) => Promise<void>;\n\n/** What a simulation is built with beside its scene and its name (design/architecture.md#how-a-game-consumes-sage). */\nexport interface RebuildSimulationOptions\n{\n meshSource ?: MeshLoader;\n fixedStepDuration ?: number;\n}\n\n/** Builds a NodeSimulation named and bound to a scene and a mesh source -- a fresh one for every level that loads. */\nexport type SimulationFactory<Registry extends NodeEntityRegistry = NodeEntityRegistry>\n = (scene : Scene, name : string, options ?: RebuildSimulationOptions) => NodeSimulation<Registry>;\n\n/**\n * Interface representing the engines used in the game.\n * All engines must implement Disposable for proper cleanup.\n */\nexport interface Engines extends Record<string, Disposable | undefined>\n{\n sceneEngine : SceneEngine;\n audioEngine ?: AudioEngine;\n}\n\n/**\n * Interface representing the managers used in the game.\n * All managers must implement Disposable for proper cleanup.\n */\nexport interface Managers<Registry extends NodeEntityRegistry = NodeEntityRegistry>\n extends Record<string, Disposable | undefined>\n{\n assetManager : AssetManager;\n gameManager : GameManager<Registry>;\n inputManager : InputManager;\n levelManager : LevelManager<Registry>;\n saveManager : SimulationSaveManager<Registry>;\n audioManager ?: AudioManager;\n}\n\n/**\n * Interface representing the debug tools available on the engine.\n */\nexport interface DebugTools\n{\n colliders : ColliderDebugManager;\n console : DebugConsole | null;\n expose(name : string, value : unknown) : void;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Central hub that owns the render loop, the event bus, and all managers.\n * Created via `createGameEngine()` in sage.ts.\n */\nexport class GameEngine<Registry extends NodeEntityRegistry = NodeEntityRegistry>\n{\n public canvas : GameCanvas;\n public renderEngine : AbstractEngine;\n public managers : Managers<Registry>;\n public engines : Engines;\n public bus : EventBus;\n public logger : LoggingUtility;\n public timer : GameTimer;\n public debug : DebugTools;\n public largeWorldRendering : boolean;\n public started = false;\n\n private _stopped = false;\n\n private _log : LoggerInterface;\n private _buildSimulation : SimulationFactory<Registry>;\n\n // Null until something binds one: the level manager activating a level, or a game binding what it built\n // itself. Only the bound simulation is attached to the bus and stepped\n // (design/simulation.md#which-simulation-the-engine-holds).\n #bound : NodeSimulation<Registry> | null = null;\n\n // Any number of hooks per point, in registration order (design/engine.md#lifecycle-hooks).\n readonly #beforeStartHooks : GameHook<Registry>[] = [];\n readonly #startHooks : GameHook<Registry>[] = [];\n readonly #teardownHooks : GameHook<Registry>[] = [];\n\n /**\n * Creates an instance of GameEngine.\n * @param buildSimulation - Builds a fresh simulation for a scene, once rebuildSimulation is called with one.\n */\n constructor(\n canvas : GameCanvas,\n renderEngine : AbstractEngine,\n bus : EventBus,\n logger : LoggingUtility,\n timer : GameTimer,\n buildSimulation : SimulationFactory<Registry>,\n engines : Engines,\n managers : Managers<Registry>,\n largeWorldRendering = false,\n debug : DebugTools\n )\n {\n this.canvas = canvas;\n this.renderEngine = renderEngine;\n this.bus = bus;\n this.logger = logger;\n this.timer = timer;\n this._buildSimulation = buildSimulation;\n this.engines = engines;\n this.managers = managers;\n this.debug = debug;\n this.largeWorldRendering = largeWorldRendering;\n\n this._log = logger.getLogger('GameEngine');\n }\n\n //------------------------------------------------------------------------------------------------------------------\n\n /** The simulation the engine holds: attached to the bus, stepped by the frame loop, and what a view reads. */\n get simulation() : NodeSimulation<Registry> | null\n {\n return this.#bound;\n }\n\n /**\n * Builds a fresh NodeSimulation named `name` for the given scene and mesh source, handed this engine's bus and\n * not yet bound. A level calls this once its own scene exists, naming the simulation after itself, so every\n * NodeEntity it spawns gets a node in that scene and loads its mesh through that source; left unset, a spawned\n * entity gets a node with no mesh (design/simulation.md#the-simulations-name).\n */\n rebuildSimulation(scene : Scene, name : string, options ?: RebuildSimulationOptions) : NodeSimulation<Registry>\n {\n return this._buildSimulation(scene, name, options);\n }\n\n /**\n * Binds the engine to a simulation, or to none. The one bound before detaches from the bus and the new one\n * attaches, carrying its entities' subscriptions and held emissions across\n * (design/events.md#one-bus-per-engine).\n */\n bindSimulation(simulation : NodeSimulation<Registry> | null) : void\n {\n if(this.#bound === simulation)\n {\n return;\n }\n\n this.#bound?.detach();\n this.#bound = simulation;\n simulation?.attach();\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Public API\n //------------------------------------------------------------------------------------------------------------------\n\n /** Runs before the frame loop starts, awaited, in registration order (design/engine.md#lifecycle-hooks). */\n onBeforeStart(hook : GameHook<Registry>) : Unsubscribe\n {\n return GameEngine.#register(this.#beforeStartHooks, hook);\n }\n\n /** Runs after the frame loop has started, awaited, in registration order. */\n onStart(hook : GameHook<Registry>) : Unsubscribe\n {\n return GameEngine.#register(this.#startHooks, hook);\n }\n\n /** Runs before anything is disposed, awaited; a hook that throws is logged and does not stop the teardown. */\n onTeardown(hook : GameHook<Registry>) : Unsubscribe\n {\n return GameEngine.#register(this.#teardownHooks, hook);\n }\n\n static #register<Registry extends NodeEntityRegistry>(\n hooks : GameHook<Registry>[],\n hook : GameHook<Registry>\n ) : Unsubscribe\n {\n hooks.push(hook);\n\n return () : void =>\n {\n const index = hooks.indexOf(hook);\n\n if(index >= 0)\n {\n hooks.splice(index, 1);\n }\n };\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Lifecycle\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Starts the frame loop, with the before-start hooks ahead of it and the start hooks after. An engine that has\n * stopped does not start again (design/architecture.md#teardown).\n */\n async start() : Promise<void>\n {\n if(this._stopped)\n {\n throw new Error('The game engine has stopped, and an engine that has stopped does not start again.');\n }\n\n if(this.started)\n {\n this._log.warn('Game engine is already started. Skipping start.');\n return;\n }\n\n this._log.info(`Starting SkewedAspect Game Engine (Version: ${ VERSION })...`);\n\n for(const hook of [ ...this.#beforeStartHooks ])\n {\n // eslint-disable-next-line no-await-in-loop\n await hook(this);\n }\n\n await this.managers.gameManager.start();\n\n for(const hook of [ ...this.#startHooks ])\n {\n // eslint-disable-next-line no-await-in-loop\n await hook(this);\n }\n\n this.started = true;\n this._log.info('Game engine started successfully');\n }\n\n /**\n * Tears the engine down in one order: the frame loop stops, the teardown hooks run, then the debug console, the\n * managers, and the engines dispose. A failure in one disposal does not stop the rest, and the first one raised\n * is raised again once everything else is disposed. Stopping twice changes nothing\n * (design/architecture.md#teardown).\n */\n async stop() : Promise<void>\n {\n if(this._stopped)\n {\n this._log.warn('Game engine is already stopped. Skipping stop.');\n return;\n }\n\n this._stopped = true;\n this._log.info(`Stopping SkewedAspect Game Engine (Version: ${ VERSION })...`);\n\n let firstError : unknown = null;\n\n const attempt = async (label : string, run : () => Promise<void>) : Promise<void> =>\n {\n try\n {\n await run();\n }\n catch(error)\n {\n this._log.error(`Error tearing down ${ label }: ${ error }`);\n\n if(firstError === null)\n {\n firstError = error;\n }\n }\n };\n\n // Nothing renders into what is being disposed.\n await attempt('the frame loop', () => this.managers.gameManager.stop());\n\n // Before any manager disposes, so nothing outlives the level that owned it.\n await attempt('unbinding the simulation', async () => this.bindSimulation(null));\n\n for(const hook of [ ...this.#teardownHooks ])\n {\n // eslint-disable-next-line no-await-in-loop\n await attempt('an onTeardown hook', () => hook(this));\n }\n\n await attempt('the debug console', async () => this.debug.console?.dispose());\n await attempt('the collider debug manager', () => this.debug.colliders.dispose());\n\n for(const managerKey of Object.keys(this.managers) as (keyof Managers)[])\n {\n const manager = this.managers[managerKey];\n\n if(manager)\n {\n // eslint-disable-next-line no-await-in-loop\n await attempt(`manager ${ managerKey }`, () => manager.dispose());\n }\n }\n\n for(const engineKey of Object.keys(this.engines) as (keyof Engines)[])\n {\n const engine = this.engines[engineKey];\n\n if(engine)\n {\n // eslint-disable-next-line no-await-in-loop\n await attempt(`engine ${ engineKey }`, () => engine.dispose());\n }\n }\n\n this.started = false;\n this._log.info('Game engine stopped successfully');\n\n if(firstError !== null)\n {\n throw firstError;\n }\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Vector / Quaternion Conversion Utilities\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { Matrix, Quaternion, type TransformNode, Vector3 } from '@babylonjs/core';\n\nimport type { QuatConfig, Vec3Config } from '../interfaces/level.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Convert a BabylonJS Vector3 to a plain { x, y, z } object.\n */\nexport function toVec3Object(vector : Vector3) : Vec3Config\n{\n return { x: vector.x, y: vector.y, z: vector.z };\n}\n\n/**\n * Convert a plain { x, y, z } object to a BabylonJS Vector3.\n */\nexport function toVector3(vec : Vec3Config) : Vector3\n{\n return new Vector3(vec.x, vec.y, vec.z);\n}\n\n/**\n * Convert a BabylonJS Quaternion to a plain { x, y, z, w } object.\n */\nexport function toQuatObject(quat : Quaternion) : QuatConfig\n{\n return { x: quat.x, y: quat.y, z: quat.z, w: quat.w }; // eslint-disable-line id-length\n}\n\n/**\n * Convert a plain { x, y, z, w } object to a BabylonJS Quaternion.\n */\nexport function toQuaternion(quat : QuatConfig) : Quaternion\n{\n return new Quaternion(quat.x, quat.y, quat.z, quat.w);\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n// Live views\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * A record whose components read and write through to whatever `read` answers with, so `view.x += 1` writes the\n * same fact `write` takes whole (design/entities.md#computed-state).\n */\nexport function vectorView(read : () => Vector3, write : (value : Vec3Config) => void) : Vec3Config\n{\n const view = {} as Vec3Config;\n\n for(const component of [ 'x', 'y', 'z' ] as const)\n {\n Object.defineProperty(view, component, {\n get: () : number => read()[component],\n set: (value : number) : void =>\n {\n const held = toVec3Object(read());\n held[component] = value;\n\n write(held);\n },\n enumerable: true,\n configurable: true,\n });\n }\n\n return view;\n}\n\n/** `vectorView` for a quaternion, with `w` alongside the other three. */\nexport function quaternionView(read : () => Quaternion, write : (value : QuatConfig) => void) : QuatConfig\n{\n const view = {} as QuatConfig;\n\n for(const component of [ 'x', 'y', 'z', 'w' ] as const)\n {\n Object.defineProperty(view, component, {\n get: () : number => read()[component],\n set: (value : number) : void =>\n {\n const held = toQuatObject(read());\n held[component] = value;\n\n write(held);\n },\n enumerable: true,\n configurable: true,\n });\n }\n\n return view;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n// Transform Hierarchy Utilities\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * A TransformNode's own local matrix, recovered from its world matrix and its parent's, since Babylon does\n * not expose the local matrix a node's world matrix was built from.\n */\nfunction localMatrix(node : TransformNode) : Matrix\n{\n node.computeWorldMatrix(true);\n const world = node.getWorldMatrix();\n const parent = node.parent as TransformNode | null;\n\n if(!parent)\n {\n return world.clone();\n }\n\n parent.computeWorldMatrix(true);\n\n return world.multiply(Matrix.Invert(parent.getWorldMatrix()));\n}\n\n/**\n * The rotation and scaling of a matrix, with its translation dropped.\n */\nfunction linearPart(matrix : Matrix) : Matrix\n{\n const linear = matrix.clone();\n linear.setTranslation(Vector3.Zero());\n\n return linear;\n}\n\n/**\n * A node's transform with the mirroring an import conversion ancestor folded into its world matrix taken back\n * out: the world position exactly, and a proper rotation and positive scaling facing the same way.\n *\n * Babylon's glTF loader gives every imported model a `__root__` whose own transform mirrors one axis, and folds\n * that into the world matrix of everything under it. Decomposing such a matrix answers with a negative scaling\n * and a rotation of the wrong handedness, and a node built from those does not sit where the marker does.\n */\nexport function getCanonicalTransform(node : TransformNode) : {\n position : Vector3;\n rotation : Quaternion;\n scaling : Vector3;\n}\n{\n node.computeWorldMatrix(true);\n let canonical = node.getWorldMatrix().clone();\n\n let ancestor = node.parent as TransformNode | null;\n while(ancestor)\n {\n const ancestorLocal = localMatrix(ancestor);\n\n // Multiplying on the left by the inverse of the mirror's rotation and scaling carries the node's own\n // rotation and scaling to the other side of it and leaves the world position alone; the mirror's\n // translation, if it has one, stays folded into that position (design/engine.md#the-canonical-transform).\n if(ancestorLocal.determinant() < 0)\n {\n canonical = Matrix.Invert(linearPart(ancestorLocal)).multiply(canonical);\n }\n\n ancestor = ancestor.parent as TransformNode | null;\n }\n\n const position = new Vector3();\n const rotation = new Quaternion();\n const scaling = new Vector3();\n canonical.decompose(scaling, rotation, position);\n\n return { position, rotation, scaling };\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// NodeEntity\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { Quaternion, TransformNode } from '@babylonjs/core';\nimport { GameEntity } from '@skewedaspect/sage-core';\n\nimport type { AbstractMesh } from '@babylonjs/core';\nimport type {\n Action, BehaviorClass, ComposedOf, DeepReadonly, GameEntityState, GameEntityType, OpsFor,\n StateBag, StateFor, ValidTuple,\n} from '@skewedaspect/sage-core';\n\nimport { quaternionView, vectorView } from '../utils/vectors.ts';\n\nimport type { AudioManager } from '../managers/audio.ts';\nimport type { ColliderDebugConfig } from '../managers/colliderDebug.ts';\nimport type { NodeSet, NodeSimulationContext } from './nodeSimulationContext.ts';\nimport type { MeshConfig } from './meshConfig.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport interface Vector3State\n{\n x : number;\n y : number;\n z : number;\n}\n\nexport interface QuaternionState\n{\n x : number;\n y : number;\n z : number;\n w : number;\n}\n\nexport interface NodeEntityState extends GameEntityState\n{\n parentID ?: string;\n nodeName ?: string;\n position : Vector3State;\n rotation : QuaternionState;\n debugCollider ?: ColliderDebugConfig;\n}\n\n// Declared here, not on sage-core's Behavior, because a GameEntity never has a scene node\n// (design/behaviors.md#hooks). A behavior opts in by implementing either method; NodeEntity recognizes it\n// structurally, the same way it recognizes onCreate/onUpdate/onEvent.\nexport interface NodeBehaviorHooks\n{\n onMeshLoaded ?() : void;\n onDestroy ?() : void;\n}\n\n// A NodeSet a load is still in flight for maps here to whoever wants to hear when it settles. A set with no\n// entry has already settled, mesh or not -- prewarm and an already-finished load both leave nothing pending\n// (design/entities.md#pooling). Keyed by object identity rather than a field on NodeSet itself, so a set an\n// older part of the codebase or a test builds by hand, with no notion of this, is still exactly \"settled.\"\nconst pendingMeshLoads = new WeakMap<NodeSet, () => void>();\n\n// A mesh a loader hands an entity is never walked, so a property authored on it configures nothing; the\n// warning names what it found, once per entity class in a simulation\n// (design/authoring.md#a-spawned-mesh-carries-no-authored-properties).\nconst warnedClasses = new WeakMap<NodeSimulationContext, Set<string>>();\n\nfunction authoredPropertyNames(node : TransformNode) : string[]\n{\n const metadata = node.metadata as { gltf ?: { extras ?: Record<string, unknown> } } | null | undefined;\n const extras = metadata?.gltf?.extras;\n\n if(extras === undefined || extras === null)\n {\n return [];\n }\n\n return Object.keys(extras);\n}\n\nfunction warnAboutAuthoredProperties(\n context : NodeSimulationContext,\n name : string,\n id : string,\n mesh : AbstractMesh\n) : void\n{\n let warned = warnedClasses.get(context);\n\n if(warned === undefined)\n {\n warned = new Set();\n warnedClasses.set(context, warned);\n }\n\n if(warned.has(name))\n {\n return;\n }\n\n const findings : string[] = [];\n\n for(const node of [ mesh, ...mesh.getDescendants(false) ])\n {\n if(node instanceof TransformNode)\n {\n const names = authoredPropertyNames(node);\n\n if(names.length > 0)\n {\n findings.push(`\"${ node.name }\" carries ${ names.join(', ') }`);\n }\n }\n }\n\n if(findings.length === 0)\n {\n return;\n }\n\n warned.add(name);\n context.logger.warn(\n `The mesh handed to ${ name } (${ id }) carries authored properties a spawned mesh never reads: `\n + `${ findings.join('; ') }. Compose HavokPhysicsBodyBehavior for a body, or leave the property on the `\n + 'level\\'s own scene.'\n );\n}\n\n// How an entity came by its node, which decides what construction writes onto it and what destruction does with\n// it (design/entities.md#gameentity-and-nodeentity).\ntype NodeAcquisition = 'adopted' | 'claimed' | 'built';\n\ninterface HeldNode\n{\n set : NodeSet;\n acquisition : NodeAcquisition;\n}\n\n// The node is acquired inside GameEntity's constructor, in declareComputedState, and a `#` field cannot hold it:\n// a subclass's private fields are installed on the instance only once `super(...)` returns, which is after every\n// hook the base constructor runs (design/entities.md#computed-state).\nconst heldNodes = new WeakMap<NodeEntity, HeldNode>();\n\nfunction heldNodeOf(entity : NodeEntity) : HeldNode\n{\n const held = heldNodes.get(entity);\n\n if(held === undefined)\n {\n throw new Error(`NodeEntity: entity \"${ entity.id }\" is asked for its node before construction acquires it.`);\n }\n\n return held;\n}\n\nfunction spatialOf(state : object) : NodeEntityState\n{\n return state as NodeEntityState;\n}\n\nfunction findAuthoredNode(nodeContext : NodeSimulationContext, nodeName : string) : TransformNode\n{\n const found = nodeContext.scene.getNodeByName(nodeName);\n\n if(!(found instanceof TransformNode))\n {\n throw new Error(`NodeEntity: no authored node named \"${ nodeName }\" exists in the scene.`);\n }\n\n return found;\n}\n\n// Adoption is decided before pooling, and a class that is both poolable and adopted for one spawn never touches\n// the pool for it (design/authoring.md#pooling-and-destruction). This is a function rather than a method because\n// it runs during `super(...)`, where the entity's own private methods do not exist yet.\nfunction acquireNode(entity : NodeEntity, spatial : NodeEntityState, nodeContext : NodeSimulationContext) : HeldNode\n{\n if(spatial.nodeName !== undefined)\n {\n return {\n set: { node: findAuthoredNode(nodeContext, spatial.nodeName), mesh: undefined },\n acquisition: 'adopted',\n };\n }\n\n if(spatial.position === undefined)\n {\n spatial.position = { x: 0, y: 0, z: 0 };\n }\n\n if(spatial.rotation === undefined)\n {\n spatial.rotation = { x: 0, y: 0, z: 0, w: 1 }; // eslint-disable-line id-length\n }\n\n const entityClass = entity.constructor as typeof NodeEntity;\n\n if(entityClass.poolable)\n {\n const pooled = nodeContext.pool.take(entity.name);\n\n if(pooled !== undefined)\n {\n // The pool held the set hidden, and the entity claiming it shows it again\n // (design/entities.md#pooling).\n pooled.node.setEnabled(true);\n\n return { set: pooled, acquisition: 'claimed' };\n }\n }\n\n return {\n set: { node: new TransformNode(entity.id, nodeContext.scene), mesh: undefined },\n acquisition: 'built',\n };\n}\n\n// The fill-in of the origin and the identity quaternion never reaches an adopted node, which it would drag away\n// from where authoring put it; only what the spawn state supplies is written through the setters\n// (design/authoring.md#position-rotation-and-scale).\nfunction writeSuppliedTransform(spatial : NodeEntityState, suppliedState : object) : void\n{\n const supplied = suppliedState as Partial<NodeEntityState>;\n\n if(supplied.position !== undefined)\n {\n spatial.position = supplied.position;\n }\n\n if(supplied.rotation !== undefined)\n {\n spatial.rotation = supplied.rotation;\n }\n}\n\n// A node built or authored without one carries no rotationQuaternion at all, and its rotation is the euler\n// triple instead. The quaternion those angles already mean takes its place once, so a component of `rotation`\n// has something to write to (design/entities.md#gameentity-and-nodeentity).\nfunction quaternionOf(node : TransformNode) : Quaternion\n{\n if(node.rotationQuaternion === null)\n {\n node.rotationQuaternion = Quaternion.FromEulerVector(node.rotation);\n }\n\n return node.rotationQuaternion;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// The behaviors tuple types the entity's own state and operations, so a subclass that names its tuple reads\n// both with no declaration of its own (design/entities.md#composition).\nexport abstract class NodeEntity<Behaviors extends readonly BehaviorClass[] = readonly BehaviorClass[]>\n extends GameEntity<NodeSimulationContext>\n{\n static readonly poolable : boolean = false;\n static readonly poolSize ?: number;\n static readonly meshConfig ?: MeshConfig;\n static readonly actions ?: readonly Action[];\n\n declare readonly state : DeepReadonly<StateFor<Behaviors>>;\n declare readonly ops : OpsFor<Behaviors>;\n\n // A NodeEntity is created with a NodeSimulationContext, the context that carries the scene its node goes in\n // (design/entities.md#gameentity-and-nodeentity). Declared here so a plain simulation's entity registry, and a\n // direct create with a plain context, are both compile errors (design/entities.md#the-entity-registry).\n static override create<Type extends GameEntityType>(\n this : Type & { behaviors : ValidTuple<Type['behaviors'], Type['prototype']> },\n id : string,\n context : NodeSimulationContext,\n state ?: Partial<StateFor<Type['behaviors']>>,\n name ?: string\n ) : ComposedOf<Type>\n {\n return super.create(id, context, state, name);\n }\n\n static override restore<Type extends GameEntityType>(\n this : Type & { behaviors : ValidTuple<Type['behaviors'], Type['prototype']> },\n id : string,\n context : NodeSimulationContext,\n state : StateFor<Type['behaviors']>,\n name ?: string\n ) : ComposedOf<Type>\n {\n return super.restore(id, context, state, name);\n }\n\n get node() : TransformNode\n {\n return heldNodeOf(this).set.node;\n }\n\n get mesh() : AbstractMesh | undefined\n {\n return heldNodeOf(this).set.mesh;\n }\n\n set mesh(mesh : AbstractMesh | undefined)\n {\n heldNodeOf(this).set.mesh = mesh;\n }\n\n get audio() : AudioManager | undefined\n {\n return this.context.audio;\n }\n\n showCollider(config ?: ColliderDebugConfig, color ?: string) : void\n {\n const colliders = this.context.colliders;\n const nodes = this.#physicsNodes();\n\n if(config === undefined || config === true)\n {\n for(const node of nodes)\n {\n colliders.showColliderForNode(node);\n }\n\n return;\n }\n\n if(config === false)\n {\n return;\n }\n\n if(typeof config === 'string')\n {\n const target = nodes.find((node) => node.name === config);\n\n if(target)\n {\n colliders.showColliderForNode(target, color);\n }\n\n return;\n }\n\n if('color' in config)\n {\n for(const node of nodes)\n {\n colliders.showColliderForNode(node, config.color);\n }\n\n return;\n }\n\n for(const node of nodes)\n {\n const entry = config.nodes[node.name];\n\n if(entry)\n {\n colliders.showColliderForNode(node, entry);\n }\n }\n }\n\n hideCollider(name ?: string) : void\n {\n const colliders = this.context.colliders;\n const nodes = this.#physicsNodes();\n\n if(name === undefined)\n {\n for(const node of nodes)\n {\n colliders.hideColliderForNode(node);\n }\n\n return;\n }\n\n const target = nodes.find((node) => node.name === name);\n\n if(target)\n {\n colliders.hideColliderForNode(target);\n }\n }\n\n // The fields NodeEntity itself declares are on every NodeEntity's state, whatever tuple a subclass types the\n // rest of it by.\n #spatial() : NodeEntityState\n {\n return spatialOf(this.state);\n }\n\n // The node is acquired here, ahead of every hook, so `onStateReady` and `onCreate` both read a position that\n // is already the node's (design/entities.md#computed-state). What is written onto it afterwards is what\n // adoption changes: an authored node keeps its authored transform, and a built or claimed one is placed from\n // the composed state (design/authoring.md#position-rotation-and-scale).\n protected override declareComputedState(suppliedState : object) : void\n {\n const spatial = spatialOf(this.state);\n const held = acquireNode(this, spatial, this.context);\n\n heldNodes.set(this, held);\n\n const node = held.set.node;\n const composed = { position: spatial.position, rotation: spatial.rotation };\n\n quaternionOf(node);\n\n const writePosition = (value : Vector3State) : void =>\n {\n node.position.set(value.x, value.y, value.z);\n };\n\n const writeRotation = (value : QuaternionState) : void =>\n {\n quaternionOf(node).set(value.x, value.y, value.z, value.w);\n };\n\n // Each answers with one view of the node's own vector, so `position.x += 1` moves the node through the\n // same write assigning the whole record makes (design/entities.md#computed-state).\n const position = vectorView(() => node.position, writePosition);\n const rotation = quaternionView(() => quaternionOf(node), writeRotation);\n\n this.computed<NodeEntityState, 'position'>('position', { get: () => position, set: writePosition });\n this.computed<NodeEntityState, 'rotation'>('rotation', { get: () => rotation, set: writeRotation });\n\n if(held.acquisition === 'adopted')\n {\n writeSuppliedTransform(spatial, suppliedState);\n\n return;\n }\n\n spatial.position = composed.position;\n spatial.rotation = composed.rotation;\n }\n\n // this.node itself counts, alongside its descendants (design/debugging.md#per-entity-collider-visualization).\n #physicsNodes() : TransformNode[]\n {\n const nodes : TransformNode[] = [];\n\n if(this.node.physicsBody)\n {\n nodes.push(this.node);\n }\n\n for(const child of this.node.getChildTransformNodes(false))\n {\n if(child.physicsBody)\n {\n nodes.push(child);\n }\n }\n\n return nodes;\n }\n\n protected constructor(\n id : string,\n context : NodeSimulationContext,\n providedState : object,\n runCreateHooks : boolean,\n name ?: string\n )\n {\n super(id, context, providedState, runCreateHooks, name);\n\n const held = heldNodeOf(this);\n const set = held.set;\n\n // Parented before any onMeshLoaded runs, so a hook that reads where the node stands in the scene reads it\n // under the parent the state names (design/entities.md#the-parent-child-hierarchy).\n const spatial = this.#spatial();\n\n if(spatial.parentID !== undefined)\n {\n const parentEntity = context.getEntity(spatial.parentID);\n\n if(parentEntity instanceof NodeEntity)\n {\n this.node.parent = parentEntity.node;\n }\n }\n\n if(held.acquisition === 'built')\n {\n pendingMeshLoads.set(set, () => { this.#runMeshLoadedHooks(); });\n\n // The catch sits on the load alone, never around the notify below: a throwing onMeshLoaded is not\n // caught, and wrapping the whole chain would swallow it (design/behaviors.md#onmeshloaded).\n context.loadMesh(context.scene, this.name, this.state as StateBag)\n .catch((error : unknown) =>\n {\n context.logger.error(\n `Loading the mesh for ${ this.name } (${ this.id }) failed. The entity keeps its node `\n + 'and carries no mesh.',\n error\n );\n\n return undefined;\n })\n .then((mesh) =>\n {\n if(mesh !== undefined)\n {\n mesh.parent = set.node;\n warnAboutAuthoredProperties(context, this.name, this.id, mesh);\n }\n\n set.mesh = mesh;\n\n const notify = pendingMeshLoads.get(set);\n pendingMeshLoads.delete(set);\n notify?.();\n });\n }\n else if(pendingMeshLoads.has(set))\n {\n pendingMeshLoads.set(set, () => { this.#runMeshLoadedHooks(); });\n }\n else\n {\n this.#settleMeshLoadedNow();\n }\n }\n\n // Runs onMeshLoaded synchronously, inside construction -- adoption or a pooled claim already settled.\n // A throwing hook here fails the construction it runs inside, the same as a throwing onCreate does\n // (design/behaviors.md#onmeshloaded): super.destroy() only unsubscribes, bypassing this class's own\n // destroy(), which assumes a fully-built entity.\n #settleMeshLoadedNow() : void\n {\n try\n {\n this.#runMeshLoadedHooks();\n }\n catch(error)\n {\n super.destroy();\n throw error;\n }\n }\n\n // Collider visualization runs after every behavior's onMeshLoaded, so a body a behavior builds in that hook is\n // what it shows (design/debugging.md#config-driven-visualization).\n #runMeshLoadedHooks() : void\n {\n for(const instance of this.attachedBehaviors())\n {\n (instance as NodeBehaviorHooks).onMeshLoaded?.();\n }\n\n const spatial = this.#spatial();\n\n if(spatial.debugCollider !== undefined)\n {\n this.showCollider(spatial.debugCollider);\n }\n }\n\n #runDestroyHooks() : void\n {\n for(const instance of this.attachedBehaviors())\n {\n (instance as NodeBehaviorHooks).onDestroy?.();\n }\n }\n\n override destroy() : void\n {\n this.hideCollider();\n\n // Every computation comes off while the node, and any body over it, are still there to be asked\n // (design/entities.md#computed-state).\n this.removeComputedState();\n\n this.#runDestroyHooks();\n\n super.destroy();\n\n const held = heldNodeOf(this);\n\n // A set this entity was still waiting to hear from stays pending for whoever claims it next\n // (design/entities.md#pooling); this entity itself just stops being who that is.\n if(pendingMeshLoads.has(held.set))\n {\n pendingMeshLoads.set(held.set, () => { /* this entity is gone; nothing left to notify */ });\n }\n\n // The node is the level's content, not a resource this entity borrowed -- destroying the entity never\n // disposes, hides, or pools it (design/authoring.md#pooling-and-destruction).\n if(held.acquisition === 'adopted')\n {\n return;\n }\n\n const entityClass = this.constructor as typeof NodeEntity;\n\n if(entityClass.poolable)\n {\n this.context.pool.give(this.name, held.set, entityClass.poolSize);\n }\n else\n {\n held.set.node.dispose();\n }\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Entity Picking\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { Vector3 } from '@babylonjs/core';\n\nimport type { AbstractMesh, Node, PickingInfo } from '@babylonjs/core';\nimport type { GameEntity, GameEntityState } from '@skewedaspect/sage-core';\n\nimport { NodeEntity } from './nodeEntity.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport interface EntityPickFilter\n{\n tags ?: readonly string[];\n}\n\nexport interface EntityPickResult\n{\n entity : NodeEntity;\n mesh : AbstractMesh;\n point : Vector3;\n distance : number;\n normal : Vector3;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// Climbs the mesh's own ancestor chain -- itself, then its parent, and so on -- against the simulation's\n// current entity list, rather than a maintained node-to-entity lookup: nothing here needs invalidating on\n// spawn, despawn, or a pooled node changing hands.\nexport function findEntityForMesh(entities : readonly GameEntity[], mesh : AbstractMesh) : NodeEntity | undefined\n{\n const nodeEntities : NodeEntity[] = [];\n\n for(const entity of entities)\n {\n if(entity instanceof NodeEntity)\n {\n nodeEntities.push(entity);\n }\n }\n\n let current : Node | null = mesh;\n\n while(current !== null)\n {\n for(const entity of nodeEntities)\n {\n if(entity.node === current)\n {\n return entity;\n }\n }\n\n current = current.parent;\n }\n\n return undefined;\n}\n\nfunction passesFilter(entity : NodeEntity, filter : EntityPickFilter | undefined) : boolean\n{\n if(filter?.tags === undefined)\n {\n return true;\n }\n\n const tags = (entity.state as GameEntityState).tags;\n\n for(const tag of filter.tags)\n {\n if(!tags.includes(tag))\n {\n return false;\n }\n }\n\n return true;\n}\n\nexport function toEntityPickResult(\n entities : readonly GameEntity[],\n info : PickingInfo | null,\n filter ?: EntityPickFilter\n) : EntityPickResult | undefined\n{\n if(info === null || !info.hit || info.pickedMesh === null || info.pickedPoint === null)\n {\n return undefined;\n }\n\n const entity = findEntityForMesh(entities, info.pickedMesh);\n\n if(entity === undefined || !passesFilter(entity, filter))\n {\n return undefined;\n }\n\n const normal = info.getNormal(true, true);\n\n return {\n entity,\n mesh: info.pickedMesh,\n point: info.pickedPoint,\n distance: info.distance,\n normal: normal ?? Vector3.Zero(),\n };\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Console Logger Backend\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { LogLevel, LoggingBackend } from '../../interfaces/logger.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\ntype ColorLevel = LogLevel | 'timer';\n\n/**\n * A logging backend that logs to the console.\n */\nexport class ConsoleBackend implements LoggingBackend\n{\n private timers : Map<string, number>;\n\n constructor()\n {\n this.timers = new Map();\n }\n\n /**\n * Get the CSS style for a specific log level\n */\n private getStyleForLevel(level : ColorLevel) : string\n {\n // CSS styles for browser console\n const styles : Record<ColorLevel, string> = {\n trace: 'color: #999999', // Dim gray\n debug: 'color: #00AAAA', // Cyan\n info: 'color: #00AA00', // Green\n warn: 'color: #AAAA00', // Yellow\n error: 'color: #AA0000', // Red\n timer: 'color: #AA00AA', // Magenta\n none: 'color: inherit', // Default\n };\n\n return styles[level] || styles.none;\n }\n\n /**\n * Format a log message with category, timestamp and log level\n */\n private formatMessage(category : string, level : ColorLevel) : [string, string, string, string]\n {\n // Format time as HH:MM:SS AM/PM\n const now = new Date();\n const hours = now.getHours() % 12 || 12; // Convert 0 to 12 for 12 AM\n const minutes = now.getMinutes().toString()\n .padStart(2, '0');\n const seconds = now.getSeconds().toString()\n .padStart(2, '0');\n const ampm = now.getHours() >= 12 ? 'PM' : 'AM';\n const timestamp = `[${ hours }:${ minutes }:${ seconds } ${ ampm }]`;\n\n const upperLevel = level.toUpperCase();\n\n // Return format strings and styling that will be used with console methods\n return [\n `${ timestamp } %c${ upperLevel }%c (${ category }): `, // Format string with placeholders\n this.getStyleForLevel(level), // Level style\n '', // Default style\n '', // Reset style\n ];\n }\n\n trace(category : string, message : string, ...args : unknown[]) : void\n {\n const [ format, defaultStyle, levelStyle, resetStyle ] = this.formatMessage(category, 'trace');\n console.debug(format, defaultStyle, levelStyle, resetStyle, message, ...args);\n }\n\n debug(category : string, message : string, ...args : unknown[]) : void\n {\n const [ format, defaultStyle, levelStyle, resetStyle ] = this.formatMessage(category, 'debug');\n console.debug(format, defaultStyle, levelStyle, resetStyle, message, ...args);\n }\n\n info(category : string, message : string, ...args : unknown[]) : void\n {\n const [ format, defaultStyle, levelStyle, resetStyle ] = this.formatMessage(category, 'info');\n console.info(format, defaultStyle, levelStyle, resetStyle, message, ...args);\n }\n\n warn(category : string, message : string, ...args : unknown[]) : void\n {\n const [ format, defaultStyle, levelStyle, resetStyle ] = this.formatMessage(category, 'warn');\n console.warn(format, defaultStyle, levelStyle, resetStyle, message, ...args);\n }\n\n error(category : string, message : string, ...args : unknown[]) : void\n {\n const [ format, defaultStyle, levelStyle, resetStyle ] = this.formatMessage(category, 'error');\n console.error(format, defaultStyle, levelStyle, resetStyle, message, ...args);\n }\n\n /**\n * Start a timer with the specified label.\n */\n time(category : string, label : string) : void\n {\n const timerKey = `${ category }:${ label }`;\n this.timers.set(timerKey, performance.now());\n }\n\n /**\n * End a timer and log the elapsed time.\n */\n timeEnd(category : string, label : string) : void\n {\n const timerKey = `${ category }:${ label }`;\n const startTime = this.timers.get(timerKey);\n\n if(startTime !== undefined)\n {\n const duration = performance.now() - startTime;\n this.timers.delete(timerKey);\n\n const [ format, defaultStyle, levelStyle, resetStyle ] = this.formatMessage(category, 'timer');\n console.info(\n `${ format } Timer '${ label }' completed in ${ duration.toFixed(2) }ms`,\n defaultStyle,\n levelStyle,\n resetStyle\n );\n }\n else\n {\n console.warn(`[${ category }]: Timer '${ label }' does not exist`);\n }\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Null Logger Backend\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { LoggingBackend } from '../../interfaces/logger.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * A logging backend that does nothing (discards all logs).\n * Useful for production environments or when you want to completely disable logging.\n */\nexport class NullBackend implements LoggingBackend\n{\n trace(_category : string, _message : string, ..._args : unknown[]) : void\n {\n // Do nothing\n }\n\n debug(_category : string, _message : string, ..._args : unknown[]) : void\n {\n // Do nothing\n }\n\n info(_category : string, _message : string, ..._args : unknown[]) : void\n {\n // Do nothing\n }\n\n warn(_category : string, _message : string, ..._args : unknown[]) : void\n {\n // Do nothing\n }\n\n error(_category : string, _message : string, ..._args : unknown[]) : void\n {\n // Do nothing\n }\n\n time(_category : string, _label : string) : void\n {\n // Do nothing\n }\n\n timeEnd(_category : string, _label : string) : void\n {\n // Do nothing\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Logger Utility\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type {\n LogLevel,\n LoggerInterface,\n LoggingBackend,\n} from '../interfaces/logger.ts';\n\nimport { ConsoleBackend } from '../classes/loggers/consoleBackend.ts';\nimport { NullBackend } from '../classes/loggers/nullBackend.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Implementation of Logger interface.\n * Each instance is bound to a specific category and backend.\n */\nexport class SAGELogger implements LoggerInterface\n{\n private category : string;\n private backend : LoggingBackend;\n private minLevel : LogLevel;\n\n constructor(category : string, minLevel : LogLevel = 'none', backend : LoggingBackend = new NullBackend())\n {\n this.category = category;\n this.backend = backend;\n this.minLevel = minLevel;\n }\n\n /**\n * Update the logger's backend and minimum level\n */\n public updateSettings(backend : LoggingBackend, minLevel : LogLevel) : void\n {\n this.backend = backend;\n this.minLevel = minLevel;\n }\n\n trace(message : string, ...args : unknown[]) : void\n {\n if(this.shouldLog('trace'))\n {\n this.backend.trace(this.category, message, ...args);\n }\n }\n\n debug(message : string, ...args : unknown[]) : void\n {\n if(this.shouldLog('debug'))\n {\n this.backend.debug(this.category, message, ...args);\n }\n }\n\n info(message : string, ...args : unknown[]) : void\n {\n if(this.shouldLog('info'))\n {\n this.backend.info(this.category, message, ...args);\n }\n }\n\n warn(message : string, ...args : unknown[]) : void\n {\n if(this.shouldLog('warn'))\n {\n this.backend.warn(this.category, message, ...args);\n }\n }\n\n error(message : string, ...args : unknown[]) : void\n {\n if(this.shouldLog('error'))\n {\n this.backend.error(this.category, message, ...args);\n }\n }\n\n time(label : string) : void\n {\n if(this.minLevel !== 'none')\n {\n this.backend.time(this.category, label);\n }\n }\n\n timeEnd(label : string) : void\n {\n if(this.minLevel !== 'none')\n {\n this.backend.timeEnd(this.category, label);\n }\n }\n\n /**\n * Determine if a message at the given level should be logged based on the current minimum level.\n */\n private shouldLog(level : LogLevel) : boolean\n {\n if(this.minLevel === 'none')\n {\n return false;\n }\n\n switch (this.minLevel)\n {\n case 'trace':\n return true;\n case 'debug':\n return level !== 'trace';\n case 'info':\n return level === 'info' || level === 'warn' || level === 'error';\n case 'warn':\n return level === 'warn' || level === 'error';\n case 'error':\n return level === 'error';\n default:\n return false;\n }\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * A utility for creating loggers and managing logging configuration.\n */\nexport class LoggingUtility\n{\n private backend : LoggingBackend;\n private level : LogLevel;\n private loggers : Map<string, LoggerInterface>;\n\n /**\n * Create a new LoggingUtility.\n *\n * @param level - The minimum logging level (defaults to INFO)\n * @param backend - The logging backend to use (defaults to ConsoleBackend)\n */\n constructor(level : LogLevel = 'debug', backend : LoggingBackend = new ConsoleBackend())\n {\n this.backend = backend;\n this.level = level;\n this.loggers = new Map();\n }\n\n /**\n * Set the logging backend. Updates all existing loggers to use the new backend.\n */\n public setBackend(backend : LoggingBackend) : void\n {\n this.backend = backend;\n\n // Update all existing loggers to use the new backend\n this.loggers.forEach((logger) =>\n {\n if(logger instanceof SAGELogger)\n {\n logger.updateSettings(this.backend, this.level);\n }\n });\n }\n\n /**\n * Set the minimum logging level. Updates all existing loggers.\n */\n public setLevel(level : LogLevel) : void\n {\n this.level = level;\n\n // Update all existing loggers to use the new level\n this.loggers.forEach((logger) =>\n {\n if(logger instanceof SAGELogger)\n {\n logger.updateSettings(this.backend, this.level);\n }\n });\n }\n\n /**\n * Get the current minimum logging level.\n */\n public getLevel() : LogLevel\n {\n return this.level;\n }\n\n /**\n * Get or create a logger for the specified category. Returns an existing logger if one was already created.\n *\n * @param category - Typically the class or module name\n */\n public getLogger(category : string) : LoggerInterface\n {\n let logger = this.loggers.get(category);\n if(!logger)\n {\n logger = new SAGELogger(category, this.level, this.backend);\n this.loggers.set(category, logger);\n }\n\n return logger;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport { ConsoleBackend } from '../classes/loggers/consoleBackend.ts';\nexport { NullBackend } from '../classes/loggers/nullBackend.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// NodeSimulation\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { TransformNode } from '@babylonjs/core';\nimport { GameSimulation } from '@skewedaspect/sage-core';\n\nimport type {\n GameEntityRegistry, GameSimulationOptions, GameSimulationSnapshot, StateBag, ValidEntityRegistry,\n} from '@skewedaspect/sage-core';\nimport type { AbstractMesh, Camera, Ray, Scene } from '@babylonjs/core';\n\nimport { findEntityForMesh, toEntityPickResult } from './entityPicking.ts';\nimport type { EntityPickFilter, EntityPickResult } from './entityPicking.ts';\nimport { NodeEntity } from './nodeEntity.ts';\nimport type { NodePool } from './nodePool.ts';\nimport type { MeshLoader, NodeSimulationContext } from './nodeSimulationContext.ts';\nimport type { AudioManager } from '../managers/audio.ts';\nimport type { ColliderDebugManager } from '../managers/colliderDebug.ts';\nimport { SAGELogger } from '../utils/logger.ts';\nimport type { LoggerInterface } from '../interfaces/logger.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport type NodeEntityRegistry = GameEntityRegistry<NodeSimulationContext>;\n\nexport interface NodeSimulationOptions<Registry extends NodeEntityRegistry = NodeEntityRegistry>\n extends GameSimulationOptions<Registry>\n{\n scene : Scene;\n loadMesh : MeshLoader;\n pool : NodePool;\n audio ?: AudioManager;\n colliders : ColliderDebugManager;\n logger ?: LoggerInterface;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport class NodeSimulation<Registry extends NodeEntityRegistry = NodeEntityRegistry>\n extends GameSimulation<Registry, NodeSimulationContext>\n implements NodeSimulationContext\n{\n readonly scene : Scene;\n readonly pool : NodePool;\n readonly loadMesh : MeshLoader;\n readonly audio : AudioManager | undefined;\n readonly colliders : ColliderDebugManager;\n readonly logger : LoggerInterface;\n\n readonly #entityClasses : Registry;\n\n constructor(options : NodeSimulationOptions<Registry> & { entityClasses : ValidEntityRegistry<Registry> })\n {\n super(options);\n\n this.scene = options.scene;\n this.pool = options.pool;\n this.loadMesh = options.loadMesh;\n this.audio = options.audio;\n this.colliders = options.colliders;\n this.logger = options.logger ?? new SAGELogger('NodeSimulation');\n this.#entityClasses = options.entityClasses;\n\n // Babylon advances physics from scene.render(), by the render frame's delta. This simulation\n // advances it instead, once per fixed step, so the two are not on separate clocks\n // (design/physics.md#physics-runs-on-the-fixed-step). The flag reads as \"does this scene have\n // physics\" and is not: it gates the per-frame advance only, and getPhysicsEngine() keeps answering.\n this.scene.physicsEnabled = false;\n }\n\n /**\n * Advances the scene's physics engine by one fixed step.\n *\n * A scene with no physics engine has nothing to advance and does nothing, which is what a game that\n * never asked for physics gets (design/physics.md#physics-is-optional).\n */\n protected override advanceFixedStep(duration : number) : void\n {\n this.scene.getPhysicsEngine()?._step(duration);\n }\n\n async prewarm(name : string, count : number, state : StateBag = {}) : Promise<void>\n {\n const entityClass = this.#entityClasses[name] as typeof NodeEntity | undefined;\n\n const meshes = await Promise.all(\n Array.from({ length: count }, () => this.loadMesh(this.scene, name, state))\n );\n\n for(const mesh of meshes)\n {\n const node = new TransformNode(name, this.scene);\n\n if(mesh !== undefined)\n {\n mesh.parent = node;\n }\n\n this.pool.give(name, { node, mesh }, entityClass?.poolSize);\n }\n }\n\n pickEntityWithRay(ray : Ray, filter ?: EntityPickFilter) : EntityPickResult | undefined\n {\n return toEntityPickResult(this.getEntitiesOfClass(NodeEntity), this.scene.pickWithRay(ray), filter);\n }\n\n pickEntity(x : number, y : number, filter ?: EntityPickFilter) : EntityPickResult | undefined\n {\n return toEntityPickResult(this.getEntitiesOfClass(NodeEntity), this.scene.pick(x, y), filter);\n }\n\n pickEntityForward(camera : Camera, range : number, filter ?: EntityPickFilter) : EntityPickResult | undefined\n {\n return this.pickEntityWithRay(camera.getForwardRay(range), filter);\n }\n\n getEntityForMesh(mesh : AbstractMesh) : NodeEntity | undefined\n {\n return findEntityForMesh(this.getEntitiesOfClass(NodeEntity), mesh);\n }\n\n // Every class this simulation's registry holds is a NodeEntity class, since the registry's context is this\n // simulation's own, so what spawnByName answers with is a NodeEntity (design/entities.md#the-entity-registry).\n // The replacement is what buildReplacementSimulation below builds, so it is a NodeSimulation; the base signature\n // cannot say so, and this one does (design/simulation.md#snapshot-and-restore).\n override buildFromSnapshot(snapshot : GameSimulationSnapshot) : NodeSimulation<Registry>\n {\n return super.buildFromSnapshot(snapshot) as NodeSimulation<Registry>;\n }\n\n protected override buildReplacementSimulation(options : GameSimulationOptions<Registry>) : NodeSimulation<Registry>\n {\n return new NodeSimulation<Registry>({\n ...(options as GameSimulationOptions<Registry> & { entityClasses : ValidEntityRegistry<Registry> }),\n scene: this.scene,\n pool: this.pool,\n loadMesh: this.loadMesh,\n audio: this.audio,\n colliders: this.colliders,\n logger: this.logger,\n });\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Tuning Constants\n//----------------------------------------------------------------------------------------------------------------------\n\nexport const poolSizeLimit = 8;\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// NodePool\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { poolSizeLimit } from './constants.ts';\n\nimport type { NodeSet } from './nodeSimulationContext.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// Node and mesh sets, keyed by entity name, capped per class at `poolSizeLimit`.\nexport class NodePool\n{\n readonly #sets = new Map<string, NodeSet[]>();\n\n take(name : string) : NodeSet | undefined\n {\n const sets = this.#sets.get(name);\n\n if(sets === undefined)\n {\n return undefined;\n }\n\n return sets.pop();\n }\n\n give(name : string, set : NodeSet, limit : number = poolSizeLimit) : void\n {\n set.node.parent = null;\n set.node.setEnabled(false);\n\n let sets = this.#sets.get(name);\n\n if(sets === undefined)\n {\n sets = [];\n this.#sets.set(name, sets);\n }\n\n if(sets.length >= limit)\n {\n set.node.dispose();\n return;\n }\n\n sets.push(set);\n }\n\n clear(name : string) : void\n {\n const sets = this.#sets.get(name);\n\n if(sets === undefined)\n {\n return;\n }\n\n for(const set of sets)\n {\n set.node.dispose();\n }\n\n this.#sets.delete(name);\n }\n\n clearAll() : void\n {\n for(const name of [ ...this.#sets.keys() ])\n {\n this.clear(name);\n }\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Audio Engine\n//----------------------------------------------------------------------------------------------------------------------\n\nimport {\n type AudioBus,\n type AudioEngineV2,\n CreateAudioEngineAsync,\n type IStaticSoundOptions,\n type MainAudioBus,\n type PrimaryAudioBus,\n type StaticSound,\n} from '@babylonjs/core';\n\n// Interfaces\nimport type { Disposable } from '../interfaces/lifecycle.ts';\nimport type { LoggerInterface } from '../interfaces/logger.ts';\n\n// Utils\nimport type { LoggingUtility } from '../utils/logger.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport class AudioEngine implements Disposable\n{\n #engine : AudioEngineV2 | null = null;\n #mainBus : MainAudioBus | null = null;\n #buses = new Map<string, AudioBus>();\n #log : LoggerInterface;\n\n constructor(logger : LoggingUtility)\n {\n this.#log = logger.getLogger('AudioEngine');\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Lifecycle\n //------------------------------------------------------------------------------------------------------------------\n\n async initialize() : Promise<void>\n {\n this.#log.info('Initializing audio engine...');\n\n this.#engine = await CreateAudioEngineAsync();\n this.#mainBus = await this.#engine.createMainBusAsync('main');\n\n this.#log.info('Audio engine initialized.');\n }\n\n async dispose() : Promise<void>\n {\n this.#log.info('Tearing down audio engine...');\n\n this.#buses.clear();\n this.#mainBus = null;\n\n if(this.#engine)\n {\n this.#engine.dispose();\n this.#engine = null;\n }\n\n this.#log.info('Audio engine torn down.');\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Bus Management\n //------------------------------------------------------------------------------------------------------------------\n\n async createBus(name : string) : Promise<AudioBus>\n {\n if(!this.#engine || !this.#mainBus)\n {\n throw new Error('AudioEngine not initialized');\n }\n\n this.#log.debug(`Creating audio bus: ${ name }`);\n const bus = await this.#engine.createBusAsync(name, { outBus: this.#mainBus });\n this.#buses.set(name, bus);\n return bus;\n }\n\n getBus(name : string) : AudioBus | undefined\n {\n return this.#buses.get(name);\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Sound Creation\n //------------------------------------------------------------------------------------------------------------------\n\n async createSound(\n name : string,\n url : string,\n bus ?: AudioBus,\n options ?: Partial<IStaticSoundOptions>\n ) : Promise<StaticSound>\n {\n if(!this.#engine)\n {\n throw new Error('AudioEngine not initialized');\n }\n\n const outBus : PrimaryAudioBus | undefined = bus ?? this.#mainBus ?? undefined;\n\n const soundOptions : Partial<IStaticSoundOptions> = { ...options };\n\n if(outBus !== undefined)\n {\n soundOptions.outBus = outBus;\n }\n\n return this.#engine.createSoundAsync(name, url, soundOptions);\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Volume\n //------------------------------------------------------------------------------------------------------------------\n\n setMasterVolume(volume : number) : void\n {\n if(!this.#engine)\n {\n throw new Error('AudioEngine not initialized');\n }\n\n this.#engine.volume = volume;\n }\n\n getMasterVolume() : number\n {\n if(!this.#engine)\n {\n return 1;\n }\n\n return this.#engine.volume;\n }\n\n setBusVolume(bus : AudioBus, volume : number) : void\n {\n bus.volume = volume;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Physics Utility\n//----------------------------------------------------------------------------------------------------------------------\n\nimport HavokPhysics, { type HavokPhysicsWithBindings } from '@babylonjs/havok';\nimport { HavokPlugin, Vector3 } from '@babylonjs/core';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Earth's gravity, downward, in the units the rest of the scene is authored in. There is one default\n * gravity, not one per call site (design/physics.md#the-physics-engine).\n *\n * A fresh vector every call: Babylon keeps the one it is handed by reference, and a game that wants gravity\n * to change while a level runs writes it onto the running engine.\n */\nexport function defaultGravity() : Vector3\n{\n return new Vector3(0, -9.81, 0);\n}\n\n/**\n * Initializes the Havok WASM module and returns the raw bindings.\n *\n * Each BabylonJS scene needs its own HavokPlugin instance (because scene.dispose() disposes the\n * plugin attached to it). The raw WASM module can be shared across plugins safely.\n *\n * @param wasmBinary - Pre-loaded Havok WASM. Left unset, Havok fetches its own over HTTP, which\n * only works in a browser.\n */\nexport async function initHavok(wasmBinary ?: BufferSource) : Promise<HavokPhysicsWithBindings>\n{\n if(wasmBinary === undefined)\n {\n return await HavokPhysics();\n }\n\n // Emscripten declares the slot as ArrayBuffer, but hands it straight to WebAssembly, which\n // takes any BufferSource. A node Buffer is the common case and is not an ArrayBuffer.\n return await HavokPhysics({ wasmBinary: wasmBinary as ArrayBuffer });\n}\n\n/**\n * Creates a new HavokPlugin from an existing WASM instance.\n */\nexport function createHavokPlugin(havok : HavokPhysicsWithBindings) : HavokPlugin\n{\n return new HavokPlugin(true, havok);\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Scene Engine\n//----------------------------------------------------------------------------------------------------------------------\n\nimport {\n AbstractEngine,\n AbstractMesh,\n AssetContainer,\n DirectionalLight,\n HavokPlugin,\n type HavokPluginParameters,\n HemisphericLight,\n ImportMeshAsync,\n Light,\n LoadAssetContainerAsync,\n Mesh,\n MeshBuilder,\n PointLight,\n RectAreaLight,\n Scene,\n SpotLight,\n Vector3,\n} from '@babylonjs/core';\n\n// Registering at import is what lets loadModel and importMeshes read any format Babylon ships a loader for.\n// Each loader's own code is fetched the first time a file of its kind is read.\nimport { registerBuiltInLoaders } from '@babylonjs/loaders/dynamic';\nregisterBuiltInLoaders();\nimport type { HavokPhysicsWithBindings } from '@babylonjs/havok';\n\n// Interfaces\nimport type { Disposable } from '../interfaces/lifecycle.ts';\nimport type { LoggerInterface } from '../interfaces/logger.ts';\n\n// Utils\nimport { type LoggingUtility } from '../utils/logger.ts';\nimport { defaultGravity, initHavok } from '../utils/physics.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport class SceneEngine implements Disposable\n{\n #engine : AbstractEngine;\n\n // Undefined until a scene asks for physics. Loading is megabytes of WebAssembly, and a game with\n // nothing simulated has no reason to pay for it (design/physics.md#physics-is-optional).\n #havok : HavokPhysicsWithBindings | undefined;\n\n // In a browser Havok fetches its own bytes; on NullEngine there is nothing to fetch from, so a game\n // booting headless hands them in and whatever asks for physics first uses them.\n readonly #havokBinary : BufferSource | undefined;\n\n // Holding the promise rather than the module is what makes two simultaneous asks load it once.\n #havokLoading : Promise<HavokPhysicsWithBindings> | undefined;\n\n #log : LoggerInterface;\n\n constructor(\n engine : AbstractEngine,\n logger : LoggingUtility,\n havokBinary ?: BufferSource\n )\n {\n this.#engine = engine;\n this.#havokBinary = havokBinary;\n this.#log = logger.getLogger('SceneEngine');\n }\n\n /** Whether the Havok module has been loaded, which nothing does until a scene asks for physics. */\n public get $havokLoaded() : boolean\n {\n return this.#havok !== undefined;\n }\n\n /** The one loaded module, for a test asserting every scene's plugin was built over the same one. */\n public get $havokModule() : HavokPhysicsWithBindings | undefined\n {\n return this.#havok;\n }\n\n /**\n * Creates an empty scene on the render engine. It belongs to whoever asked for it.\n */\n public createScene() : Scene\n {\n return new Scene(this.#engine);\n }\n\n public async enablePhysics(\n scene : Scene,\n gravityVector : Vector3 = defaultGravity(),\n floatingOriginWorldRadius ?: number\n ) : Promise<void>\n {\n const havok = await this.#loadHavok();\n\n this.#log.debug(\n `Enabling physics with gravity (${ gravityVector.x }, ${ gravityVector.y }, ${ gravityVector.z })...`\n );\n\n let pluginOptions : HavokPluginParameters | undefined;\n\n if(floatingOriginWorldRadius !== undefined)\n {\n pluginOptions = { floatingOriginWorldRadius };\n }\n\n // Each scene gets its own HavokPlugin so that scene.dispose() doesn't corrupt the shared WASM module.\n const plugin = new HavokPlugin(true, havok, pluginOptions);\n scene.enablePhysics(gravityVector, plugin);\n }\n\n /**\n * The first ask starts the load and every ask after it joins that one. A load that fails is not\n * remembered, so the ask after a failure starts a fresh one (design/physics.md#physics-is-optional).\n */\n async #loadHavok() : Promise<HavokPhysicsWithBindings>\n {\n if(this.#havokLoading === undefined)\n {\n this.#havokLoading = initHavok(this.#havokBinary);\n }\n\n const loading = this.#havokLoading;\n\n try\n {\n const havok = await loading;\n\n // A dispose while this load was in flight forgot it. The scene that asked still gets its plugin\n // over what loaded, but a disposed engine remembers nothing.\n if(this.#havokLoading === loading)\n {\n this.#havok = havok;\n }\n\n return havok;\n }\n catch(error)\n {\n if(this.#havokLoading === loading)\n {\n this.#havokLoading = undefined;\n }\n\n throw error;\n }\n }\n\n // Lighting Utilities\n /**\n * Creates a hemispheric light (ambient lighting)\n */\n public createHemisphericLight(\n name : string,\n direction : Vector3,\n scene : Scene,\n intensity = 0.7\n ) : HemisphericLight\n {\n const light = new HemisphericLight(name, direction, scene);\n light.intensity = intensity;\n return light;\n }\n\n /**\n * Creates a directional light (sun-like lighting)\n */\n public createDirectionalLight(\n name : string,\n direction : Vector3,\n scene : Scene,\n intensity = 1.0\n ) : DirectionalLight\n {\n const light = new DirectionalLight(name, direction, scene);\n light.intensity = intensity;\n return light;\n }\n\n /**\n * Creates a point light (bulb-like lighting)\n */\n public createPointLight(\n name : string,\n position : Vector3,\n scene : Scene,\n intensity = 1.0\n ) : PointLight\n {\n const light = new PointLight(name, position, scene);\n light.intensity = intensity;\n return light;\n }\n\n /**\n * Creates a spotlight (flashlight-like lighting)\n * @param angle - Cone angle in radians\n * @param exponent - Controls light falloff from center to edge of cone\n */\n public createSpotLight(\n name : string,\n position : Vector3,\n direction : Vector3,\n angle : number,\n exponent : number,\n scene : Scene,\n intensity = 1.0\n ) : SpotLight\n {\n const light = new SpotLight(name, position, direction, angle, exponent, scene);\n light.intensity = intensity;\n return light;\n }\n\n /**\n * Creates a rectangular area light (emits from a flat rectangle in the -Z direction)\n */\n public createRectAreaLight(\n name : string,\n position : Vector3,\n width : number,\n height : number,\n scene : Scene,\n intensity = 1.0\n ) : RectAreaLight\n {\n const light = new RectAreaLight(name, position, width, height, scene);\n light.intensity = intensity;\n return light;\n }\n\n // Mesh Creation Utilities\n /**\n * Creates a sphere mesh. Defaults: diameter 1, 32 segments.\n */\n public createSphere(\n name : string,\n options : { diameter ?: number; segments ?: number } = {},\n scene : Scene\n ) : Mesh\n {\n const defaultOptions = { diameter: 1, segments: 32 };\n return MeshBuilder.CreateSphere(name, { ...defaultOptions, ...options }, scene);\n }\n\n /**\n * Creates a box mesh. Default size: 1.\n */\n public createBox(\n name : string,\n options : { size ?: number; width ?: number; height ?: number; depth ?: number } = {},\n scene : Scene\n ) : Mesh\n {\n const defaultOptions = { size: 1 };\n return MeshBuilder.CreateBox(name, { ...defaultOptions, ...options }, scene);\n }\n\n /**\n * Creates a ground mesh. Defaults: 6x6 with 2 subdivisions.\n */\n public createGround(\n name : string,\n options : { width ?: number; height ?: number; subdivisions ?: number } = {},\n scene : Scene\n ) : Mesh\n {\n const defaultOptions = { width: 6, height: 6, subdivisions: 2 };\n return MeshBuilder.CreateGround(name, { ...defaultOptions, ...options }, scene);\n }\n\n /**\n * Creates a cylinder mesh. Defaults: height 2, diameter 1 (top and bottom).\n */\n public createCylinder(\n name : string,\n options : { height ?: number; diameterTop ?: number; diameterBottom ?: number } = {},\n scene : Scene\n ) : Mesh\n {\n const defaultOptions = { height: 2, diameterTop: 1, diameterBottom: 1 };\n return MeshBuilder.CreateCylinder(name, { ...defaultOptions, ...options }, scene);\n }\n\n // Model Loading Utilities\n /**\n * Loads a 3D model/asset into an AssetContainer without adding it to the scene automatically.\n */\n public async loadModel(\n sceneFilename : string,\n scene : Scene\n ) : Promise<AssetContainer>\n {\n this.#log.debug(`Loading model: ${ sceneFilename }`);\n\n try\n {\n const result = await LoadAssetContainerAsync(sceneFilename, scene);\n this.#log.debug(`Model loaded successfully: ${ sceneFilename }`);\n return result;\n }\n catch (error)\n {\n this.#log.error(`Failed to load model: ${ sceneFilename }`, error);\n throw error;\n }\n }\n\n /**\n * Imports meshes from a file directly into the scene.\n * @param meshNames - Specific meshes to import; empty array imports all\n */\n public async importMeshes(\n meshNames : string[],\n sceneFilename : string,\n scene : Scene\n ) : Promise<{\n meshes : AbstractMesh[];\n particleSystems : unknown[];\n skeletons : unknown[];\n animationGroups : unknown[];\n transformNodes : unknown[];\n geometries : unknown[];\n lights : Light[];\n }>\n {\n this.#log.debug(`Importing meshes from: ${ sceneFilename }`);\n\n // To Babylon an empty meshNames means \"import nothing\"; leaving the option out is what imports all.\n let options : { meshNames : string[] } | undefined;\n let imported = 'all';\n\n if(meshNames.length > 0)\n {\n options = { meshNames };\n imported = meshNames.join(', ');\n }\n\n try\n {\n const result = await ImportMeshAsync(sceneFilename, scene, options);\n this.#log.debug(`Meshes imported successfully: ${ imported }`);\n return result;\n }\n catch (error)\n {\n this.#log.error(`Failed to import meshes from: ${ sceneFilename }`, error);\n throw error;\n }\n }\n\n /**\n * Releases the Havok module and forgets a load of it still in flight. The scenes this engine created\n * belong to their levels and are left alone.\n */\n async dispose() : Promise<void>\n {\n this.#log.info('Tearing down SceneEngine');\n\n this.#havok = undefined;\n this.#havokLoading = undefined;\n\n this.#log.info('SceneEngine torn down successfully');\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Asset Manager\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { Mesh } from '@babylonjs/core';\n\nimport type { AbstractMesh, AssetContainer, InstancedMesh, InstantiatedEntries, Scene } from '@babylonjs/core';\n\nimport type { EventBus } from '@skewedaspect/sage-core';\nimport type { SceneEngine } from '../engines/scene.ts';\nimport type { Disposable } from '../interfaces/lifecycle.ts';\nimport type { LoggerInterface } from '../interfaces/logger.ts';\nimport { type LoggingUtility, SAGELogger } from '../utils/logger.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\ninterface CachedContainer\n{\n container : AssetContainer;\n scene : Scene;\n refCount : number;\n}\n\n/**\n * How many assets a preload loads at once. Six is the per-origin connection limit browsers have long held\n * for HTTP/1.1: below it a batch leaves the connection pool idle, and above it the extra loads only queue\n * somewhere else while splitting the same bandwidth and competing for the same main thread to parse on.\n */\nconst MAX_CONCURRENT_LOADS = 6;\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Central asset cache with preloading, GLB fragment extraction, mesh instancing, and reference counting.\n *\n * Supports fragment syntax (`models/props.glb#chest_lid`) to extract individual meshes from a shared container.\n * Containers are reference-counted and only disposed when all consumers release them.\n */\nexport class AssetManager implements Disposable\n{\n private _bus : EventBus;\n private _sceneEngine : SceneEngine;\n private _log : LoggerInterface;\n\n /** Full GLB/GLTF asset containers, keyed by base path (without fragment) */\n private _containers = new Map<string, CachedContainer>();\n\n /** Extracted fragment meshes (source meshes for instancing), keyed by full fragment path */\n private _sourceMeshes = new Map<string, AbstractMesh>();\n\n /** Container loads still in flight, keyed by base path, so concurrent callers share one load */\n private _pendingContainers = new Map<string, Promise<AssetContainer>>();\n\n /**\n * Containers `instantiate` read for a caller's scene, keyed by that scene and then by base path. Each one is\n * the scene's: Babylon disposes it with the scene, and a scene no one holds takes its entry with it.\n */\n private _sceneContainers = new WeakMap<Scene, Map<string, Promise<AssetContainer>>>();\n\n //------------------------------------------------------------------------------------------------------------------\n\n constructor(bus : EventBus, sceneEngine : SceneEngine, logger ?: LoggingUtility)\n {\n this._bus = bus;\n this._sceneEngine = sceneEngine;\n this._log = logger?.getLogger('AssetManager') ?? new SAGELogger('AssetManager');\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Private Helpers\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Split a path on `#` into base path and optional fragment name.\n */\n private _parsePath(path : string) : { basePath : string; fragment ?: string }\n {\n const hashIdx = path.indexOf('#');\n if(hashIdx === -1)\n {\n return { basePath: path };\n }\n\n return {\n basePath: path.substring(0, hashIdx),\n fragment: path.substring(hashIdx + 1),\n };\n }\n\n /**\n * Load (or retrieve from cache) the AssetContainer for a base path, counting one reference for this\n * caller. A caller arriving while the same base path is already loading shares that load instead of\n * starting a second one, which would leave the first container cached over and uncounted.\n */\n private async _loadContainer(basePath : string) : Promise<AssetContainer>\n {\n const cached = this._containers.get(basePath);\n if(cached)\n {\n cached.refCount++;\n return cached.container;\n }\n\n let inFlight = this._pendingContainers.get(basePath);\n if(!inFlight)\n {\n inFlight = this._fetchContainer(basePath);\n this._pendingContainers.set(basePath, inFlight);\n }\n\n const container = await inFlight;\n\n // The fetch caches with no references counted; each caller that awaited it counts its own here.\n // A dispose() during the fetch leaves nothing to count against, so check before counting.\n const entry = this._containers.get(basePath);\n if(entry)\n {\n entry.refCount++;\n }\n\n return container;\n }\n\n /**\n * Read one container off disk and cache it, counting no references; `_loadContainer` does the counting.\n *\n * The scene the read goes into is cached beside the container and lives as long as it does. Babylon's\n * `AssetContainer` subscribes to its scene's disposal and disposes itself, meshes and all, so disposing\n * the scene early would leave an emptied container in the cache\n * (design/engine.md#the-asset-manager).\n */\n private async _fetchContainer(basePath : string) : Promise<AssetContainer>\n {\n this._log.debug(`Loading asset container: ${ basePath }`);\n\n const scene = this._sceneEngine.createScene();\n\n try\n {\n const container = await this._sceneEngine.loadModel(basePath, scene);\n\n this._containers.set(basePath, { container, scene, refCount: 0 });\n this._log.debug(`Cached asset container: ${ basePath }`);\n\n return container;\n }\n catch (error)\n {\n scene.dispose();\n throw error;\n }\n finally\n {\n this._pendingContainers.delete(basePath);\n }\n }\n\n /**\n * The container for a base path read into the given scene, reading it on the first call. Calls made while\n * the read is in flight share it. A read that fails is not remembered, so the next call reads again.\n */\n private _containerForScene(basePath : string, scene : Scene) : Promise<AssetContainer>\n {\n let containers = this._sceneContainers.get(scene);\n if(!containers)\n {\n containers = new Map<string, Promise<AssetContainer>>();\n this._sceneContainers.set(scene, containers);\n }\n\n const cached = containers.get(basePath);\n if(cached)\n {\n return cached;\n }\n\n const reading = this._sceneEngine.loadModel(basePath, scene);\n containers.set(basePath, reading);\n\n const sceneContainers = containers;\n reading.catch(() =>\n {\n sceneContainers.delete(basePath);\n });\n\n return reading;\n }\n\n /**\n * Find a mesh by name inside a container. Throws if not found.\n */\n private _findMeshInContainer(container : AssetContainer, meshName : string, fullPath : string) : AbstractMesh\n {\n const mesh = container.meshes.find((entry) => entry.name === meshName);\n if(!mesh)\n {\n const available = container.meshes.map((entry) => entry.name).join(', ');\n throw new Error(\n `Mesh '${ meshName }' not found in container for '${ fullPath }'. `\n + `Available meshes: ${ available }`\n );\n }\n\n return mesh;\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Public API\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Load an asset, caching the result. Supports fragment syntax (`path#meshName`).\n *\n * - Without fragment: loads and caches the full AssetContainer, returns it.\n * - With fragment: loads the container, finds the named mesh, caches it as a source mesh, returns the mesh.\n *\n * Increments refCount on the underlying container each time.\n */\n async load(path : string) : Promise<AssetContainer | AbstractMesh>\n {\n const { basePath, fragment } = this._parsePath(path);\n\n if(fragment)\n {\n // Check if we already cached this fragment mesh\n const cachedMesh = this._sourceMeshes.get(path);\n if(cachedMesh)\n {\n // Still increment refCount on the container\n const cached = this._containers.get(basePath);\n if(cached)\n {\n cached.refCount++;\n }\n\n return cachedMesh;\n }\n\n // Load the container (handles caching + refCount internally)\n const container = await this._loadContainer(basePath);\n\n // Find and cache the named mesh\n const mesh = this._findMeshInContainer(container, fragment, path);\n this._sourceMeshes.set(path, mesh);\n\n return mesh;\n }\n\n // No fragment — return the full container\n return this._loadContainer(basePath);\n }\n\n /**\n * Create an InstancedMesh from a cached source mesh. The path must have been loaded first.\n */\n instance(path : string) : InstancedMesh\n {\n const sourceMesh = this._sourceMeshes.get(path);\n if(!sourceMesh)\n {\n throw new Error(`Asset '${ path }' is not loaded. Call load() first.`);\n }\n\n return (sourceMesh as Mesh).createInstance(`${ sourceMesh.name }-instance`);\n }\n\n /**\n * Clone a cached source mesh into an independent copy, in the scene it is handed. The path must have been\n * loaded first.\n *\n * `Mesh.clone` keeps the source's scene, and the source lives in the scene this manager read the file\n * into, so a clone made that way would never draw in the level that asked for it. Building the copy\n * against the scene it is handed is what puts it in the level's, and `doNotCloneChildren` keeps a mesh\n * parented under the source from being left behind in the manager's\n * (design/engine.md#the-asset-manager).\n */\n clone(path : string, into : Scene) : Mesh\n {\n const sourceMesh = this._sourceMeshes.get(path);\n if(!sourceMesh)\n {\n throw new Error(`Asset '${ path }' is not loaded. Call load() first.`);\n }\n\n const copy = new Mesh(`${ sourceMesh.name }-clone`, into, {\n source: sourceMesh as Mesh,\n doNotCloneChildren: true,\n });\n\n // The source's metadata is the glb's extras, which the level walk reads off the level's own scene and a\n // spawned mesh never carries (design/authoring.md#the-standard-mesh-source).\n copy.metadata = null;\n\n return copy;\n }\n\n /**\n * Copies a whole file into the scene it is handed: every mesh and node, with the file's skeletons and animation\n * groups. The meshes are cloned and share the container's materials. The copy is the caller's to dispose.\n */\n async instantiate(path : string, scene : Scene) : Promise<InstantiatedEntries>\n {\n const { basePath, fragment } = this._parsePath(path);\n if(fragment)\n {\n throw new Error(\n `Cannot instantiate '${ path }': the fragment '${ fragment }' is one mesh. clone() copies one mesh.`\n );\n }\n\n const container = await this._containerForScene(basePath, scene);\n const entries = container.instantiateModelsToScene(undefined, false, { doNotInstantiate: true });\n\n // The glb's extras are the level walk's to read off the level's own scene; a copy never carries them\n // (design/authoring.md#the-standard-mesh-source).\n for(const root of entries.rootNodes)\n {\n root.metadata = null;\n\n for(const node of root.getDescendants())\n {\n node.metadata = null;\n }\n }\n\n return entries;\n }\n\n /**\n * Preload a batch of asset paths, up to `MAX_CONCURRENT_LOADS` at a time, so a batch costs about as\n * long as its slowest asset rather than the sum of them all.\n *\n * Emits `asset:progress` as each asset finishes loading, naming the asset that finished and counting\n * one to one up to the total. Loads finish out of request order, so the paths do too.\n *\n * Every asset is attempted, and every load has finished before this returns. If any failed, it throws\n * an `AggregateError` naming all of them and emits no `asset:complete`.\n */\n async preload(paths : string[]) : Promise<void>\n {\n const total = paths.length;\n const failures : { path : string; error : unknown }[] = [];\n let nextIndex = 0;\n let loaded = 0;\n\n // A worker takes the next unclaimed path the moment its own load finishes, so a slow asset holds\n // up only itself. Nothing here yields between reading nextIndex and advancing it, so no two\n // workers can claim the same path.\n const runWorker = async () : Promise<void> =>\n {\n while(nextIndex < paths.length)\n {\n const path = paths[nextIndex];\n nextIndex++;\n\n let succeeded = true;\n\n try\n {\n // eslint-disable-next-line no-await-in-loop\n await this.load(path);\n }\n catch (error)\n {\n succeeded = false;\n failures.push({ path, error });\n }\n\n if(succeeded)\n {\n loaded++;\n\n this._bus.emit({\n type: 'asset:progress',\n senderID: 'assetManager',\n payload: {\n path,\n loaded,\n total,\n },\n });\n }\n }\n };\n\n const workers : Promise<void>[] = [];\n const workerCount = Math.min(MAX_CONCURRENT_LOADS, total);\n for(let i = 0; i < workerCount; i++)\n {\n workers.push(runWorker());\n }\n\n await Promise.all(workers);\n\n if(failures.length > 0)\n {\n const failedPaths = failures.map((failure) => failure.path).join(', ');\n\n throw new AggregateError(\n failures.map((failure) => failure.error),\n `Preload failed for ${ failures.length } of ${ total } assets: ${ failedPaths }`\n );\n }\n\n this._bus.emit({\n type: 'asset:complete',\n senderID: 'assetManager',\n payload: { paths },\n });\n }\n\n /**\n * Gives one reference back. Once the count reaches zero the container and every fragment mesh taken from it\n * are disposed (design/engine.md#the-asset-manager).\n */\n release(path : string) : void\n {\n const { basePath } = this._parsePath(path);\n const cached = this._containers.get(basePath);\n if(!cached)\n {\n return;\n }\n\n cached.refCount--;\n\n if(cached.refCount <= 0)\n {\n this._log.debug(`Disposing asset container: ${ basePath } (refCount reached 0)`);\n\n // Clean up all fragment meshes that came from this container\n for(const [ fragmentPath, mesh ] of this._sourceMeshes)\n {\n if(fragmentPath.startsWith(`${ basePath }#`))\n {\n mesh.dispose();\n this._sourceMeshes.delete(fragmentPath);\n }\n }\n\n cached.container.dispose();\n cached.scene.dispose();\n this._containers.delete(basePath);\n }\n }\n\n async dispose() : Promise<void>\n {\n for(const cached of this._containers.values())\n {\n cached.container.dispose();\n cached.scene.dispose();\n }\n\n this._containers.clear();\n this._sourceMeshes.clear();\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Game Manager\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { AbstractEngine, Scene } from '@babylonjs/core';\n\n// Interfaces\nimport type { Disposable } from '../interfaces/lifecycle.ts';\nimport type { LoggerInterface } from '../interfaces/logger.ts';\n\n// Classes\nimport type { EventBus } from '@skewedaspect/sage-core';\nimport type { GameEngine } from '../classes/gameEngine.ts';\n\n// Managers\nimport type { InputManager } from './input.ts';\nimport type { LevelManager } from './level.ts';\nimport type { NodeEntityRegistry } from '../entities/nodeSimulation.ts';\n\n// Utils\nimport { isBrowser } from '../utils/capabilities.ts';\nimport { type LoggingUtility, SAGELogger } from '../utils/logger.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/** Run once per rendered frame, with the frame's elapsed time in seconds. */\nexport type FrameCallback = (elapsed : number) => void;\n\n/** How a frame callback answers to pause (design/ui.md#when-a-view-observes). */\nexport interface FrameCallbackOptions\n{\n suspendedByPause ?: boolean;\n}\n\ninterface RegisteredFrameCallback\n{\n id : number;\n callback : FrameCallback;\n suspendedByPause : boolean;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport class GameManager<Registry extends NodeEntityRegistry = NodeEntityRegistry> implements Disposable\n{\n #renderEngine : AbstractEngine;\n #bus : EventBus;\n #levelManager : LevelManager<Registry>;\n #log : LoggerInterface;\n\n #gameEngine : GameEngine<Registry> | null = null;\n #inputManager : InputManager | null = null;\n\n #frameCallbacks : RegisteredFrameCallback[] = [];\n #nextCallbackID = 0;\n\n #paused = false;\n #timeScale = 1;\n\n #boundRenderLoop : () => void;\n #boundResizeHandler : () => void;\n\n public started = false;\n\n //------------------------------------------------------------------------------------------------------------------\n\n constructor(\n renderEngine : AbstractEngine,\n bus : EventBus,\n levelManager : LevelManager<Registry>,\n logger ?: LoggingUtility\n )\n {\n this.#renderEngine = renderEngine;\n this.#bus = bus;\n this.#levelManager = levelManager;\n this.#log = logger?.getLogger('GameManager') || new SAGELogger('GameManager');\n\n this.#boundRenderLoop = () => { this.#renderFrame(); };\n this.#boundResizeHandler = () => { this.#handleResize(); };\n\n if(isBrowser())\n {\n window.addEventListener('resize', this.#boundResizeHandler);\n }\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Accessors\n //------------------------------------------------------------------------------------------------------------------\n\n get currentScene() : Scene | null\n {\n return this.#levelManager.currentLevel?.scene ?? null;\n }\n\n get isPaused() : boolean\n {\n return this.#paused;\n }\n\n /** The multiplier on the elapsed time the frame hands the simulation (design/engine.md#time-scale). */\n get timeScale() : number\n {\n return this.#timeScale;\n }\n\n set timeScale(scale : number)\n {\n if(!Number.isFinite(scale) || scale < 0)\n {\n throw new RangeError(`timeScale requires a finite scale >= 0, got ${ scale }`);\n }\n\n this.#timeScale = scale;\n this.#log.debug(`Time scale set to ${ scale }`);\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Late binding\n //------------------------------------------------------------------------------------------------------------------\n\n // The engine takes its managers as constructor arguments, so it is built after this one; nothing here reads it\n // before the first frame, which is later still.\n $setGameEngine(gameEngine : GameEngine<Registry>) : void\n {\n this.#gameEngine = gameEngine;\n }\n\n // The routing loop's caller-side half (design/input.md#the-routing-loop): the frame calls\n // `routeCapturedInput` on whatever is registered here immediately before `runStepsForElapsedTime`,\n // once a simulation exists.\n setInputManager(inputManager : InputManager) : void\n {\n this.#inputManager = inputManager;\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Frame callbacks\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Registers a function to run once per rendered frame, in registration order, and answers with a function that\n * removes that one callback (design/engine.md#frame-callbacks).\n */\n registerFrameCallback(callback : FrameCallback, options ?: FrameCallbackOptions) : () => void\n {\n const id = this.#nextCallbackID;\n this.#nextCallbackID += 1;\n\n this.#frameCallbacks.push({ id, callback, suspendedByPause: options?.suspendedByPause ?? true });\n this.#log.debug(`Registered frame callback (id: ${ id })`);\n\n return () =>\n {\n const index = this.#frameCallbacks.findIndex((registered) => registered.id === id);\n\n if(index !== -1)\n {\n this.#frameCallbacks.splice(index, 1);\n this.#log.debug(`Unregistered frame callback (id: ${ id })`);\n }\n };\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // The frame\n //------------------------------------------------------------------------------------------------------------------\n\n #renderFrame() : void\n {\n const elapsed = this.#renderEngine.getDeltaTime() / 1000;\n const scaledElapsed = elapsed * this.#timeScale;\n\n let steppedThisFrame = false;\n\n if(!this.#paused)\n {\n if(this.#gameEngine === null)\n {\n throw new Error('GameManager: gameEngine not set. Call $setGameEngine() first.');\n }\n\n // No simulation is bound until a level is activated, or a game binds what it built itself.\n const simulation = this.#gameEngine.simulation;\n\n if(simulation !== null)\n {\n if(this.#inputManager !== null)\n {\n this.#inputManager.routeCapturedInput(simulation);\n }\n\n // Not wrapped in a try/catch: a throw out of an entity's update is a defect in a behavior, and it\n // leaves the frame loop rather than being carried on as news (design/simulation.md#failure).\n const tickBefore = simulation.tick;\n simulation.runStepsForElapsedTime(scaledElapsed);\n steppedThisFrame = simulation.tick !== tickBefore;\n }\n }\n\n // The fixed step drains the bus; on a frame no step ran this drains once where the step's own drain would\n // have fallen, so a frame callback sees the same delivered events either way (design/events.md#the-drain).\n if(!steppedThisFrame)\n {\n this.#bus.drain();\n }\n\n this.#runFrameCallbacks(elapsed, scaledElapsed);\n\n // Rendering is untouched by pause and by the time scale, so the last frame stays on the screen.\n if(this.currentScene !== null)\n {\n this.currentScene.render();\n }\n }\n\n // A callback that answers to pause reads game time; one registered to run through pause reads the wall clock,\n // because presentation follows the wall clock (design/engine.md#time-scale).\n #runFrameCallbacks(elapsed : number, scaledElapsed : number) : void\n {\n for(const { callback, suspendedByPause } of this.#frameCallbacks)\n {\n const suspended = this.#paused && suspendedByPause;\n\n if(!suspended)\n {\n let handed = elapsed;\n\n if(suspendedByPause)\n {\n handed = scaledElapsed;\n }\n\n try\n {\n callback(handed);\n }\n catch (err)\n {\n this.#log.error('Error in frame callback:', err);\n }\n }\n }\n }\n\n #handleResize() : void\n {\n if(this.started)\n {\n this.#renderEngine.resize();\n }\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Public API\n //------------------------------------------------------------------------------------------------------------------\n\n async start() : Promise<void>\n {\n this.#renderEngine.runRenderLoop(this.#boundRenderLoop);\n this.started = true;\n\n this.#log.info('SkewedAspect Game Engine started successfully');\n }\n\n async stop() : Promise<void>\n {\n this.started = false;\n this.#renderEngine.stopRenderLoop();\n\n this.#log.info('SkewedAspect Game Engine stopped successfully');\n }\n\n /** Suspends the routing loop, the fixed step, and every frame callback that did not opt out. */\n pause(reason ?: string) : void\n {\n if(this.#paused)\n {\n this.#log.debug('Game is already paused');\n return;\n }\n\n this.#paused = true;\n\n let reasonSuffix = '';\n if(reason)\n {\n reasonSuffix = `(reason: ${ reason })`;\n }\n\n this.#log.info('Game paused', reasonSuffix);\n\n this.#bus.emit({\n type: 'game:paused',\n senderID: 'gameManager',\n payload: { reason },\n });\n }\n\n /** Puts the routing loop, the fixed step, and the suspended frame callbacks back. */\n resume(reason ?: string) : void\n {\n if(!this.#paused)\n {\n this.#log.debug('Game is not paused');\n return;\n }\n\n this.#paused = false;\n\n let reasonSuffix = '';\n if(reason)\n {\n reasonSuffix = `(reason: ${ reason })`;\n }\n\n this.#log.info('Game resumed', reasonSuffix);\n\n this.#bus.emit({\n type: 'game:resumed',\n senderID: 'gameManager',\n payload: { reason },\n });\n }\n\n async dispose() : Promise<void>\n {\n this.#log.info('Tearing down GameManager');\n\n if(this.started)\n {\n await this.stop();\n }\n\n if(isBrowser())\n {\n window.removeEventListener('resize', this.#boundResizeHandler);\n }\n\n this.#frameCallbacks = [];\n\n this.#log.info('GameManager torn down successfully');\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Action Context\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { LoggerInterface } from '../interfaces/logger.ts';\nimport type { Binding } from './binding.ts';\n\nimport { type LoggingUtility, SAGELogger } from '../utils/logger.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\ninterface ContextEntry\n{\n readonly exclusive : boolean;\n active : boolean;\n}\n\n// A named group of bindings that activate and deactivate together (input.md#action-contexts).\nexport class ActionContextRegistry\n{\n readonly #contexts = new Map<string, ContextEntry>();\n readonly #bindings : Binding<unknown>[] = [];\n readonly #log : LoggerInterface;\n\n constructor(logger ?: LoggingUtility)\n {\n this.#log = logger?.getLogger('ActionContextRegistry') ?? new SAGELogger('ActionContextRegistry');\n }\n\n // Exclusive by default. Registering over a name already registered warns and replaces it, the way the\n // level manager's own registries do (input.md#action-contexts).\n registerContext(name : string, exclusive = true) : void\n {\n if(this.#contexts.has(name))\n {\n this.#log.warn(`Action context '${ name }' is already registered. Overwriting.`);\n }\n\n this.#contexts.set(name, { exclusive, active: false });\n }\n\n // Activating an exclusive context deactivates every other exclusive one. Primes every member binding\n // against the current state the caller reports for it, so an input already held does not read as a\n // fresh press.\n activateContext(name : string, currentState : (binding : Binding<unknown>) => unknown) : void\n {\n const context = this.#requireContext(name);\n\n if(context.exclusive)\n {\n for(const [ otherName, other ] of this.#contexts)\n {\n if(otherName !== name && other.exclusive)\n {\n other.active = false;\n }\n }\n }\n\n context.active = true;\n\n for(const binding of this.#bindings)\n {\n if(binding.context === name)\n {\n binding.prime(currentState(binding));\n }\n }\n }\n\n deactivateContext(name : string) : void\n {\n this.#requireContext(name).active = false;\n }\n\n isActive(name : string) : boolean\n {\n return this.#contexts.get(name)?.active === true;\n }\n\n isRegistered(name : string) : boolean\n {\n return this.#contexts.has(name);\n }\n\n // Registers a binding under its own named context, or under none.\n addBinding(binding : Binding<unknown>) : void\n {\n this.#bindings.push(binding);\n }\n\n // Retires a binding entirely: the routing loop stops delivering through it and the next context\n // activation stops priming it. Idempotent -- removing a binding not held, or removing it twice, changes\n // nothing (input.md#action-contexts).\n removeBinding(binding : Binding<unknown>) : void\n {\n const index = this.#bindings.indexOf(binding);\n\n if(index !== -1)\n {\n this.#bindings.splice(index, 1);\n }\n }\n\n // Every binding currently registered, in the order it was added -- the same list the routing loop walks\n // and a context activation primes (input.md#action-contexts).\n list() : readonly Binding<unknown>[]\n {\n return [ ...this.#bindings ];\n }\n\n // True when the binding names no context, or names one that is active.\n isLive(binding : Binding<unknown>) : boolean\n {\n if(binding.context === undefined)\n {\n return true;\n }\n\n return this.isActive(binding.context);\n }\n\n #requireContext(name : string) : ContextEntry\n {\n const context = this.#contexts.get(name);\n\n if(context === undefined)\n {\n throw new Error(`No context is registered under the name \"${ name }\".`);\n }\n\n return context;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Action Registry\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { Action } from '@skewedaspect/sage-core';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// An action is registered by name before anything binds to it (input.md#actions).\nexport class ActionRegistry\n{\n readonly #actions = new Map<string, Action>();\n\n register(action : Action) : void\n {\n if(this.#actions.has(action.name))\n {\n throw new Error(`An action is already registered under the name \"${ action.name }\".`);\n }\n\n if(action.type === 'analog' && action.minValue !== undefined && action.maxValue !== undefined)\n {\n if(action.minValue > action.maxValue)\n {\n throw new Error(\n `Action \"${ action.name }\": minValue (${ action.minValue }) is above `\n + `maxValue (${ action.maxValue }).`\n );\n }\n }\n\n this.#actions.set(action.name, action);\n }\n\n get(name : string) : Action | undefined\n {\n return this.#actions.get(name);\n }\n\n list() : readonly Action[]\n {\n return [ ...this.#actions.values() ];\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Delivering an Action\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { Action, GameEvent, GameSimulationView } from '@skewedaspect/sage-core';\n\nimport type { Binding } from './binding.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// A fired binding reaches every subscribed entity as a broadcast `action:<name>` event, carrying the\n// boolean or number the binding computed -- deciding which entities answer is game logic, not something\n// this delivers (events.md#delivery, input.md#actions).\nexport function deliverAction<State>(\n binding : Binding<State>,\n state : State,\n context : GameSimulationView,\n senderID : string\n) : void\n{\n const fire = binding.process(state);\n\n if(fire === undefined)\n {\n return;\n }\n\n // The action name is only known at runtime, so the event map can never narrow `type` to a literal here --\n // the same gap sage-core's own GameEntity.emit() bridges by widening payload to `unknown` before the cast.\n const event : { type : string; senderID : string; payload : unknown } = {\n type: `action:${ binding.action.name }`,\n senderID,\n payload: fire.value,\n };\n\n context.bus.emit(event as GameEvent);\n}\n\n// A value action reaches every subscribed entity the same way, as a broadcast carrying the value the\n// routing loop computed for it. No one binding decides that value, so this takes the action and the value\n// rather than a binding (input.md#value-actions, input.md#the-routing-loop).\nexport function deliverValueAction(\n action : Action,\n value : number,\n context : GameSimulationView,\n senderID : string\n) : void\n{\n const event : { type : string; senderID : string; payload : unknown } = {\n type: `action:${ action.name }`,\n senderID,\n payload: value,\n };\n\n context.bus.emit(event as GameEvent);\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Gamepad Device Reader\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { GamepadState } from '../deviceState.ts';\nimport type { DeviceReader } from '../deviceReader.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport type GamepadSourceType = 'button' | 'axis';\n\n// Reads a gamepad button or stick axis (input.md#device-readers).\nexport class GamepadReader implements DeviceReader<GamepadState>\n{\n readonly sourceType : GamepadSourceType;\n readonly sourceKey : string;\n\n constructor(sourceType : GamepadSourceType, sourceKey : string)\n {\n this.sourceType = sourceType;\n this.sourceKey = sourceKey;\n }\n\n getValue(state : GamepadState) : boolean | number | undefined\n {\n if(this.sourceType === 'button')\n {\n return state.buttons[this.sourceKey];\n }\n\n if(this.sourceType === 'axis')\n {\n return state.axes[this.sourceKey];\n }\n\n return undefined;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Keyboard Device Reader\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { KeyboardState } from '../deviceState.ts';\nimport type { DeviceReader } from '../deviceReader.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// Reads one key by its DOM code (input.md#device-readers).\nexport class KeyboardReader implements DeviceReader<KeyboardState>\n{\n readonly sourceType = 'key';\n readonly sourceKey : string;\n\n constructor(sourceKey : string)\n {\n this.sourceKey = sourceKey;\n }\n\n getValue(state : KeyboardState) : boolean | undefined\n {\n return state.keys[this.sourceKey];\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Mouse Device Reader\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { MouseState } from '../deviceState.ts';\nimport type { DeviceReader } from '../deviceReader.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport type MouseSourceType = 'button' | 'position' | 'wheel' | 'movement';\n\n// Reads a mouse button, the cursor position, or a wheel axis (input.md#device-readers).\nexport class MouseReader implements DeviceReader<MouseState>\n{\n readonly sourceType : MouseSourceType;\n readonly sourceKey : string;\n\n constructor(sourceType : MouseSourceType, sourceKey : string)\n {\n this.sourceType = sourceType;\n this.sourceKey = sourceKey;\n }\n\n getValue(state : MouseState) : boolean | number | undefined\n {\n if(this.sourceType === 'button')\n {\n return state.buttons[this.sourceKey];\n }\n\n if(this.sourceType === 'movement')\n {\n if(this.sourceKey === 'x')\n {\n return state.movement.x;\n }\n\n if(this.sourceKey === 'y')\n {\n return state.movement.y;\n }\n\n return undefined;\n }\n\n if(this.sourceType === 'position')\n {\n if(this.sourceKey === 'x')\n {\n return state.position.x;\n }\n\n if(this.sourceKey === 'y')\n {\n return state.position.y;\n }\n\n return undefined;\n }\n\n if(this.sourceType === 'wheel')\n {\n if(this.sourceKey === 'deltaX')\n {\n return state.wheel.deltaX;\n }\n\n if(this.sourceKey === 'deltaY')\n {\n return state.wheel.deltaY;\n }\n\n if(this.sourceKey === 'deltaZ')\n {\n return state.wheel.deltaZ;\n }\n\n return undefined;\n }\n\n return undefined;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Device Reader Factory\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { BindingInput } from './binding.ts';\nimport type { DeviceReader } from './deviceReader.ts';\n\nimport { GamepadReader, type GamepadSourceType } from './deviceReaders/gamepad.ts';\nimport { KeyboardReader } from './deviceReaders/keyboard.ts';\nimport { MouseReader, type MouseSourceType } from './deviceReaders/mouse.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nfunction isMouseSourceType(sourceType : string) : sourceType is MouseSourceType\n{\n return sourceType === 'button' || sourceType === 'position' || sourceType === 'wheel'\n || sourceType === 'movement';\n}\n\nfunction isGamepadSourceType(sourceType : string) : sourceType is GamepadSourceType\n{\n return sourceType === 'button' || sourceType === 'axis';\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// Builds the reader a binding input names: a keyboard key by its DOM code, a mouse button, cursor position,\n// wheel axis, or movement axis, a gamepad button or stick axis (input.md#device-readers).\nexport function readerFor(input : BindingInput) : DeviceReader<unknown>\n{\n if(input.device.type === 'keyboard' && input.sourceType === 'key')\n {\n return new KeyboardReader(input.sourceKey);\n }\n\n if(input.device.type === 'mouse' && isMouseSourceType(input.sourceType))\n {\n return new MouseReader(input.sourceType, input.sourceKey);\n }\n\n if(input.device.type === 'gamepad' && isGamepadSourceType(input.sourceType))\n {\n return new GamepadReader(input.sourceType, input.sourceKey);\n }\n\n throw new Error(`A ${ input.device.type } reads nothing named by the source type \"${ input.sourceType }\".`);\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Trigger Binding\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { Action } from '@skewedaspect/sage-core';\n\nimport type { Binding, BindingDefinition, BindingFire, BindingInput, DeviceID } from '../binding.ts';\nimport type { DeviceReader } from '../deviceReader.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport type EdgeMode = 'rising' | 'falling' | 'both';\n\nexport interface TriggerOptions\n{\n edge ?: EdgeMode;\n threshold ?: number;\n\n /** Takes this input from the browser while the binding is live (input.md#claiming-an-input-from-the-browser). */\n preventDefault ?: boolean;\n}\n\n// Fires once on the rising edge, the falling edge, or both. An analog input crossing the threshold counts\n// as the change (input.md#bindings).\nexport class TriggerBinding<State> implements Binding<State>\n{\n readonly kind = 'trigger' as const;\n readonly action : Action;\n readonly device : DeviceID;\n readonly context ?: string;\n readonly preventDefault : boolean;\n\n readonly #reader : DeviceReader<State>;\n readonly #edge : EdgeMode;\n readonly #threshold ?: number;\n\n // undefined until the first process()/prime() call establishes a baseline, so that call never fires.\n #pressed : boolean | undefined;\n\n constructor(\n action : Action,\n device : DeviceID,\n reader : DeviceReader<State>,\n options : TriggerOptions = {},\n context ?: string\n )\n {\n this.action = action;\n this.device = device;\n this.context = context;\n this.preventDefault = options.preventDefault ?? false;\n\n this.#reader = reader;\n this.#edge = options.edge ?? 'rising';\n this.#threshold = options.threshold;\n }\n\n get input() : BindingInput\n {\n return { device: this.device, sourceType: this.#reader.sourceType, sourceKey: this.#reader.sourceKey };\n }\n\n process(state : State) : BindingFire | undefined\n {\n const pressed = this.#isPressed(this.#reader.getValue(state));\n const previous = this.#pressed;\n\n this.#pressed = pressed;\n\n if(previous === undefined)\n {\n return undefined;\n }\n\n if(!this.#crossesEdge(previous, pressed))\n {\n return undefined;\n }\n\n if(this.action.type === 'analog')\n {\n return { value: this.#numericValue(this.#reader.getValue(state)) };\n }\n\n return { value: pressed };\n }\n\n prime(state : State) : void\n {\n this.#pressed = this.#isPressed(this.#reader.getValue(state));\n }\n\n toDefinition() : BindingDefinition\n {\n const options : Record<string, unknown> = { edge: this.#edge };\n\n if(this.#threshold !== undefined)\n {\n options.threshold = this.#threshold;\n }\n\n if(this.preventDefault)\n {\n options.preventDefault = true;\n }\n\n return {\n kind: 'trigger',\n action: this.action.name,\n input: this.input,\n context: this.context,\n options,\n };\n }\n\n #crossesEdge(previous : boolean, pressed : boolean) : boolean\n {\n if(this.#edge === 'rising')\n {\n return !previous && pressed;\n }\n\n if(this.#edge === 'falling')\n {\n return previous && !pressed;\n }\n\n return previous !== pressed;\n }\n\n #isPressed(raw : boolean | number | undefined) : boolean\n {\n if(typeof raw === 'boolean')\n {\n return raw;\n }\n\n if(typeof raw === 'number')\n {\n if(this.#threshold !== undefined)\n {\n return raw >= this.#threshold;\n }\n\n return raw !== 0;\n }\n\n return false;\n }\n\n #numericValue(raw : boolean | number | undefined) : number\n {\n if(typeof raw === 'number')\n {\n return raw;\n }\n\n if(raw === true)\n {\n return 1;\n }\n\n return 0;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Toggle Binding\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { Action } from '@skewedaspect/sage-core';\n\nimport type { Binding, BindingDefinition, BindingFire, BindingInput, DeviceID } from '../binding.ts';\nimport type { DeviceReader } from '../deviceReader.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport interface ToggleOptions\n{\n onValue ?: boolean | number;\n offValue ?: boolean | number;\n initialState ?: boolean;\n threshold ?: number;\n invert ?: boolean;\n\n /** Takes this input from the browser while the binding is live (input.md#claiming-an-input-from-the-browser). */\n preventDefault ?: boolean;\n}\n\n// Alternates each time its input is pressed and holds its state between presses (input.md#bindings).\nexport class ToggleBinding<State> implements Binding<State>\n{\n readonly kind = 'toggle' as const;\n readonly action : Action;\n readonly device : DeviceID;\n readonly context ?: string;\n readonly preventDefault : boolean;\n\n readonly #reader : DeviceReader<State>;\n readonly #onValue : boolean | number;\n readonly #offValue : boolean | number;\n readonly #threshold ?: number;\n readonly #invert : boolean;\n\n // No declared default (input.md#bindings): left undeclared, the on/off bit that alternation flips starts\n // undefined, and the first flip lands on true the same way any boolean negation of undefined does.\n #isOn : boolean | undefined;\n\n // What the game declared, written back as declared: where the player has left the toggle since is play\n // state, not configuration (design/input.md#bindings).\n readonly #initialState : boolean | undefined;\n\n // undefined until the first process()/prime() call establishes a baseline, so that call never fires.\n #previousEffective : boolean | undefined;\n\n constructor(\n action : Action,\n device : DeviceID,\n reader : DeviceReader<State>,\n options : ToggleOptions = {},\n context ?: string\n )\n {\n this.action = action;\n this.device = device;\n this.context = context;\n this.preventDefault = options.preventDefault ?? false;\n\n this.#reader = reader;\n this.#onValue = options.onValue ?? true;\n this.#offValue = options.offValue ?? false;\n this.#threshold = options.threshold;\n this.#invert = options.invert ?? false;\n this.#isOn = options.initialState;\n this.#initialState = options.initialState;\n }\n\n get input() : BindingInput\n {\n return { device: this.device, sourceType: this.#reader.sourceType, sourceKey: this.#reader.sourceKey };\n }\n\n process(state : State) : BindingFire | undefined\n {\n const effective = this.#effective(this.#reader.getValue(state));\n const previous = this.#previousEffective;\n\n this.#previousEffective = effective;\n\n if(previous === undefined)\n {\n return undefined;\n }\n\n if(previous || !effective)\n {\n return undefined;\n }\n\n this.#isOn = !this.#isOn;\n\n if(this.#isOn)\n {\n return { value: this.#onValue };\n }\n\n return { value: this.#offValue };\n }\n\n prime(state : State) : void\n {\n this.#previousEffective = this.#effective(this.#reader.getValue(state));\n }\n\n toDefinition() : BindingDefinition\n {\n const options : Record<string, unknown> = {\n onValue: this.#onValue,\n offValue: this.#offValue,\n invert: this.#invert,\n };\n\n if(this.#threshold !== undefined)\n {\n options.threshold = this.#threshold;\n }\n\n if(this.#initialState !== undefined)\n {\n options.initialState = this.#initialState;\n }\n\n if(this.preventDefault)\n {\n options.preventDefault = true;\n }\n\n return {\n kind: 'toggle',\n action: this.action.name,\n input: this.input,\n context: this.context,\n options,\n };\n }\n\n // The pressed state, inverted when the binding alternates on a release instead of a press.\n #effective(raw : boolean | number | undefined) : boolean\n {\n const pressed = this.#isPressed(raw);\n\n if(this.#invert)\n {\n return !pressed;\n }\n\n return pressed;\n }\n\n #isPressed(raw : boolean | number | undefined) : boolean\n {\n if(typeof raw === 'boolean')\n {\n return raw;\n }\n\n if(typeof raw === 'number')\n {\n if(this.#threshold !== undefined)\n {\n return raw >= this.#threshold;\n }\n\n return raw !== 0;\n }\n\n return false;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Value Binding\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { Action } from '@skewedaspect/sage-core';\n\nimport type { Binding, BindingDefinition, BindingFire, BindingInput, DeviceID } from '../binding.ts';\nimport type { DeviceReader } from '../deviceReader.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport interface ValueOptions\n{\n scale ?: number;\n offset ?: number;\n invert ?: boolean;\n deadzone ?: number;\n min ?: number;\n max ?: number;\n\n /** Takes this input from the browser while the binding is live (input.md#claiming-an-input-from-the-browser). */\n preventDefault ?: boolean;\n}\n\n// Passes its input through continuously: scales, offsets, clamps, inverts, and ignores magnitudes inside\n// a deadzone. What it computes is a contribution to its action's value, not a delivery of its own, so it\n// always answers with one (input.md#bindings, input.md#value-actions).\nexport class ValueBinding<State> implements Binding<State>\n{\n readonly kind = 'value' as const;\n readonly action : Action;\n readonly device : DeviceID;\n readonly context ?: string;\n readonly preventDefault : boolean;\n\n readonly #reader : DeviceReader<State>;\n readonly #scale : number;\n readonly #offset : number;\n readonly #invert : boolean;\n readonly #deadzone : number;\n readonly #min ?: number;\n readonly #max ?: number;\n\n constructor(\n action : Action,\n device : DeviceID,\n reader : DeviceReader<State>,\n options : ValueOptions = {},\n context ?: string\n )\n {\n this.action = action;\n this.device = device;\n this.context = context;\n this.preventDefault = options.preventDefault ?? false;\n\n this.#reader = reader;\n this.#scale = options.scale ?? 1;\n this.#offset = options.offset ?? 0;\n this.#invert = options.invert ?? false;\n this.#deadzone = options.deadzone ?? 0;\n this.#min = options.min;\n this.#max = options.max;\n }\n\n get input() : BindingInput\n {\n return { device: this.device, sourceType: this.#reader.sourceType, sourceKey: this.#reader.sourceKey };\n }\n\n process(state : State) : BindingFire\n {\n return { value: this.#compute(state) };\n }\n\n // A value binding has no edge to miss, so an input already held when its context activates needs no\n // baseline: it reports its value on the next pass like any other (input.md#action-contexts).\n prime(_state : State) : void\n {\n // Nothing to prime.\n }\n\n toDefinition() : BindingDefinition\n {\n const options : Record<string, unknown> = {\n scale: this.#scale,\n offset: this.#offset,\n invert: this.#invert,\n deadzone: this.#deadzone,\n };\n\n if(this.#min !== undefined)\n {\n options.min = this.#min;\n }\n\n if(this.#max !== undefined)\n {\n options.max = this.#max;\n }\n\n if(this.preventDefault)\n {\n options.preventDefault = true;\n }\n\n return {\n kind: 'value',\n action: this.action.name,\n input: this.input,\n context: this.context,\n options,\n };\n }\n\n #compute(state : State) : number\n {\n let value = this.#toNumber(this.#reader.getValue(state));\n\n value = value * this.#scale;\n value = value + this.#offset;\n\n if(this.#min !== undefined && value < this.#min)\n {\n value = this.#min;\n }\n\n if(this.#max !== undefined && value > this.#max)\n {\n value = this.#max;\n }\n\n if(this.#invert)\n {\n value = -value;\n }\n\n if(Math.abs(value) < this.#deadzone)\n {\n value = 0;\n }\n\n return value;\n }\n\n #toNumber(raw : boolean | number | undefined) : number\n {\n if(typeof raw === 'number')\n {\n return raw;\n }\n\n if(raw === true)\n {\n return 1;\n }\n\n return 0;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Delta Binding\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { Action } from '@skewedaspect/sage-core';\n\nimport type { Binding, BindingDefinition, BindingFire, BindingInput, DeviceID } from '../binding.ts';\nimport type { DeviceReader } from '../deviceReader.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport interface DeltaOptions\n{\n scale ?: number;\n invert ?: boolean;\n deadzone ?: number;\n\n /** Takes this input from the browser while the binding is live (input.md#claiming-an-input-from-the-browser). */\n preventDefault ?: boolean;\n}\n\n// Fires once for a one-shot sample its input reports -- a wheel notch, a frame of raw mouse movement --\n// treating the sample as consumed. Applies scale, invert, and deadzone; never offset or clamp\n// (input.md#bindings).\nexport class DeltaBinding<State> implements Binding<State>\n{\n readonly kind = 'delta' as const;\n readonly action : Action;\n readonly device : DeviceID;\n readonly context ?: string;\n readonly preventDefault : boolean;\n\n readonly #reader : DeviceReader<State>;\n readonly #scale : number;\n readonly #invert : boolean;\n readonly #deadzone : number;\n\n constructor(\n action : Action,\n device : DeviceID,\n reader : DeviceReader<State>,\n options : DeltaOptions = {},\n context ?: string\n )\n {\n this.action = action;\n this.device = device;\n this.context = context;\n this.preventDefault = options.preventDefault ?? false;\n\n this.#reader = reader;\n this.#scale = options.scale ?? 1;\n this.#invert = options.invert ?? false;\n this.#deadzone = options.deadzone ?? 0;\n }\n\n get input() : BindingInput\n {\n return { device: this.device, sourceType: this.#reader.sourceType, sourceKey: this.#reader.sourceKey };\n }\n\n process(state : State) : BindingFire | undefined\n {\n let value = this.#toNumber(this.#reader.getValue(state));\n\n value = value * this.#scale;\n\n if(this.#invert)\n {\n value = -value;\n }\n\n if(Math.abs(value) < this.#deadzone)\n {\n value = 0;\n }\n\n // No held value to compare against -- a delta fires whenever the sample computes nonzero, every\n // time, and stays silent otherwise (input.md#bindings).\n if(value === 0)\n {\n return undefined;\n }\n\n return { value };\n }\n\n // A delta has no held state to prime -- it fires from a fresh sample every time, never from what a\n // context activation reports (input.md#bindings).\n prime(_state : State) : void\n {\n // Nothing to prime.\n }\n\n toDefinition() : BindingDefinition\n {\n const options : Record<string, unknown> = {\n scale: this.#scale, invert: this.#invert, deadzone: this.#deadzone,\n };\n\n if(this.preventDefault)\n {\n options.preventDefault = true;\n }\n\n return {\n kind: 'delta',\n action: this.action.name,\n input: this.input,\n context: this.context,\n options,\n };\n }\n\n #toNumber(raw : boolean | number | undefined) : number\n {\n if(typeof raw === 'number')\n {\n return raw;\n }\n\n if(raw === true)\n {\n return 1;\n }\n\n return 0;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Binding Resolution\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { Action } from '@skewedaspect/sage-core';\n\nimport type { Binding, BindingDefinition } from './binding.ts';\nimport type { DeviceReader } from './deviceReader.ts';\n\nimport { readerFor } from './readerFor.ts';\n\nimport { TriggerBinding, type TriggerOptions } from './bindings/trigger.ts';\nimport { ToggleBinding, type ToggleOptions } from './bindings/toggle.ts';\nimport { ValueBinding, type ValueOptions } from './bindings/value.ts';\nimport { DeltaBinding, type DeltaOptions } from './bindings/delta.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// Builds the live binding a definition describes: the kind picks trigger, toggle, value, or delta, and the\n// definition's own input names the reader. A caller reading a device state shape of its own hands that\n// reader in instead (input.md#bindings, input.md#device-readers).\nexport function resolveBinding<State>(\n definition : BindingDefinition,\n action : Action,\n reader ?: DeviceReader<State>\n) : Binding<State>\n{\n const device = definition.input.device;\n const options = definition.options ?? {};\n const resolved = reader ?? readerFor(definition.input);\n\n if(definition.kind === 'trigger')\n {\n return new TriggerBinding(action, device, resolved, options as TriggerOptions, definition.context);\n }\n\n if(definition.kind === 'toggle')\n {\n return new ToggleBinding(action, device, resolved, options as ToggleOptions, definition.context);\n }\n\n if(definition.kind === 'delta')\n {\n return new DeltaBinding(action, device, resolved, options as DeltaOptions, definition.context);\n }\n\n return new ValueBinding(action, device, resolved, options as ValueOptions, definition.context);\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Input Scheme\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { DeviceType } from './binding.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// One way of playing. Every device type belongs to exactly one (input.md#input-schemes).\nexport type InputScheme = 'keyboard-mouse' | 'gamepad';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// The scheme the named device type belongs to (input.md#input-schemes).\nexport function schemeOf(deviceType : DeviceType) : InputScheme\n{\n if(deviceType === 'gamepad')\n {\n return 'gamepad';\n }\n\n return 'keyboard-mouse';\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Input Manager\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { Action, GameSimulationView, Unsubscribe } from '@skewedaspect/sage-core';\n\n// Interfaces\nimport type { Disposable } from '../interfaces/lifecycle.ts';\n\n// Input\nimport type { Binding, BindingDefinition, DeviceType } from '../input/binding.ts';\nimport type { CandidateInput, InputCapture } from '../input/inputCapture.ts';\nimport type { GamepadState, KeyboardState, MouseState } from '../input/deviceState.ts';\n\nimport type { InputScheme } from '../input/scheme.ts';\n\nimport { ActionContextRegistry } from '../input/actionContext.ts';\nimport { ActionRegistry } from '../input/actionRegistry.ts';\nimport { deliverAction, deliverValueAction } from '../input/deliverAction.ts';\nimport { resolveBinding } from '../input/resolveBinding.ts';\nimport { schemeOf } from '../input/scheme.ts';\n\n// Utils\nimport type { LoggingUtility } from '../utils/logger.ts';\n\nimport { isBrowser } from '../utils/capabilities.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// `input:deviceChanged` and `input:schemeChanged` are the library's own events, so `sage` augments the map\n// core declares (events.md#the-event-map).\ndeclare module '@skewedaspect/sage-core'\n{\n interface EventMap\n {\n 'input:deviceChanged' : DeviceChangedPayload;\n 'input:schemeChanged' : SchemeChangedPayload;\n }\n}\n\nexport interface DeviceChangedPayload\n{\n deviceType : DeviceType;\n\n /** Undefined for the first deliberate input of a session, which replaces no device type. */\n previousDeviceType : DeviceType | undefined;\n}\n\nexport interface SchemeChangedPayload\n{\n scheme : InputScheme;\n\n /** Undefined for the first deliberate input of a session, which replaces no scheme. */\n previousScheme : InputScheme | undefined;\n}\n\n// The routing loop's own emissions carry this fixed sender, naming the routing loop itself and no entity,\n// so a test or a behavior can compare against it (input.md#the-routing-loop).\nexport const ROUTING_LOOP_SENDER_ID = 'sage-input';\n\n// An analog input is deliberate once its distance from rest passes this; a stick at rest drifts below it\n// for as long as its gamepad is connected (input.md#device-state-capture).\nconst DELIBERATE_THRESHOLD = 0.2;\n\n// At rest. A gamepad button or axis at exactly this value is not deliberate, the same threshold InputCapture\n// applies to a candidate, and it is where a value action rests before the action's own bounds clamp it\n// (input.md#device-state-capture, input.md#value-actions).\nconst REST_VALUE = 0;\n\n// Raw mouse movement is measured in pixels, where the deliberate threshold would be no threshold at all\n// (input.md#device-state-capture).\nconst MOVEMENT_THRESHOLD = 2;\n\n// A gamepad reports its buttons as numbers, so a worn trigger is held to the threshold the same as a stick.\nfunction isDeliberate(value : number) : boolean\n{\n return Math.abs(value) > DELIBERATE_THRESHOLD;\n}\n\nfunction isDeliberateMovement(x : number, y : number) : boolean\n{\n return Math.abs(x) > MOVEMENT_THRESHOLD || Math.abs(y) > MOVEMENT_THRESHOLD;\n}\n\n// Every other `<input>` type takes typed text (input.md#device-state-capture).\nconst INPUT_TYPES_WITHOUT_TEXT = new Set([\n 'button',\n 'checkbox',\n 'color',\n 'file',\n 'hidden',\n 'image',\n 'radio',\n 'range',\n 'reset',\n 'submit',\n]);\n\nfunction isEditableElement(target : EventTarget | undefined) : boolean\n{\n if(!(target instanceof Element))\n {\n return false;\n }\n\n if(target instanceof HTMLTextAreaElement)\n {\n return true;\n }\n\n if(target instanceof HTMLInputElement)\n {\n return !INPUT_TYPES_WITHOUT_TEXT.has(target.type);\n }\n\n // The nearest element carrying the attribute decides, the way `isContentEditable` does; jsdom has no\n // `isContentEditable` to ask.\n const editingHost = target.closest('[contenteditable]');\n\n if(editingHost === null)\n {\n return false;\n }\n\n return editingHost.getAttribute('contenteditable') !== 'false';\n}\n\ninterface MutableGamepadState\n{\n buttons : Record<string, number>;\n axes : Record<string, number>;\n}\n\n// One action's running total over the live value bindings driving it, for the pass being routed\n// (input.md#value-actions).\ninterface ValueActionSum\n{\n action : Action;\n value : number;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// `sage`'s owner of input: one action registry, one action context registry, device state capture, and the\n// routing loop that runs before each frame's fixed steps (input.md).\nexport class InputManager implements Disposable\n{\n readonly actions = new ActionRegistry();\n readonly contexts : ActionContextRegistry;\n\n readonly #keys : Record<string, boolean> = {};\n readonly #mouseButtons : Record<string, boolean> = {};\n readonly #mousePosition = { x: 0, y: 0 };\n readonly #mouseWheel = { deltaX: 0, deltaY: 0, deltaZ: 0 };\n readonly #mouseMovement = { x: 0, y: 0 };\n readonly #gamepadStates = new Map<number, MutableGamepadState>();\n\n readonly #captures = new Set<InputCapture>();\n\n // Every value action the last pass delivered away from rest, by name. A pass that finds one back at\n // rest delivers that rest once and drops it from here (input.md#value-actions).\n readonly #valueActionsAwayFromRest = new Map<string, Action>();\n\n #lastActiveDeviceType : DeviceType | undefined;\n\n #activeScheme : InputScheme | undefined;\n\n // What the previous routing pass left in #lastActiveDeviceType (input.md#the-routing-loop).\n #reportedDeviceType : DeviceType | undefined;\n\n // What the previous routing pass left in #activeScheme (input.md#the-routing-loop).\n #reportedScheme : InputScheme | undefined;\n\n readonly #handleKeyDown : (event : KeyboardEvent) => void;\n readonly #handleKeyUp : (event : KeyboardEvent) => void;\n readonly #handlePointerDown : (event : PointerEvent) => void;\n readonly #handlePointerUp : (event : PointerEvent) => void;\n readonly #handlePointerMove : (event : PointerEvent) => void;\n readonly #handleWheel : (event : WheelEvent) => void;\n\n constructor(logger ?: LoggingUtility)\n {\n this.contexts = new ActionContextRegistry(logger);\n\n this.#handleKeyDown = (event) =>\n {\n // The first entry is the element focus is in, inside a shadow root too, where `target` names the host.\n if(isEditableElement(event.composedPath()[0]))\n {\n return;\n }\n\n if(this.#claims('keyboard', 'key', event.code))\n {\n event.preventDefault();\n }\n\n this.#keys[event.code] = true;\n this.#reportDeliberateInput('keyboard');\n this.#feedCandidate({\n device: { type: 'keyboard', index: 0 }, sourceType: 'key', sourceKey: event.code, value: true,\n });\n };\n\n this.#handleKeyUp = (event) =>\n {\n this.#keys[event.code] = false;\n };\n\n this.#handlePointerDown = (event) =>\n {\n if(event.pointerType !== 'mouse') { return; }\n\n const sourceKey = String(event.button);\n\n if(this.#claims('mouse', 'button', sourceKey))\n {\n event.preventDefault();\n }\n\n this.#mouseButtons[sourceKey] = true;\n this.#reportDeliberateInput('mouse');\n this.#feedCandidate({ device: { type: 'mouse', index: 0 }, sourceType: 'button', sourceKey, value: true });\n };\n\n this.#handlePointerUp = (event) =>\n {\n if(event.pointerType !== 'mouse') { return; }\n\n this.#mouseButtons[String(event.button)] = false;\n };\n\n this.#handlePointerMove = (event) =>\n {\n if(event.pointerType !== 'mouse') { return; }\n\n this.#mousePosition.x = event.clientX;\n this.#mousePosition.y = event.clientY;\n\n this.#mouseMovement.x += event.movementX;\n this.#mouseMovement.y += event.movementY;\n\n // Movement names the mouse and stops there: a hand resting on a mouse moves it, and a nudge must\n // not swap a gamepad player's prompts (input.md#input-schemes).\n if(isDeliberateMovement(event.movementX, event.movementY))\n {\n this.#lastActiveDeviceType = 'mouse';\n }\n };\n\n this.#handleWheel = (event) =>\n {\n if(this.#claims('mouse', 'wheel'))\n {\n event.preventDefault();\n }\n\n // Every notch a frame reported adds up, the way raw movement does: the routing loop reads the\n // frame's whole sample and resets it (input.md#device-state-capture).\n this.#mouseWheel.deltaX += event.deltaX;\n this.#mouseWheel.deltaY += event.deltaY;\n this.#mouseWheel.deltaZ += event.deltaZ;\n\n if(event.deltaX !== REST_VALUE || event.deltaY !== REST_VALUE || event.deltaZ !== REST_VALUE)\n {\n this.#reportDeliberateInput('mouse');\n this.#feedWheelCandidates(event);\n }\n };\n\n if(isBrowser())\n {\n window.addEventListener('keydown', this.#handleKeyDown);\n window.addEventListener('keyup', this.#handleKeyUp);\n window.addEventListener('pointerdown', this.#handlePointerDown);\n window.addEventListener('pointerup', this.#handlePointerUp);\n window.addEventListener('pointermove', this.#handlePointerMove);\n window.addEventListener('wheel', this.#handleWheel, { passive: false });\n }\n }\n\n // The device type behind the most recent deliberate input -- a button held true, or an analog value\n // away from zero -- undefined before any device has reported one (input.md#device-state-capture).\n get lastActiveDeviceType() : DeviceType | undefined\n {\n return this.#lastActiveDeviceType;\n }\n\n // The scheme of the most recent deliberate input, undefined before any device has reported one. Raw\n // mouse movement is the one deliberate input that never sets it (input.md#input-schemes).\n get activeScheme() : InputScheme | undefined\n {\n return this.#activeScheme;\n }\n\n // The state currently captured for one device, the same shape every device reader and a context\n // activation's priming read (input.md#device-state-capture).\n deviceState(type : 'keyboard', index : number) : KeyboardState;\n deviceState(type : 'mouse', index : number) : MouseState;\n deviceState(type : 'gamepad', index : number) : GamepadState;\n deviceState(type : DeviceType, index : number) : KeyboardState | MouseState | GamepadState;\n deviceState(type : DeviceType, index : number) : KeyboardState | MouseState | GamepadState\n {\n return this.#captureFor(type, index);\n }\n\n // Builds the binding a definition describes, reader and all, and registers it under the context it\n // names, so a game binds every definition an imported configuration hands back without writing reader\n // code of its own (input.md#bindings, input.md#device-readers).\n bind(definition : BindingDefinition) : Binding<unknown>\n {\n const action = this.actions.get(definition.action);\n\n if(action === undefined)\n {\n throw new Error(`No action is registered under the name \"${ definition.action }\".`);\n }\n\n const binding = resolveBinding(definition, action);\n\n this.contexts.addBinding(binding);\n\n return binding;\n }\n\n // Feeds every deliberate candidate this manager's device state capture observes to the given capture,\n // while it holds a pending wait. Answers with a function that unregisters it (input.md#input-capture).\n registerCapture(capture : InputCapture) : Unsubscribe\n {\n this.#captures.add(capture);\n\n return () =>\n {\n this.#captures.delete(capture);\n };\n }\n\n // Every binding registered for the named action, filtered to a device type, an input scheme, or a\n // context when the caller gives one, in the order the bindings were registered\n // (input.md#looking-up-bindings).\n getBindingsForAction(\n action : string,\n deviceTypeOrScheme ?: DeviceType | InputScheme,\n context ?: string\n ) : readonly Binding<unknown>[]\n {\n const matches : Binding<unknown>[] = [];\n\n for(const binding of this.contexts.list())\n {\n const matchesAction = binding.action.name === action;\n const matchesDevice = deviceTypeOrScheme === undefined\n || binding.device.type === deviceTypeOrScheme\n || schemeOf(binding.device.type) === deviceTypeOrScheme;\n const matchesContext = context === undefined || binding.context === context;\n\n if(matchesAction && matchesDevice && matchesContext)\n {\n matches.push(binding);\n }\n }\n\n return matches;\n }\n\n // Captures device state, updates lastActiveDeviceType, and routes: every live trigger, toggle, and delta\n // binding hands its device's captured state to `deliverAction`, while the live value bindings are summed\n // by action and each action that has one is delivered once with the value that sum computes. Both\n // broadcast with `ROUTING_LOOP_SENDER_ID` (input.md#the-routing-loop, input.md#value-actions).\n routeCapturedInput(context : GameSimulationView) : void\n {\n this.#pollGamepads();\n\n const sums = new Map<string, ValueActionSum>();\n\n for(const binding of this.contexts.list())\n {\n if(this.contexts.isLive(binding))\n {\n const state = this.#captureFor(binding.device.type, binding.device.index);\n\n if(binding.kind === 'value')\n {\n this.#addContribution(sums, binding, state);\n }\n else\n {\n deliverAction(binding, state, context, ROUTING_LOOP_SENDER_ID);\n }\n }\n }\n\n this.#deliverValueActions(sums, context);\n this.#reportDeviceChange(context);\n this.#reportSchemeChange(context);\n\n // A one-shot sample -- the mouse wheel -- resets once this pass has read it, so a still wheel\n // reports nothing on the next frame. Held state is never reset this way (input.md#device-state-capture).\n this.#mouseMovement.x = REST_VALUE;\n this.#mouseMovement.y = REST_VALUE;\n\n this.#mouseWheel.deltaX = REST_VALUE;\n this.#mouseWheel.deltaY = REST_VALUE;\n this.#mouseWheel.deltaZ = REST_VALUE;\n }\n\n // True while a live binding claims the captured input named. An undefined sourceKey matches any key of\n // that source type, which is what a wheel event needs: it carries every axis at once\n // (input.md#claiming-an-input-from-the-browser).\n #claims(deviceType : DeviceType, sourceType : string, sourceKey ?: string) : boolean\n {\n for(const binding of this.contexts.list())\n {\n if(binding.preventDefault && this.contexts.isLive(binding))\n {\n const { device, sourceType: boundType, sourceKey: boundKey } = binding.input;\n\n if(device.type === deviceType && boundType === sourceType\n && (sourceKey === undefined || boundKey === sourceKey))\n {\n return true;\n }\n }\n }\n\n return false;\n }\n\n async dispose() : Promise<void>\n {\n if(isBrowser())\n {\n window.removeEventListener('keydown', this.#handleKeyDown);\n window.removeEventListener('keyup', this.#handleKeyUp);\n window.removeEventListener('pointerdown', this.#handlePointerDown);\n window.removeEventListener('pointerup', this.#handlePointerUp);\n window.removeEventListener('pointermove', this.#handlePointerMove);\n window.removeEventListener('wheel', this.#handleWheel);\n }\n }\n\n // Adds one value binding's contribution to its action's running total, starting that total when this is\n // the first binding the pass has seen for the action (input.md#value-actions).\n #addContribution(sums : Map<string, ValueActionSum>, binding : Binding<unknown>, state : unknown) : void\n {\n const fire = binding.process(state);\n\n if(fire === undefined)\n {\n return;\n }\n\n // `BindingFire` carries whatever any of the four kinds sends; a value binding's is always a number.\n const contribution = Number(fire.value);\n const running = sums.get(binding.action.name);\n\n if(running === undefined)\n {\n sums.set(binding.action.name, { action: binding.action, value: contribution });\n }\n else\n {\n running.value = running.value + contribution;\n }\n }\n\n // Delivers each value action on every pass it is away from rest, and once more on the pass it returns\n // to rest. An action left with no live value binding to contribute still settles: an empty total is\n // zero, which is rest (input.md#value-actions).\n #deliverValueActions(sums : Map<string, ValueActionSum>, context : GameSimulationView) : void\n {\n for(const [ name, action ] of this.#valueActionsAwayFromRest)\n {\n if(!sums.has(name))\n {\n sums.set(name, { action, value: 0 });\n }\n }\n\n for(const [ name, sum ] of sums)\n {\n const value = this.#clampToAction(sum.action, sum.value);\n const awayFromRest = value !== this.#clampToAction(sum.action, REST_VALUE);\n const wasAwayFromRest = this.#valueActionsAwayFromRest.has(name);\n\n if(awayFromRest)\n {\n this.#valueActionsAwayFromRest.set(name, sum.action);\n }\n else\n {\n this.#valueActionsAwayFromRest.delete(name);\n }\n\n if(awayFromRest || wasAwayFromRest)\n {\n deliverValueAction(sum.action, value, context, ROUTING_LOOP_SENDER_ID);\n }\n }\n }\n\n // A value action's value stays inside the bounds its declaration set, its rest included\n // (input.md#value-actions).\n #clampToAction(action : Action, value : number) : number\n {\n if(action.type !== 'analog')\n {\n return value;\n }\n\n if(action.minValue !== undefined && value < action.minValue)\n {\n return action.minValue;\n }\n\n if(action.maxValue !== undefined && value > action.maxValue)\n {\n return action.maxValue;\n }\n\n return value;\n }\n\n #captureFor(type : DeviceType, index : number) : KeyboardState | MouseState | GamepadState\n {\n if(type === 'keyboard')\n {\n return { keys: this.#keys };\n }\n\n if(type === 'mouse')\n {\n return {\n buttons: this.#mouseButtons,\n position: this.#mousePosition,\n wheel: this.#mouseWheel,\n movement: this.#mouseMovement,\n };\n }\n\n const gamepad = this.#gamepadStates.get(index);\n\n if(gamepad === undefined)\n {\n return { buttons: {}, axes: {} };\n }\n\n return gamepad;\n }\n\n // Polls every connected gamepad (input.md#device-state-capture). Unlike the DOM listeners above, this\n // needs no window: `navigator.getGamepads` is a plain function, present or absent on its own account.\n #pollGamepads() : void\n {\n if(typeof navigator === 'undefined' || typeof navigator.getGamepads !== 'function')\n {\n return;\n }\n\n const gamepads = navigator.getGamepads();\n\n for(let index = 0; index < gamepads.length; index++)\n {\n const gamepad = gamepads[index];\n\n if(gamepad !== null)\n {\n this.#pollOneGamepad(index, gamepad);\n }\n }\n }\n\n #pollOneGamepad(index : number, gamepad : Gamepad) : void\n {\n const buttons : Record<string, number> = {};\n let deliberate = false;\n\n for(let buttonIndex = 0; buttonIndex < gamepad.buttons.length; buttonIndex++)\n {\n const value = gamepad.buttons[buttonIndex].value;\n const sourceKey = String(buttonIndex);\n\n buttons[sourceKey] = value;\n\n if(isDeliberate(value))\n {\n deliberate = true;\n this.#feedCandidate({ device: { type: 'gamepad', index }, sourceType: 'button', sourceKey, value });\n }\n }\n\n const axes : Record<string, number> = {};\n\n for(let axisIndex = 0; axisIndex < gamepad.axes.length; axisIndex++)\n {\n const value = gamepad.axes[axisIndex];\n const sourceKey = String(axisIndex);\n\n axes[sourceKey] = value;\n\n if(isDeliberate(value))\n {\n deliberate = true;\n this.#feedCandidate({ device: { type: 'gamepad', index }, sourceType: 'axis', sourceKey, value });\n }\n }\n\n this.#gamepadStates.set(index, { buttons, axes });\n\n if(deliberate)\n {\n this.#reportDeliberateInput('gamepad');\n }\n }\n\n // Emitted when this pass leaves a different device type than the previous pass did: a broadcast under\n // the routing loop's own sender, alongside the actions it just delivered (input.md#the-routing-loop).\n #reportDeviceChange(context : GameSimulationView) : void\n {\n if(this.#lastActiveDeviceType === this.#reportedDeviceType)\n {\n return;\n }\n\n const previousDeviceType = this.#reportedDeviceType;\n this.#reportedDeviceType = this.#lastActiveDeviceType;\n\n if(this.#lastActiveDeviceType === undefined)\n {\n return;\n }\n\n context.bus.emit({\n type: 'input:deviceChanged',\n senderID: ROUTING_LOOP_SENDER_ID,\n payload: { deviceType: this.#lastActiveDeviceType, previousDeviceType },\n });\n }\n\n // A deliberate input names its device type and makes that device type's scheme the active one\n // (input.md#device-state-capture, input.md#input-schemes).\n #reportDeliberateInput(deviceType : DeviceType) : void\n {\n this.#lastActiveDeviceType = deviceType;\n this.#activeScheme = schemeOf(deviceType);\n }\n\n // Emitted when this pass leaves a different scheme than the previous pass did, under the same rules and\n // the same sender as `input:deviceChanged` (input.md#the-routing-loop).\n #reportSchemeChange(context : GameSimulationView) : void\n {\n if(this.#activeScheme === this.#reportedScheme)\n {\n return;\n }\n\n const previousScheme = this.#reportedScheme;\n this.#reportedScheme = this.#activeScheme;\n\n if(this.#activeScheme === undefined)\n {\n return;\n }\n\n context.bus.emit({\n type: 'input:schemeChanged',\n senderID: ROUTING_LOOP_SENDER_ID,\n payload: { scheme: this.#activeScheme, previousScheme },\n });\n }\n\n // One candidate per axis the notch actually turned, so a rebind screen binds the axis the player\n // scrolled rather than whichever this reads first (input.md#input-capture).\n #feedWheelCandidates(event : WheelEvent) : void\n {\n const axes = [\n { sourceKey: 'deltaY', value: event.deltaY },\n { sourceKey: 'deltaX', value: event.deltaX },\n { sourceKey: 'deltaZ', value: event.deltaZ },\n ];\n\n for(const axis of axes)\n {\n if(axis.value !== REST_VALUE)\n {\n this.#feedCandidate({\n device: { type: 'mouse', index: 0 },\n sourceType: 'wheel',\n sourceKey: axis.sourceKey,\n value: axis.value,\n });\n }\n }\n }\n\n #feedCandidate(candidate : CandidateInput) : void\n {\n for(const capture of this.#captures)\n {\n capture.feed(candidate);\n }\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Post-Processing Pipeline Backend\n//\n// Applies post-processing effects via BabylonJS DefaultRenderingPipeline and SSAO2RenderingPipeline.\n//----------------------------------------------------------------------------------------------------------------------\n\nimport {\n Color4,\n DefaultRenderingPipeline,\n ImageProcessingConfiguration,\n SSAO2RenderingPipeline,\n type Scene,\n} from '@babylonjs/core';\n\nimport type { PostProcessingConfig } from '../interfaces/level.ts';\nimport type { LoggerInterface } from '../interfaces/logger.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Map from user-facing tonemap operator names to BabylonJS constants.\n * BabylonJS only has STANDARD (0) and ACES (1) — the other names all map to STANDARD.\n */\nconst TONEMAP_OPERATORS : Record<string, number> = {\n hable: ImageProcessingConfiguration.TONEMAPPING_STANDARD,\n reinhard: ImageProcessingConfiguration.TONEMAPPING_STANDARD,\n hejidawson: ImageProcessingConfiguration.TONEMAPPING_STANDARD,\n photographic: ImageProcessingConfiguration.TONEMAPPING_STANDARD,\n aces: ImageProcessingConfiguration.TONEMAPPING_ACES,\n};\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Apply post-processing effects to a scene based on a declarative config.\n * Creates BabylonJS rendering pipelines and configures them according to the provided settings.\n *\n * This is a level-config handler, not a node property handler. The logger is the level's own.\n */\nexport function applyPipelinePostProcessing(\n scene : Scene,\n config : PostProcessingConfig,\n logger : LoggerInterface\n) : void\n{\n // Can't set up post-processing without a camera\n if(!scene.activeCamera)\n {\n return;\n }\n\n // Volumetric lighting is recognized and applied by neither backend.\n if(config.volumetric)\n {\n logger.warn('Volumetric lighting is recognized and applied by neither backend. Ignored.');\n }\n\n // Create the default rendering pipeline\n const pipeline = new DefaultRenderingPipeline('sage-default', true, scene, scene.cameras);\n\n // Bloom\n if(config.bloom)\n {\n pipeline.bloomEnabled = true;\n\n if(config.bloom.weight !== undefined)\n {\n pipeline.bloomWeight = config.bloom.weight;\n }\n if(config.bloom.threshold !== undefined)\n {\n pipeline.bloomThreshold = config.bloom.threshold;\n }\n if(config.bloom.scale !== undefined)\n {\n pipeline.bloomScale = config.bloom.scale;\n }\n if(config.bloom.kernel !== undefined)\n {\n pipeline.bloomKernel = config.bloom.kernel;\n }\n }\n\n // Tone mapping\n if(config.tonemap)\n {\n pipeline.imageProcessingEnabled = true;\n pipeline.imageProcessing.toneMappingEnabled = true;\n\n if(config.tonemap.operator)\n {\n pipeline.imageProcessing.toneMappingType\n = TONEMAP_OPERATORS[config.tonemap.operator] ?? ImageProcessingConfiguration.TONEMAPPING_STANDARD;\n }\n }\n\n // Film grain\n if(config.grain)\n {\n pipeline.grainEnabled = true;\n\n if(config.grain.intensity !== undefined)\n {\n pipeline.grain.intensity = config.grain.intensity;\n }\n if(config.grain.animated !== undefined)\n {\n pipeline.grain.animated = config.grain.animated;\n }\n }\n\n // Vignette\n if(config.vignette)\n {\n pipeline.imageProcessingEnabled = true;\n pipeline.imageProcessing.vignetteEnabled = true;\n\n if(config.vignette.weight !== undefined)\n {\n pipeline.imageProcessing.vignetteWeight = config.vignette.weight;\n }\n if(config.vignette.stretch !== undefined)\n {\n pipeline.imageProcessing.vignetteStretch = config.vignette.stretch;\n }\n if(config.vignette.color)\n {\n pipeline.imageProcessing.vignetteColor = new Color4(\n config.vignette.color.r,\n config.vignette.color.g,\n config.vignette.color.b,\n 1\n );\n }\n }\n\n // Sharpen\n if(config.sharpen)\n {\n pipeline.sharpenEnabled = true;\n\n if(config.sharpen.edge !== undefined)\n {\n pipeline.sharpen.edgeAmount = config.sharpen.edge;\n }\n if(config.sharpen.color !== undefined)\n {\n pipeline.sharpen.colorAmount = config.sharpen.color;\n }\n }\n\n // Chromatic aberration\n if(config.chromaticAberration)\n {\n pipeline.chromaticAberrationEnabled = true;\n\n if(config.chromaticAberration.amount !== undefined)\n {\n pipeline.chromaticAberration.aberrationAmount = config.chromaticAberration.amount;\n }\n }\n\n // SSAO — uses its own separate pipeline\n if(config.ssao)\n {\n const ssao = new SSAO2RenderingPipeline('sage-ssao', scene, { ssaoRatio: 0.5, blurRatio: 1.0 });\n\n if(config.ssao.radius !== undefined)\n {\n ssao.radius = config.ssao.radius;\n }\n if(config.ssao.samples !== undefined)\n {\n ssao.samples = config.ssao.samples;\n }\n if(config.ssao.totalStrength !== undefined)\n {\n ssao.totalStrength = config.ssao.totalStrength;\n }\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Post-Processing Dispatcher\n//\n// Routes to either the DefaultRenderingPipeline backend or the Frame Graph backend\n// based on config.renderer.\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { Scene } from '@babylonjs/core';\n\nimport type { PostProcessingConfig } from '../interfaces/level.ts';\nimport type { LoggerInterface } from '../interfaces/logger.ts';\n\nimport { applyPipelinePostProcessing } from './postProcessingPipeline.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Applies a level's post-processing configuration to its scene through the backend the configuration names. The\n * logger is the level's own, and is where a backend's warning is written (design/authoring.md#post-processing).\n */\nexport async function applyPostProcessing(\n scene : Scene,\n config : PostProcessingConfig,\n logger : LoggerInterface\n) : Promise<void>\n{\n if(config.renderer === 'frameGraph')\n {\n const { applyFrameGraphPostProcessing } = await import('./postProcessingFrameGraph.ts');\n await applyFrameGraphPostProcessing(scene, config, logger);\n }\n else\n {\n applyPipelinePostProcessing(scene, config, logger);\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Hierarchy Bounds\n//\n// Blender's glTF exporter splits a multi-material model into primitive children, leaving a bare\n// TransformNode parent with no geometry of its own. `PhysicsAggregate` reads such a node's raw bounding\n// info -- a default unit cube -- so anything sizing a collision shape from one of these nodes has to\n// measure the descendants instead.\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { Quaternion, type TransformNode, Vector3 } from '@babylonjs/core';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport interface HierarchyBounds\n{\n /** Full box size across the node's descendants. */\n extents : Vector3;\n\n /** Offset from the node's origin, in the node's local frame. */\n center : Vector3;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Measures a node's descendant geometry as a box, in the format `PhysicsAggregate` takes in its options.\n */\nexport function computeHierarchyBounds(node : TransformNode) : HierarchyBounds\n{\n const { min, max } = node.getHierarchyBoundingVectors(true);\n const extents = max.subtract(min);\n const worldCenter = min.add(max).scale(0.5);\n\n // Undo the node's rotation so the center lands in its local frame; for the common identity case this\n // is just worldCenter - absolutePosition.\n const worldOffset = worldCenter.subtract(node.absolutePosition);\n const inverseRotation = Quaternion.Inverse(node.absoluteRotationQuaternion);\n const center = new Vector3();\n\n worldOffset.rotateByQuaternionToRef(inverseRotation, center);\n\n return { extents, center };\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Collider Property Handler\n//----------------------------------------------------------------------------------------------------------------------\n\nimport {\n Mesh,\n PhysicsAggregate,\n type PhysicsAggregateParameters,\n PhysicsMotionType,\n PhysicsPrestepType,\n PhysicsShapeType,\n type TransformNode,\n} from '@babylonjs/core';\n\nimport type { LoggerInterface } from '../interfaces/logger.ts';\nimport type { LevelManager } from '../managers/level.ts';\nimport { type HierarchyBounds, computeHierarchyBounds } from '../utils/bounds.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nconst DEFAULT_COLLIDER_MASS = 1;\n\n// The words the authored property takes, and nothing else: the body behavior's own vocabulary is wider\n// (design/physics.md#the-authored-collider-property).\nconst AUTHORED_SHAPES = [ 'box', 'sphere', 'mesh', 'none' ] as const;\n\ntype AuthoredShape = typeof AUTHORED_SHAPES[number];\n\n/**\n * What a node's `collider_motion` and `collider_mass` ask the body to be.\n */\ninterface ColliderMotion\n{\n type : PhysicsMotionType;\n mass : number;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n// Reading the authored properties\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Reads the shape a node's `collider` names, warning and answering with nothing for a value outside the four\n * (design/physics.md#the-authored-collider-property).\n */\nfunction readShape(node : TransformNode, value : unknown, logger : LoggerInterface) : AuthoredShape | undefined\n{\n for(const shape of AUTHORED_SHAPES)\n {\n if(value === shape)\n {\n return shape;\n }\n }\n\n logger.warn(\n `Unusable collider on \"${ node.name }\": ${ JSON.stringify(value) }. `\n + `The collider property takes ${ AUTHORED_SHAPES.join(', ') }. Building nothing.`\n );\n\n return undefined;\n}\n\n/**\n * Reads a node's mass, falling back to the default for anything that is not a finite number\n * (design/physics.md#the-authored-collider-property).\n */\nfunction readColliderMass(node : TransformNode, logger : LoggerInterface) : number\n{\n const authored = node.metadata?.collider_mass;\n\n if(authored === undefined || authored === null)\n {\n return DEFAULT_COLLIDER_MASS;\n }\n\n if(typeof authored !== 'number' || !Number.isFinite(authored))\n {\n logger.warn(\n `Unusable collider_mass on ${ node.name }: ${ JSON.stringify(authored) }. `\n + `Using the default of ${ DEFAULT_COLLIDER_MASS }.`\n );\n\n return DEFAULT_COLLIDER_MASS;\n }\n\n return authored;\n}\n\n/**\n * Reads a node's motion. An authored collider with no `collider_motion` is static: a node in a level is\n * scenery until its author says otherwise, and a floor that falls out of the level takes the level with it\n * (design/physics.md#the-authored-collider-property).\n */\nfunction readColliderMotion(node : TransformNode, logger : LoggerInterface) : ColliderMotion\n{\n const mass = readColliderMass(node, logger);\n const authored = node.metadata?.collider_motion as string | undefined;\n\n if(authored === 'dynamic')\n {\n return { type: PhysicsMotionType.DYNAMIC, mass };\n }\n else if(authored === 'kinematic')\n {\n return { type: PhysicsMotionType.ANIMATED, mass };\n }\n else if(authored === 'static' || authored === undefined)\n {\n return { type: PhysicsMotionType.STATIC, mass };\n }\n else\n {\n logger.warn(`Unknown collider_motion on ${ node.name }: \"${ authored }\". Building a static body.`);\n return { type: PhysicsMotionType.STATIC, mass };\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n// Building the body\n//----------------------------------------------------------------------------------------------------------------------\n\nfunction hasOwnGeometry(node : TransformNode) : boolean\n{\n return node instanceof Mesh && node.getTotalVertices() > 0;\n}\n\nfunction hasGeometryBeneath(node : TransformNode) : boolean\n{\n for(const child of node.getChildMeshes(false))\n {\n if(child.getTotalVertices() > 0)\n {\n return true;\n }\n }\n\n return false;\n}\n\nfunction createAggregate(\n node : TransformNode,\n shapeType : PhysicsShapeType,\n motion : ColliderMotion,\n measurement : HierarchyBounds | undefined\n) : void\n{\n let mass = motion.mass;\n\n if(motion.type === PhysicsMotionType.STATIC)\n {\n mass = 0;\n }\n\n const options : PhysicsAggregateParameters = { mass };\n\n if(measurement !== undefined)\n {\n options.center = measurement.center;\n\n if(shapeType === PhysicsShapeType.SPHERE)\n {\n // The largest half-extent, so the sphere contains the geometry rather than cutting through its\n // corners (design/physics.md#shapes).\n const { x, y, z } = measurement.extents;\n options.radius = Math.max(x, y, z) / 2;\n }\n else\n {\n options.extents = measurement.extents;\n }\n }\n\n const aggregate = new PhysicsAggregate(node, shapeType, options, node.getScene());\n\n aggregate.body.setMotionType(motion.type);\n\n // The node is the authority for an authored kinematic body: a game writes the node and the body follows it\n // exactly. Moving the body toward the node instead makes the body the authority, and the post-step sync\n // writes its lagging position back over the game's own write\n // (design/physics.md#the-authored-collider-property).\n if(motion.type === PhysicsMotionType.ANIMATED)\n {\n aggregate.body.setPrestepType(PhysicsPrestepType.TELEPORT);\n }\n}\n\n/**\n * A box or a sphere, sized from the node's own bounds when it has geometry and from everything under it when\n * it does not (design/physics.md#sizing-from-a-bare-transform-node).\n */\nfunction buildMeasuredBody(node : TransformNode, shapeType : PhysicsShapeType, motion : ColliderMotion) : void\n{\n if(hasOwnGeometry(node))\n {\n createAggregate(node, shapeType, motion, undefined);\n return;\n }\n\n if(!hasGeometryBeneath(node))\n {\n throw new Error(\n `Cannot build a collider on \"${ node.name }\": it has neither a mesh nor anything under it to size a `\n + 'body from.'\n );\n }\n\n createAggregate(node, shapeType, motion, computeHierarchyBounds(node));\n}\n\n/**\n * The triangle-mesh shape, built from the child a `collider_mesh` marks where there is one: authoring's way\n * of shipping a low-triangle stand-in for a detailed model (design/physics.md#the-authored-collider-property).\n */\nfunction buildTriangleMeshBody(node : TransformNode, motion : ColliderMotion) : void\n{\n let source = node;\n\n for(const child of node.getChildMeshes(false))\n {\n if(child.metadata?.collider_mesh === true && child instanceof Mesh)\n {\n child.isVisible = false;\n source = child;\n break;\n }\n }\n\n if(!hasOwnGeometry(source))\n {\n throw new Error(\n `Cannot build a \"mesh\" collider on \"${ source.name }\": a triangle mesh reads vertices, and this node `\n + 'has none of its own.'\n );\n }\n\n createAggregate(source, PhysicsShapeType.MESH, motion, undefined);\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Register the collider property handler.\n *\n * Supports the following properties on scene nodes:\n * - `collider` (string): the shape, one of \"box\", \"sphere\", \"mesh\", or \"none\"\n * - `collider_mesh` (boolean): on a child node, marks it as the collision source for a parent's \"mesh\" collider\n * - `collider_motion` (string): \"static\", \"dynamic\", or \"kinematic\". Defaults to \"static\".\n * - `collider_mass` (number): mass in kilograms, defaulting to 1. It sets the mass and nothing else; it does\n * not decide whether the body moves.\n */\nexport function registerColliderHandler(levelManager : Pick<LevelManager, 'registerPropertyHandler'>) : void\n{\n levelManager.registerPropertyHandler('collider', (node, value, level, context) =>\n {\n if(!level.scene)\n {\n throw new Error('Scene not available for collider handler');\n }\n\n const shape = readShape(node, value, context.logger);\n\n if(shape === undefined)\n {\n return;\n }\n\n // `none` builds nothing, which is what a node carrying no `collider` at all already gets: it is there\n // so an authored file can say out loud that a node is not meant to collide\n // (design/physics.md#the-authored-collider-property).\n if(shape === 'none')\n {\n return;\n }\n\n const motion = readColliderMotion(node, context.logger);\n\n if(shape === 'mesh')\n {\n buildTriangleMeshBody(node, motion);\n }\n else if(shape === 'sphere')\n {\n buildMeasuredBody(node, PhysicsShapeType.SPHERE, motion);\n }\n else\n {\n buildMeasuredBody(node, PhysicsShapeType.BOX, motion);\n }\n });\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// LOD (Level of Detail) Property Handler\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { Mesh } from '@babylonjs/core';\n\nimport type { LevelManager } from '../managers/level.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/** The numbers in a comma-separated list, with anything that is not one left out. */\nfunction parseDistances(value : string) : number[]\n{\n const distances : number[] = [];\n\n for(const part of value.split(','))\n {\n const distance = parseFloat(part.trim());\n\n if(!Number.isNaN(distance))\n {\n distances.push(distance);\n }\n }\n\n return distances;\n}\n\n/**\n * Register the LOD (Level of Detail) property handler.\n *\n * Supports the following property on scene nodes:\n * - `lod_distances` (string): Comma-separated distances for LOD levels\n *\n * `addLODLevel(distance, mesh)` means \"use this mesh beyond that distance\", so the node itself is the\n * highest detail and each child covers the band past the distance it was registered at.\n * Example: \"10,25,50\" with three children means:\n * - 0-10 units: the node's own mesh\n * - 10-25 units: first child mesh\n * - 25-50 units: second child mesh\n * - 50+ units: third child mesh\n *\n * A run with a child for every distance never hides the node. Hiding needs a distance with no child left\n * to fill it: \"10,25,50\" with two children renders the node, then both children, then nothing past 50.\n */\nexport function registerLodHandler(levelManager : Pick<LevelManager, 'registerPropertyHandler'>) : void\n{\n levelManager.registerPropertyHandler('lod_distances', (node, value, _level, context) =>\n {\n if(!(node instanceof Mesh))\n {\n return;\n }\n\n const distances = parseDistances(String(value));\n\n if(distances.length === 0)\n {\n context.logger.warn(\n `Unusable lod_distances on \"${ node.name }\": ${ JSON.stringify(value) }. No level added.`\n );\n return;\n }\n\n const children = node.getChildMeshes(true) as Mesh[];\n const levelCount = Math.min(distances.length, children.length);\n\n for(let index = 0; index < levelCount; index++)\n {\n node.addLODLevel(distances[index], children[index]);\n }\n\n // Hiding the node beyond the last distance is only expressible when a distance is left over with no\n // child to fill it. Registering null at a distance a child already holds loses: the sort is stable\n // and the child went in first, so the node would never stop rendering\n // (design/authoring.md#property-handlers).\n if(distances.length > children.length)\n {\n const lastDistance = distances[distances.length - 1];\n node.addLODLevel(lastDistance, null);\n }\n });\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Occlusion Property Handler\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { Mesh } from '@babylonjs/core';\n\nimport type { LevelManager } from '../managers/level.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Register the occlusion property handler.\n *\n * Supports the following property on scene nodes:\n * - `occlusion` (string): `optimistic` or `strict`, Babylon's own occlusion query types\n */\nexport function registerOcclusionHandler(levelManager : Pick<LevelManager, 'registerPropertyHandler'>) : void\n{\n levelManager.registerPropertyHandler('occlusion', (node, value, _level, context) =>\n {\n if(!(node instanceof Mesh))\n {\n return;\n }\n\n if(value === 'optimistic')\n {\n node.occlusionType = Mesh.OCCLUSION_TYPE_OPTIMISTIC;\n }\n else if(value === 'strict')\n {\n node.occlusionType = Mesh.OCCLUSION_TYPE_STRICT;\n }\n else\n {\n context.logger.warn(\n `Unknown occlusion on \"${ node.name }\": ${ JSON.stringify(value) }. `\n + 'The occlusion property takes \"optimistic\" or \"strict\".'\n );\n }\n });\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Metadata Utilities\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Collect properties with a common prefix into a single object.\n * Useful for gathering related properties from node metadata.\n *\n * For example, given metadata with:\n * - sound_volume: 0.5\n * - sound_loop: true\n * - sound_spatial: false\n *\n * Calling collectPrefixedProperties(metadata, 'sound') returns:\n * { volume: 0.5, loop: true, spatial: false }\n *\n * @param metadata - The node metadata to search\n * @param prefix - The prefix to match (without trailing underscore)\n * @returns Object with sub-properties (prefix and underscore removed from keys)\n */\nexport function collectPrefixedProperties(\n metadata : Record<string, unknown>,\n prefix : string\n) : Record<string, unknown>\n{\n const result : Record<string, unknown> = {};\n const prefixWithUnderscore = `${ prefix }_`;\n\n for(const [ key, value ] of Object.entries(metadata))\n {\n if(key.startsWith(prefixWithUnderscore))\n {\n const subKey = key.slice(prefixWithUnderscore.length);\n result[subKey] = value;\n }\n }\n\n return result;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Sound Property Handler\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { collectPrefixedProperties } from '../utils/metadata.ts';\nimport type { LevelManager } from '../managers/level.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Register the sound property handler.\n *\n * Supports the following properties on scene nodes:\n * - `sound` (string): Path to the sound file\n * - `sound_volume` (number): Volume 0-1, default 1\n * - `sound_loop` (boolean): Whether to loop, default true\n * - `sound_spatial` (boolean): Whether to use 3D spatial audio, default true\n * - `sound_distance` (number): Maximum distance for spatial audio, default 100\n * - `sound_autoplay` (boolean): Whether to autoplay, default true\n * - `sound_channel` (string): Audio channel name, default 'ambient'\n */\nexport function registerSoundHandler(levelManager : Pick<LevelManager, 'registerPropertyHandler'>) : void\n{\n // The handler answers with the promise rather than letting the load run on behind the walk: a node an\n // entity adopts carries its sound from before that entity existed, finished rather than pending\n // (design/authoring.md#when-handlers-run).\n levelManager.registerPropertyHandler('sound', async (node, value, level, context) =>\n {\n const soundPath = value as string;\n const scene = level.scene;\n\n if(!scene)\n {\n throw new Error('Scene not available for sound handler');\n }\n\n const extras = collectPrefixedProperties(\n node.metadata as Record<string, unknown>,\n 'sound'\n );\n\n const volume = (extras.volume as number) ?? 1;\n const loop = (extras.loop as boolean) ?? true;\n const spatial = (extras.spatial as boolean) ?? true;\n const maxDistance = (extras.distance as number) ?? 100;\n const autoplay = (extras.autoplay as boolean) ?? true;\n const channel = (extras.channel as string) ?? 'ambient';\n\n const audioManager = context.audioManager;\n\n if(!audioManager)\n {\n context.logger.warn(`No AudioManager available. Sound for \"${ node.name }\" skipped.`);\n return;\n }\n\n const sound = await audioManager.createSound(\n `${ node.name }_sound`,\n soundPath,\n channel,\n {\n loop,\n autoplay,\n volume,\n spatialEnabled: spatial,\n spatialMaxDistance: maxDistance,\n spatialDistanceModel: 'linear',\n }\n );\n\n if(spatial)\n {\n sound.spatial.attach(node);\n }\n });\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Trigger Property Handler\n//----------------------------------------------------------------------------------------------------------------------\n\nimport {\n AbstractMesh,\n ActionManager,\n ExecuteCodeAction,\n type IAction,\n type Node,\n type Observer,\n} from '@babylonjs/core';\n\nimport type { EventBus } from '@skewedaspect/sage-core';\n\nimport type { LevelManager } from '../managers/level.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Registers an intersection action (enter or exit) for a trigger mesh against a target mesh.\n */\nfunction registerIntersectionAction(\n actionManager : ActionManager,\n triggerName : string,\n bus : EventBus,\n targetMesh : AbstractMesh,\n actionType : 'enter' | 'exit'\n) : IAction | null\n{\n let triggerType = ActionManager.OnIntersectionExitTrigger;\n\n if(actionType === 'enter')\n {\n triggerType = ActionManager.OnIntersectionEnterTrigger;\n }\n\n const eventType = `trigger:${ actionType }` as const;\n\n return actionManager.registerAction(\n new ExecuteCodeAction(\n {\n trigger: triggerType,\n parameter: { mesh: targetMesh },\n },\n (evt) =>\n {\n // Babylon fires an intersection action with the trigger mesh as its source and the mesh that\n // crossed it as the additional data.\n bus.emit({\n type: eventType,\n senderID: 'trigger',\n payload: {\n trigger: triggerName,\n other: evt.additionalData as AbstractMesh,\n },\n });\n }\n )\n );\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Register the trigger property handler.\n *\n * Supports the following property on scene nodes:\n * - `trigger` (string): The trigger zone name\n *\n * When an object enters or exits the trigger zone, events are published:\n * - `trigger:enter` with payload { trigger: string, other: AbstractMesh }\n * - `trigger:exit` with payload { trigger: string, other: AbstractMesh }\n *\n * The trigger mesh is made invisible but retains collision detection.\n * Intersection checks are registered against all non-trigger meshes in the scene,\n * including meshes added after the handler runs (e.g. dynamically spawned entities).\n */\nexport function registerTriggerHandler(levelManager : Pick<LevelManager, 'registerPropertyHandler'>) : void\n{\n levelManager.registerPropertyHandler('trigger', (node, value, level, context) =>\n {\n const triggerName = value as string;\n const scene = level.scene;\n\n if(!scene)\n {\n throw new Error('Scene not available for trigger handler');\n }\n\n if(!(node instanceof AbstractMesh))\n {\n return;\n }\n\n const bus = context.bus;\n\n node.isVisible = false;\n node.checkCollisions = true;\n\n const existing = node.actionManager as ActionManager | null;\n let actionManager : ActionManager;\n\n if(existing)\n {\n actionManager = existing;\n }\n else\n {\n actionManager = new ActionManager(scene);\n node.actionManager = actionManager;\n }\n\n // Babylon tests every action on a zone's manager on every frame the zone is active, so a watched\n // mesh's pair has to come off when the mesh goes: spawn churn otherwise grows the per-frame work by\n // two actions per despawn, against meshes that no longer exist.\n const watched = new Map<AbstractMesh, Observer<Node>>();\n\n function watch(target : AbstractMesh) : void\n {\n if(target === node || target.metadata?.trigger)\n {\n return;\n }\n\n const actions : IAction[] = [];\n\n for(const actionType of [ 'enter', 'exit' ] as const)\n {\n const action = registerIntersectionAction(actionManager, triggerName, bus, target, actionType);\n\n if(action)\n {\n actions.push(action);\n }\n }\n\n const observer = target.onDisposeObservable.add(() =>\n {\n for(const action of actions)\n {\n actionManager.unregisterAction(action);\n }\n\n watched.delete(target);\n });\n\n if(observer)\n {\n watched.set(target, observer);\n }\n }\n\n for(const mesh of scene.meshes)\n {\n watch(mesh);\n }\n\n const newMeshObserver = scene.onNewMeshAddedObservable.add(watch);\n\n // A zone that goes takes its own watching with it: the meshes it was watching outlive it.\n node.onDisposeObservable.add(() =>\n {\n scene.onNewMeshAddedObservable.remove(newMeshObserver);\n\n for(const [ target, observer ] of watched)\n {\n target.onDisposeObservable.remove(observer);\n }\n\n watched.clear();\n });\n });\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Visible Property Handler\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { AbstractMesh, type TransformNode } from '@babylonjs/core';\nimport type { LevelManager } from '../managers/level.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Register the visible property handler.\n *\n * Supports the following property on scene nodes:\n * - `visible` (boolean | string | number): Controls mesh visibility.\n * Accepts true/false, 'true'/'false', or 1/0. Anything else, including an unlisted string, leaves the\n * mesh visible (design/authoring.md#the-built-in-properties).\n */\nexport function registerVisibleHandler(levelManager : Pick<LevelManager, 'registerPropertyHandler'>) : void\n{\n levelManager.registerPropertyHandler('visible', (node : TransformNode, value : unknown) =>\n {\n // Only meshes have isVisible property\n if(!(node instanceof AbstractMesh))\n {\n return;\n }\n\n let visible = true;\n\n if(typeof value === 'boolean')\n {\n visible = value;\n }\n else if(typeof value === 'string')\n {\n if(value.toLowerCase() === 'true' || value === '1')\n {\n visible = true;\n }\n else if(value.toLowerCase() === 'false')\n {\n visible = false;\n }\n }\n else if(typeof value === 'number')\n {\n visible = value !== 0;\n }\n\n node.isVisible = visible;\n });\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Property Handlers Index\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { LevelManager } from '../managers/level.ts';\n\nimport { registerColliderHandler } from './collider.ts';\nimport { registerLodHandler } from './lod.ts';\nimport { registerOcclusionHandler } from './occlusion.ts';\nimport { applyPostProcessing } from './postProcessing.ts';\nimport { registerSoundHandler } from './sound.ts';\nimport { registerTriggerHandler } from './trigger.ts';\nimport { registerVisibleHandler } from './visible.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport {\n applyPostProcessing,\n registerColliderHandler,\n registerLodHandler,\n registerOcclusionHandler,\n registerSoundHandler,\n registerTriggerHandler,\n registerVisibleHandler,\n};\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// The properties SAGE brings handlers for. A level warns when a scene carries one of these and nothing is\n// registered to act on it, since registering is the game's to do and forgetting is otherwise silent\n// (design/authoring.md#registering-a-handler).\nexport const BUILT_IN_PROPERTIES = [\n 'collider',\n 'lod_distances',\n 'occlusion',\n 'sound',\n 'trigger',\n 'visible',\n] as const;\n\n/**\n * Registers every built-in property handler with the given level manager, one per name in\n * `BUILT_IN_PROPERTIES`. A game that never calls this loads its levels with none of them.\n */\nexport function registerAllPropertyHandlers(levelManager : Pick<LevelManager, 'registerPropertyHandler'>) : void\n{\n registerColliderHandler(levelManager);\n registerLodHandler(levelManager);\n registerOcclusionHandler(levelManager);\n registerSoundHandler(levelManager);\n registerTriggerHandler(levelManager);\n registerVisibleHandler(levelManager);\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Abstract Level Class\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { Color3 } from '@babylonjs/core';\nimport { ClusteredLightContainer } from '@babylonjs/core/Lights/Clustered/clusteredLightContainer';\n\nimport type { Scene, TransformNode } from '@babylonjs/core';\nimport type { NodeEntityRegistry, NodeSimulation } from '../entities/nodeSimulation.ts';\nimport type { MeshLoader } from '../entities/nodeSimulationContext.ts';\nimport type { LoggerInterface } from '../interfaces/logger.ts';\nimport type {\n ColorConfig,\n HandlerContext,\n LevelConfig,\n LevelContext,\n LevelInstance,\n LevelLoadOptions,\n PropertyHandler,\n} from '../interfaces/level.ts';\n\nimport { OutlineManager } from '../managers/outline.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * The base a game subclasses when it builds a level's scene in code. A subclass writes one method,\n * `buildScene`, and reaches every dependency it has through `this.context`; the base drives the load,\n * publishes the level's own events, and owns the scene, the simulation built against it, and the teardown\n * (design/levels.md#the-base-class).\n *\n * The level manager is the factory: it constructs a subclass with the configuration and the context, so a\n * game registers a class and loads a level rather than constructing one itself.\n *\n * ```typescript\n * levelManager.registerLevelClass('my-level', MyCustomLevel);\n *\n * const level = await levelManager.loadLevel({ name: 'Level1', class: 'my-level' });\n * ```\n */\nexport abstract class Level<Registry extends NodeEntityRegistry = NodeEntityRegistry> implements LevelInstance\n{\n public readonly name : string;\n protected readonly _log : LoggerInterface;\n protected readonly _context : LevelContext<Registry>;\n protected _scene : Scene | null = null;\n protected _forRestore = false;\n\n /**\n * The simulation this level's scene was built against. A transition loads the next level before\n * disposing this one, so by the time this level despawns what it spawned, the engine already holds the\n * next level's simulation, minting the same IDs.\n */\n protected _simulation : NodeSimulation<Registry> | null = null;\n public clusteredLights : ClusteredLightContainer | null = null;\n public outlines : OutlineManager | null = null;\n\n //------------------------------------------------------------------------------------------------------------------\n // Constructor\n //------------------------------------------------------------------------------------------------------------------\n\n constructor(config : LevelConfig, context : LevelContext<Registry>)\n {\n this.name = config.name;\n this._context = context;\n this._log = context.logger.getLogger(`Level:${ config.name }`);\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Accessors\n //------------------------------------------------------------------------------------------------------------------\n\n get scene() : Scene | null\n {\n return this._scene;\n }\n\n get isLoaded() : boolean\n {\n return this._scene !== null;\n }\n\n // Every dependency this level has, by name, as the manager handed them\n // (design/levels.md#what-a-level-is-handed).\n protected get context() : LevelContext<Registry>\n {\n return this._context;\n }\n\n get propertyHandlers() : Map<string, PropertyHandler>\n {\n return this._context.propertyHandlers;\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Protected Methods\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * The simulation this level owns, or nothing for a level that never built one. The level manager binds\n * the engine to the current level's simulation (design/simulation.md#which-simulation-the-engine-holds).\n */\n get $simulation() : NodeSimulation<Registry> | null\n {\n return this._simulation;\n }\n\n /**\n * Takes over a simulation built to replace this level's own, as a save load does once it has restored\n * into a fresh one (design/simulation.md#saved-games).\n */\n $adoptSimulation(simulation : NodeSimulation<Registry>) : void\n {\n this._simulation = simulation;\n }\n\n /**\n * Builds this level's simulation for the scene it just created, named after the level, and keeps it. A\n * subclass calls this from `buildScene` before it spawns anything, since a NodeEntity constructs its node\n * in its simulation's scene (design/levels.md#the-scene-and-its-simulation).\n */\n protected rebuildSimulation(scene : Scene, meshSource ?: MeshLoader) : NodeSimulation<Registry>\n {\n this._simulation = this._context.rebuildSimulation(scene, meshSource);\n\n return this._simulation;\n }\n\n /**\n * Publishes one more `level:progress` at the number and the message a subclass names, which is how a\n * level reports its own progress between the 0 and the 100 the base publishes\n * (design/levels.md#the-base-class).\n */\n protected $emitProgress(progress : number, message : string) : void\n {\n this._context.bus.emit({\n type: 'level:progress',\n senderID: this.name,\n payload: {\n levelName: this.name,\n progress,\n message,\n },\n });\n }\n\n //------------------------------------------------------------------------------------------------------------------\n\n protected _toColor3(color : ColorConfig) : Color3\n {\n return new Color3(color.r, color.g, color.b);\n }\n\n /**\n * A node's metadata as one flat record: its own top-level keys with its glTF extras merged onto them, extras\n * winning where both name one. The `gltf` container itself is left out, since it is the loader's record and\n * never an authored property, so a node whose extras are empty and whose own keys are too normalizes to an\n * empty record and the walk skips it (design/authoring.md#the-walk).\n */\n protected _getNormalizedMetadata(node : TransformNode) : Record<string, unknown> | null\n {\n if(!node.metadata)\n {\n return null;\n }\n\n const own : Record<string, unknown> = { ...(node.metadata as Record<string, unknown>) };\n const gltf = own.gltf as { extras ?: Record<string, unknown> } | undefined;\n delete own.gltf;\n\n return { ...own, ...gltf?.extras };\n }\n\n /**\n * Run registered property handlers on a node.\n */\n protected async _runPropertyHandlers(node : TransformNode) : Promise<void>\n {\n // Babylon leaves a node's metadata null until something sets it, and a Level subclass building its\n // own scene reaches this with whatever nodes it has (design/authoring.md#property-handlers).\n const metadata = node.metadata as Record<string, unknown> | null | undefined;\n\n if(metadata === null || metadata === undefined)\n {\n return;\n }\n\n for(const [ property, handler ] of this.propertyHandlers)\n {\n if(property in metadata)\n {\n try\n {\n // eslint-disable-next-line no-await-in-loop\n await handler(node, metadata[property], this, this.#handlerContext());\n }\n catch (error)\n {\n this._log.error(\n `Error in property handler '${ property }' for node '${ node.name }':`,\n error\n );\n }\n }\n }\n }\n\n // A handler is handed the bus, the audio manager where there is one, and the logging utility's logger for\n // handlers, and nothing that reaches past the level (design/authoring.md#what-a-handler-is-handed).\n #handlerContext() : HandlerContext\n {\n return {\n bus: this._context.bus,\n audioManager: this._context.audioManager,\n logger: this._context.logger.getLogger('PropertyHandlers'),\n };\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Public Methods\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Builds the scene through `buildScene`, publishing `level:progress` at 0 and at 100 around it and\n * `level:complete` after, or `level:error` before rethrowing when building fails. A level already holding a\n * scene warns and answers with the one it has.\n */\n async load(options ?: LevelLoadOptions) : Promise<Scene>\n {\n if(this._scene)\n {\n this._log.warn(`Level ${ this.name } is already loaded`);\n return this._scene;\n }\n\n this._forRestore = options?.forRestore ?? false;\n\n this._log.info(`Loading level: ${ this.name }`);\n this._log.time(`level-${ this.name }-load`);\n\n try\n {\n this.$emitProgress(0, 'Starting level load...');\n\n this._scene = await this.buildScene();\n\n this.$emitProgress(100, 'Level loaded successfully');\n this._context.bus.emit({\n type: 'level:complete',\n senderID: this.name,\n payload: {\n levelName: this.name,\n message: 'Level loaded successfully',\n level: this,\n },\n });\n\n this._log.timeEnd(`level-${ this.name }-load`);\n this._log.info(`Level ${ this.name } loaded successfully`);\n\n return this._scene;\n }\n catch (error)\n {\n this._log.error(`Failed to load level ${ this.name }:`, error);\n this._context.bus.emit({\n type: 'level:error',\n senderID: this.name,\n payload: {\n levelName: this.name,\n message: 'Failed to load level',\n error,\n },\n });\n throw error;\n }\n }\n\n /**\n * Builds the scene: a subclass answers with the scene it built, reaching what it needs through `this.context`\n * (design/levels.md#the-base-class).\n */\n protected abstract buildScene() : Promise<Scene>;\n\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Called when this level becomes the active level during a transition.\n * Override in subclasses to perform activation logic (e.g., start music, show UI).\n * Default implementation is a no-op.\n */\n onActivate?() : Promise<void> | void;\n\n /**\n * Called when this level is being replaced during a transition.\n * Override in subclasses to perform cleanup logic (e.g., fade out, save state).\n * Default implementation is a no-op.\n */\n onDeactivate?() : Promise<void> | void;\n\n /**\n * Releases what the base owns: the outline manager, the clustered light container, and the scene. Nothing for\n * a level that never loaded one, and nothing the second time (design/architecture.md#teardown).\n */\n async dispose() : Promise<void>\n {\n if(this._scene)\n {\n this._log.info(`Disposing level: ${ this.name }`);\n\n if(this.outlines)\n {\n this.outlines.dispose();\n this.outlines = null;\n }\n\n if(this.clusteredLights)\n {\n this.clusteredLights.dispose();\n this.clusteredLights = null;\n }\n\n this._scene.dispose();\n this._scene = null;\n }\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Outline Manager\n//\n// Manages named SelectionOutlineLayer instances for entity highlighting.\n// Each layer tracks a Set<NodeEntity> and rebuilds its selection on any change.\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { Color3, type Scene, SelectionOutlineLayer } from '@babylonjs/core';\n\nimport type { NodeEntity } from '../entities/nodeEntity.ts';\nimport type { OutlineLayerConfig } from '../interfaces/level.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\ninterface OutlineLayerEntry\n{\n layer : SelectionOutlineLayer;\n entities : Set<NodeEntity>;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport class OutlineManager\n{\n private _scene : Scene;\n private _layers = new Map<string, OutlineLayerEntry>();\n\n constructor(scene : Scene, configs ?: Record<string, OutlineLayerConfig>)\n {\n this._scene = scene;\n\n if(configs)\n {\n for(const [ name, config ] of Object.entries(configs))\n {\n this.create(name, config);\n }\n }\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Public API\n //------------------------------------------------------------------------------------------------------------------\n\n get(name : string) : SelectionOutlineLayer | undefined\n {\n return this._layers.get(name)?.layer;\n }\n\n create(name : string, config ?: OutlineLayerConfig) : SelectionOutlineLayer\n {\n const existing = this._layers.get(name);\n if(existing)\n {\n return existing.layer;\n }\n\n const layer = new SelectionOutlineLayer(`sage-outline-${ name }`, this._scene);\n\n if(config?.color)\n {\n layer.outlineColor = new Color3(config.color.r, config.color.g, config.color.b);\n }\n\n if(config?.thickness !== undefined) { layer.outlineThickness = config.thickness; }\n if(config?.occlusionStrength !== undefined) { layer.occlusionStrength = config.occlusionStrength; }\n\n this._layers.set(name, { layer, entities: new Set() });\n return layer;\n }\n\n highlightEntity(layerName : string, entity : NodeEntity) : void\n {\n const entry = this._layers.get(layerName);\n if(!entry)\n {\n return;\n }\n\n entry.entities.add(entity);\n this._rebuildSelection(entry);\n }\n\n unhighlightEntity(layerName : string, entity : NodeEntity) : void\n {\n const entry = this._layers.get(layerName);\n if(!entry)\n {\n return;\n }\n\n entry.entities.delete(entity);\n this._rebuildSelection(entry);\n }\n\n unhighlightEntityAll(entity : NodeEntity) : void\n {\n for(const entry of this._layers.values())\n {\n if(entry.entities.delete(entity))\n {\n this._rebuildSelection(entry);\n }\n }\n }\n\n clear(layerName : string) : void\n {\n const entry = this._layers.get(layerName);\n if(!entry)\n {\n return;\n }\n\n entry.entities.clear();\n entry.layer.clearSelection();\n }\n\n dispose() : void\n {\n for(const entry of this._layers.values())\n {\n entry.entities.clear();\n entry.layer.dispose();\n }\n\n this._layers.clear();\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Private\n //------------------------------------------------------------------------------------------------------------------\n\n private _rebuildSelection(entry : OutlineLayerEntry) : void\n {\n entry.layer.clearSelection();\n\n for(const entity of entry.entities)\n {\n const meshes = entity.node.getChildMeshes();\n\n if(meshes.length > 0)\n {\n entry.layer.addSelection(meshes);\n }\n }\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// GameLevel Class - Default Level Implementation\n//----------------------------------------------------------------------------------------------------------------------\n\nimport {\n ArcRotateCamera,\n type Camera,\n type Color3,\n CubeTexture,\n DirectionalLight,\n FreeCamera,\n HDRCubeTexture,\n HemisphericLight,\n MeshBuilder,\n PBRMaterial,\n PointLight,\n Quaternion,\n RectAreaLight,\n Scene,\n SoundState,\n SpotLight,\n StandardMaterial,\n type StaticSound,\n Texture,\n TransformNode,\n UniversalCamera,\n Vector3,\n} from '@babylonjs/core';\nimport { GeospatialCamera } from '@babylonjs/core/Cameras/geospatialCamera';\nimport { ClusteredLightContainer } from '@babylonjs/core/Lights/Clustered/clusteredLightContainer';\n\nimport type {\n CameraDefinition,\n EntityDefinition,\n LevelConfig,\n LevelContext,\n LightDefinition,\n SpawnDefinition,\n} from '../interfaces/level.ts';\nimport { applyPostProcessing } from '../handlers/postProcessing.ts';\nimport { defaultGravity } from '../utils/physics.ts';\nimport { getCanonicalTransform, toQuatObject, toVec3Object, toVector3 } from '../utils/vectors.ts';\n\nimport type { StateBag } from '@skewedaspect/sage-core';\nimport { NodeEntity } from '../entities/nodeEntity.ts';\nimport type { NodeEntityRegistry, NodeSimulation } from '../entities/nodeSimulation.ts';\nimport { BUILT_IN_PROPERTIES } from '../handlers/index.ts';\nimport { Level } from './level.ts';\nimport { OutlineManager } from '../managers/outline.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Metadata collected from a spawn point node.\n * Transforms are normalized to canonical Babylon space — the source level's __root__\n * conversion is removed so these values are clean for any entity type.\n */\ninterface SpawnPointData\n{\n name : string;\n position : Vector3;\n rotation : Quaternion;\n scaling : Vector3;\n node : TransformNode;\n}\n\n/**\n * Metadata collected from an entity node\n */\ninterface EntityNodeData\n{\n type : string;\n node : TransformNode;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * The level a game gets when it describes a scene in configuration instead of writing a `Level` subclass: it\n * builds the scene its configuration names, in one fixed order, and spawns what the scene's own markers name\n * (design/levels.md#gamelevel).\n */\nexport class GameLevel<Registry extends NodeEntityRegistry = NodeEntityRegistry> extends Level<Registry>\n{\n /** The level configuration */\n protected _config : LevelConfig;\n\n /** Collected spawn points from the scene */\n protected _spawnPoints : SpawnPointData[] = [];\n\n /** Collected entity nodes from the scene */\n protected _entityNodes : EntityNodeData[] = [];\n\n /** Entities spawned by this level */\n protected _spawnedEntities : NodeEntity[] = [];\n\n /** Level-scoped sounds created from config */\n protected _levelSounds = new Map<string, StaticSound>();\n\n /** Sounds that were playing before deactivation (for resume on activate) */\n private _playingSoundsBeforeDeactivate = new Set<string>();\n\n //------------------------------------------------------------------------------------------------------------------\n // Constructor\n //------------------------------------------------------------------------------------------------------------------\n\n constructor(config : LevelConfig, context : LevelContext<Registry>)\n {\n super(config, context);\n this._config = config;\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Accessors\n //------------------------------------------------------------------------------------------------------------------\n\n get config() : LevelConfig\n {\n return this._config;\n }\n\n get spawnedEntities() : readonly NodeEntity[]\n {\n return this._spawnedEntities;\n }\n\n /**\n * The simulation this level built its scene against: the one its own spawns and despawns go through,\n * whichever simulation the engine holds at the time. Throws before `buildScene` has bound one.\n */\n protected get simulation() : NodeSimulation<Registry>\n {\n if(this._simulation === null)\n {\n throw new Error(`Level ${ this.name }: rebuildSimulation has not run against this level's scene yet.`);\n }\n\n return this._simulation;\n }\n\n // A marker names its class in the scene file, so the registry checks the name at run time\n // (design/simulation.md#spawn-and-despawn). What it names has to be a class whose entities have a node, since a\n // spawn point places one and an entity marker is adopted as one (design/authoring.md#markers).\n #spawnFromMarker(name : string, state : StateBag) : NodeEntity\n {\n const entity = this.simulation.spawnByName(name, state);\n\n if(!(entity instanceof NodeEntity))\n {\n this.simulation.despawn(entity.id);\n\n throw new Error(\n `GameLevel: the marker naming \"${ name }\" spawned an entity with no node; a marker names a NodeEntity `\n + 'class.'\n );\n }\n\n return entity;\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Protected Methods\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Build the scene by loading assets, processing node metadata, and spawning entities.\n */\n protected async buildScene() : Promise<Scene>\n {\n const { sceneEngine, assetManager } = this.context;\n\n if(this._config.preload && this._config.preload.length > 0)\n {\n this.$emitProgress(2, 'Preloading assets...');\n await assetManager.preload(this._config.preload);\n }\n\n // Assigned early so property handlers can access it during the node walk.\n const scene = sceneEngine.createScene();\n this._scene = scene;\n\n // A NodeEntity always constructs its node in the simulation's own scene, so the simulation the level\n // spawns through has to be bound to this scene before anything below can spawn.\n this.rebuildSimulation(scene, this._config.meshSource);\n\n // Before the scene file, so colliders import into a scene that already has physics.\n if(this._config.physics)\n {\n this.$emitProgress(5, 'Initializing physics...');\n await this._enablePhysics(scene);\n }\n\n if(this._config.scene)\n {\n this.$emitProgress(10, 'Loading scene file...');\n await this._loadSceneFile(scene);\n }\n\n // Before cameras, so the background is ready when one activates.\n if(this._config.environment)\n {\n this.$emitProgress(15, 'Setting up environment...');\n this._processEnvironment(scene);\n }\n\n // Before post-processing, which needs an active camera.\n this._processCameras(scene);\n\n this._processLights(scene);\n\n // After lights, since clustering moves them out of the scene once they exist.\n this._setupClustering(scene);\n\n if(this._config.postProcessing)\n {\n const renderer = this._config.postProcessing.renderer ?? 'pipeline';\n this._log.info(`Applying post-processing with ${ renderer } renderer`);\n await applyPostProcessing(scene, this._config.postProcessing, this._log);\n }\n\n this.outlines = new OutlineManager(scene, this._config.outlines);\n\n this.$emitProgress(50, 'Processing scene properties...');\n await this._processNodeProperties(scene);\n\n // A level loaded to restore a save spawns nothing of its own: the restored snapshot is the sole\n // source of entities, and adoption re-binds through the nodeName already in restored state\n // (design/simulation.md#saved-games).\n if(!this._forRestore)\n {\n this.$emitProgress(70, 'Spawning entities...');\n await this._processSpawnPoints();\n\n this.$emitProgress(85, 'Configuring entities...');\n this._processEntityNodes();\n }\n\n if(this._config.sounds)\n {\n this.$emitProgress(95, 'Loading sounds...');\n await this._processLevelSounds();\n }\n\n return scene;\n }\n\n /**\n * Import all meshes from the configured scene file (GLB/GLTF/Babylon) into the scene.\n */\n private async _loadSceneFile(scene : Scene) : Promise<void>\n {\n const scenePath = this._getScenePath();\n if(!scenePath)\n {\n return;\n }\n\n this._log.debug(`Loading scene file: ${ scenePath }`);\n\n // Set right-handed coordinate system before loading so loaders respect it\n if(this._isRightHanded())\n {\n scene.useRightHandedSystem = true;\n this._log.debug('Scene set to right-handed coordinate system');\n }\n\n try\n {\n await this.context.sceneEngine.importMeshes([], scenePath, scene);\n this._log.debug('Scene file loaded successfully');\n }\n catch (error)\n {\n this._log.error(`Failed to load scene file: ${ scenePath }`, error);\n throw error;\n }\n }\n\n /**\n * Extract the scene file path from the config, which can be a string or SceneConfig object.\n */\n private _getScenePath() : string | undefined\n {\n if(!this._config.scene)\n {\n return undefined;\n }\n\n if(typeof this._config.scene === 'string')\n {\n return this._config.scene;\n }\n\n return this._config.scene.path;\n }\n\n /**\n * Check if the scene config specifies a right-handed coordinate system.\n */\n private _isRightHanded() : boolean\n {\n if(!this._config.scene || typeof this._config.scene === 'string')\n {\n return false;\n }\n\n return this._config.scene.rightHanded === true;\n }\n\n /**\n * Set up the scene environment: IBL (image-based lighting) for PBR reflections and/or a visible skybox.\n */\n private _processEnvironment(scene : Scene) : void\n {\n const env = this._config.environment;\n if(!env) { return; }\n\n // Set up IBL for PBR reflections\n if(env.ibl)\n {\n scene.environmentTexture = this._createEnvironmentTexture(env.ibl, env.iblResolution ?? 256, scene);\n this._log.debug(`IBL set from: ${ env.ibl }`);\n }\n\n // Set up visible skybox\n if(env.skybox)\n {\n const ext = this._getFileExtension(env.skybox);\n\n if(ext === '.hdr' || ext === '.env')\n {\n // HDR/env skybox — use as IBL too if no separate IBL was specified\n if(!env.ibl)\n {\n const res = env.iblResolution ?? 256;\n scene.environmentTexture = this._createEnvironmentTexture(env.skybox, res, scene);\n }\n\n if(scene.environmentTexture)\n {\n scene.createDefaultSkybox(scene.environmentTexture, true, env.skyboxSize ?? 1000, 0, false);\n }\n }\n else\n {\n // JPG/PNG — create a textured sphere\n const size = env.skyboxSize ?? 1000;\n const skybox = MeshBuilder.CreateSphere('skybox', { diameter: size, segments: 32 }, scene);\n\n const mat = new StandardMaterial('skybox-mat', scene);\n mat.backFaceCulling = false;\n mat.disableLighting = true;\n mat.emissiveTexture = new Texture(env.skybox, scene);\n skybox.material = mat;\n skybox.infiniteDistance = true;\n }\n\n this._log.debug(`Skybox set from: ${ env.skybox }`);\n }\n\n // Apply rotation to environment texture\n if(env.rotation !== undefined && scene.environmentTexture)\n {\n (scene.environmentTexture as HDRCubeTexture).rotationY = env.rotation;\n }\n }\n\n /**\n * Create an environment texture from an HDR or env file.\n */\n private _createEnvironmentTexture(path : string, resolution : number, scene : Scene) : CubeTexture | HDRCubeTexture\n {\n const ext = this._getFileExtension(path);\n\n if(ext === '.env')\n {\n return CubeTexture.CreateFromPrefilteredData(path, scene);\n }\n\n return new HDRCubeTexture(path, scene, resolution);\n }\n\n private _getFileExtension(path : string) : string\n {\n const dotIndex = path.lastIndexOf('.');\n\n if(dotIndex >= 0)\n {\n return path.slice(dotIndex).toLowerCase();\n }\n\n return '';\n }\n\n //------------------------------------------------------------------------------------------------------------------\n\n // GameCanvas admits an OffscreenCanvas, which Camera.attachControl cannot take; only a real\n // HTMLCanvasElement attaches.\n private _attachableCanvas() : HTMLCanvasElement | null\n {\n const canvas = this.context.canvas;\n\n if(canvas === null)\n {\n return null;\n }\n\n if(typeof OffscreenCanvas !== 'undefined' && canvas instanceof OffscreenCanvas)\n {\n return null;\n }\n\n return canvas as HTMLCanvasElement;\n }\n\n /**\n * An entry whose key names an imported camera overrides it, and one whose key names none creates a camera\n * of the type it declares. With no camera configuration at all, the first imported camera activates.\n */\n private _processCameras(scene : Scene) : void\n {\n const cameraConfigs = this._config.cameras;\n const canvas = this._attachableCanvas();\n\n if(!cameraConfigs)\n {\n // With no camera configuration at all, the first imported camera activates.\n if(scene.cameras.length > 0)\n {\n scene.activeCamera = scene.cameras[0];\n\n if(canvas)\n {\n scene.activeCamera.attachControl(canvas, true);\n }\n\n this._log.debug(`Activated imported camera: \"${ scene.activeCamera.name }\"`);\n }\n\n return;\n }\n\n let activeCamera : Camera | null = null;\n\n for(const [ name, config ] of Object.entries(cameraConfigs))\n {\n const existing = scene.getCameraByName(name);\n\n if(existing)\n {\n this._applyCameraConfig(existing, config);\n this._log.debug(`Applied overrides to camera: \"${ name }\"`);\n\n if(!activeCamera || config.active)\n {\n activeCamera = existing;\n }\n }\n else if(config.type)\n {\n const camera = this._createCamera(name, config, scene);\n\n if(camera !== null)\n {\n this._log.debug(`Created ${ config.type } camera: \"${ name }\"`);\n\n if(!activeCamera || config.active)\n {\n activeCamera = camera;\n }\n }\n }\n else\n {\n this._log.warn(\n `Camera \"${ name }\" not found in scene and no type specified — skipping`\n );\n }\n }\n\n if(activeCamera)\n {\n scene.activeCamera = activeCamera;\n\n const activeDef = cameraConfigs[activeCamera.name];\n if(canvas && activeDef?.attachControl !== false)\n {\n activeCamera.attachControl(canvas, true);\n }\n }\n }\n\n /**\n * Create a camera of the type an entry declares, or nothing for an entry that cannot be built, which is\n * logged and skipped (design/levels.md#the-load-sequence).\n */\n private _createCamera(name : string, config : CameraDefinition, scene : Scene) : Camera | null\n {\n let pos = Vector3.Zero();\n if(config.position)\n {\n pos = toVector3(config.position);\n }\n\n let camera : Camera;\n\n switch (config.type)\n {\n case 'arcRotate':\n {\n let target = Vector3.Zero();\n if(config.target)\n {\n target = toVector3(config.target);\n }\n\n camera = new ArcRotateCamera(\n name,\n config.alpha ?? Math.PI / 2,\n config.beta ?? Math.PI / 3,\n config.radius ?? 10,\n target,\n scene\n );\n break;\n }\n\n case 'universal':\n {\n camera = new UniversalCamera(name, pos, scene);\n break;\n }\n\n case 'geospatial':\n {\n if(!config.planetRadius)\n {\n this._log.warn(`Geospatial camera \"${ name }\" names no planetRadius; skipped.`);\n return null;\n }\n\n // Every other field this camera takes is set once, by _applyCameraConfig, which runs\n // unconditionally on the camera this switch builds.\n camera = new GeospatialCamera(name, scene, {\n planetRadius: config.planetRadius,\n });\n break;\n }\n\n case 'free':\n default:\n {\n camera = new FreeCamera(name, pos, scene);\n break;\n }\n }\n\n this._applyCameraConfig(camera, config);\n return camera;\n }\n\n /**\n * Apply shared camera properties from a config to an existing camera.\n */\n private _applyCameraConfig(camera : Camera, config : CameraDefinition) : void\n {\n if(config.fov !== undefined) { camera.fov = config.fov; }\n if(config.position) { camera.position = toVector3(config.position); }\n if(config.minZ !== undefined) { camera.minZ = config.minZ; }\n if(config.maxZ !== undefined) { camera.maxZ = config.maxZ; }\n\n if(camera instanceof FreeCamera)\n {\n if(config.speed !== undefined) { camera.speed = config.speed; }\n if(config.rotation) { camera.rotation = toVector3(config.rotation); }\n }\n\n if(camera instanceof ArcRotateCamera)\n {\n // setTarget recomputes alpha, beta, and radius from the camera's position, so the entry's own\n // values are written after it (design/levels.md#the-load-sequence).\n if(config.target) { camera.setTarget(toVector3(config.target)); }\n if(config.alpha !== undefined) { camera.alpha = config.alpha; }\n if(config.beta !== undefined) { camera.beta = config.beta; }\n if(config.radius !== undefined) { camera.radius = config.radius; }\n if(config.lowerRadiusLimit !== undefined) { camera.lowerRadiusLimit = config.lowerRadiusLimit; }\n if(config.upperRadiusLimit !== undefined) { camera.upperRadiusLimit = config.upperRadiusLimit; }\n if(config.lowerBetaLimit !== undefined) { camera.lowerBetaLimit = config.lowerBetaLimit; }\n if(config.upperBetaLimit !== undefined) { camera.upperBetaLimit = config.upperBetaLimit; }\n if(config.wheelPrecision !== undefined) { camera.wheelPrecision = config.wheelPrecision; }\n }\n\n if(camera instanceof GeospatialCamera)\n {\n if(config.planetRadius !== undefined) { camera.limits.planetRadius = config.planetRadius; }\n if(config.yaw !== undefined) { camera.yaw = config.yaw; }\n if(config.pitch !== undefined) { camera.pitch = config.pitch; }\n if(config.radius !== undefined) { camera.radius = config.radius; }\n if(config.center) { camera.center = toVector3(config.center); }\n if(config.checkCollisions !== undefined) { camera.checkCollisions = config.checkCollisions; }\n if(config.yawMin !== undefined) { camera.limits.yawMin = config.yawMin; }\n if(config.yawMax !== undefined) { camera.limits.yawMax = config.yawMax; }\n if(config.radiusMin !== undefined) { camera.limits.radiusMin = config.radiusMin; }\n if(config.radiusMax !== undefined) { camera.limits.radiusMax = config.radiusMax; }\n if(config.pitchMin !== undefined) { camera.limits.pitchMin = config.pitchMin; }\n if(config.pitchMax !== undefined) { camera.limits.pitchMax = config.pitchMax; }\n }\n }\n\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Lights override and create by key the way cameras do. With no light configuration at all, imported\n * lights are left as they are.\n */\n private _processLights(scene : Scene) : void\n {\n const lightConfigs = this._config.lights;\n if(!lightConfigs) { return; }\n\n for(const [ name, config ] of Object.entries(lightConfigs))\n {\n const existing = scene.getLightByName(name);\n\n if(existing)\n {\n this._applyLightConfig(existing, config);\n this._log.debug(`Applied overrides to light: \"${ name }\"`);\n }\n else if(config.type)\n {\n this._createLight(name, config, scene);\n this._log.debug(`Created ${ config.type } light: \"${ name }\"`);\n }\n else\n {\n this._log.warn(\n `Light \"${ name }\" not found in scene and no type specified — skipping`\n );\n }\n }\n }\n\n /**\n * Create a light of the type an entry declares.\n */\n private _createLight(name : string, config : LightDefinition, scene : Scene) : void\n {\n let dir = new Vector3(0, -1, 0);\n if(config.direction)\n {\n dir = toVector3(config.direction);\n }\n\n let pos = Vector3.Zero();\n if(config.position)\n {\n pos = toVector3(config.position);\n }\n\n switch (config.type)\n {\n case 'hemispheric':\n {\n const light = new HemisphericLight(name, dir, scene);\n\n if(config.groundColor)\n {\n light.groundColor = this._toColor3(config.groundColor);\n }\n\n this._applyLightConfig(light, config);\n break;\n }\n\n case 'directional':\n {\n const light = new DirectionalLight(name, dir, scene);\n this._applyLightConfig(light, config);\n break;\n }\n\n case 'point':\n {\n const light = new PointLight(name, pos, scene);\n this._applyLightConfig(light, config);\n break;\n }\n\n case 'spot':\n {\n const light = new SpotLight(\n name,\n pos,\n dir,\n config.angle ?? Math.PI / 3,\n config.exponent ?? 2,\n scene\n );\n this._applyLightConfig(light, config);\n break;\n }\n\n case 'rectarea':\n {\n const light = new RectAreaLight(\n name,\n pos,\n config.width ?? 1,\n config.height ?? 1,\n scene\n );\n this._applyLightConfig(light, config);\n\n // RectAreaLight emits along local -Z and has no direction property. If a direction\n // is specified, parent the light to a rotated pivot node that orients -Z toward it.\n if(config.direction)\n {\n const pivot = new TransformNode(`${ name }_pivot`, scene);\n pivot.position = light.position.clone();\n light.position = Vector3.Zero();\n light.parent = pivot;\n\n // lookAt aligns +Z toward the target; we want -Z toward dir, so look opposite\n pivot.lookAt(pivot.position.subtract(dir));\n }\n\n break;\n }\n }\n }\n\n /**\n * Apply shared light properties from a config to an existing light.\n */\n private _applyLightConfig(\n light : { intensity : number; diffuse : Color3; specular : Color3 },\n config : LightDefinition\n ) : void\n {\n if(config.intensity !== undefined) { light.intensity = config.intensity; }\n if(config.diffuse) { light.diffuse = this._toColor3(config.diffuse); }\n if(config.specular) { light.specular = this._toColor3(config.specular); }\n }\n\n /**\n * Set up clustered lighting: create a container, move eligible lights into it,\n * and fix PBR material falloff for compatibility.\n */\n private _setupClustering(scene : Scene) : void\n {\n const clusterConfig = this._config.clustering;\n if(!clusterConfig?.enabled)\n {\n return;\n }\n\n // Collect eligible lights from the scene\n const eligibleLights = scene.lights.filter(\n (light) => ClusteredLightContainer.IsLightSupported(light)\n );\n\n if(eligibleLights.length === 0)\n {\n this._log.debug('Clustering enabled but no eligible lights found');\n return;\n }\n\n // Remove eligible lights from the scene before adding to container\n for(const light of eligibleLights)\n {\n scene.removeLight(light);\n }\n\n // Create the container with collected lights\n const container = new ClusteredLightContainer('sage-clustered', eligibleLights, scene);\n\n // Apply tuning parameters\n if(clusterConfig.horizontalTiles !== undefined) { container.horizontalTiles = clusterConfig.horizontalTiles; }\n if(clusterConfig.verticalTiles !== undefined) { container.verticalTiles = clusterConfig.verticalTiles; }\n if(clusterConfig.depthSlices !== undefined) { container.depthSlices = clusterConfig.depthSlices; }\n if(clusterConfig.maxRange !== undefined) { container.maxRange = clusterConfig.maxRange; }\n\n this.clusteredLights = container;\n\n this._log.debug(\n `Clustered lighting enabled: ${ eligibleLights.length } lights, `\n + `${ container.horizontalTiles }x${ container.verticalTiles } tiles, `\n + `${ container.depthSlices } depth slices`\n );\n\n // Fix PBR material falloff — physical falloff is incompatible with clustering\n for(const material of scene.materials)\n {\n if(material instanceof PBRMaterial)\n {\n material.useGLTFLightFalloff = true;\n }\n }\n }\n\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Configure and enable the Havok physics plugin with the level's gravity settings.\n */\n private async _enablePhysics(scene : Scene) : Promise<void>\n {\n const physicsConfig = this._config.physics;\n\n let gravity = defaultGravity();\n\n if(typeof physicsConfig === 'object' && physicsConfig.gravity)\n {\n gravity = new Vector3(\n physicsConfig.gravity.x,\n physicsConfig.gravity.y,\n physicsConfig.gravity.z\n );\n }\n\n // When large world rendering is active, enable Havok multi-region physics\n let floatingOriginRadius : number | undefined;\n if(this.context.largeWorldRendering)\n {\n floatingOriginRadius = 100000;\n }\n\n this._log.debug(`Enabling physics with gravity: ${ gravity.toString() }`);\n await this.context.sceneEngine.enablePhysics(scene, gravity, floatingOriginRadius);\n }\n\n /**\n * Walk all transform nodes and meshes, normalizing glTF metadata and dispatching to property handlers.\n */\n private async _processNodeProperties(scene : Scene) : Promise<void>\n {\n const nodes = scene.transformNodes.concat(scene.meshes as TransformNode[]);\n const carrying : TransformNode[] = [];\n\n // Normalize the whole scene before dispatching to any of it. A handler reaching sideways -- a\n // collider looking for the child marked as its collision source, say -- needs that node's\n // properties reachable by name whether or not the walk has arrived at it yet, and glTF puts them\n // under metadata.gltf.extras until it does (design/authoring.md#property-handlers).\n for(const node of nodes)\n {\n const metadata = this._getNormalizedMetadata(node);\n\n if(metadata && Object.keys(metadata).length > 0)\n {\n node.metadata = { ...node.metadata, ...metadata };\n carrying.push(node);\n }\n }\n\n for(const node of carrying)\n {\n this._warnUnhandledProperties(node);\n\n // eslint-disable-next-line no-await-in-loop\n await this._processNodeMetadata(node);\n }\n\n this._log.debug(\n `Processed ${ nodes.length } nodes, found ${ this._spawnPoints.length } spawn points `\n + `and ${ this._entityNodes.length } entity nodes`\n );\n }\n\n // Authoring a property nothing is registered for produces no body, no trigger, no sound and no error,\n // so the only sign is authoring that did nothing. Only SAGE's own names are worth saying: a glb carries\n // whatever else its exporter wrote (design/authoring.md#registering-a-handler).\n private _warnUnhandledProperties(node : TransformNode) : void\n {\n const metadata = node.metadata as Record<string, unknown>;\n\n for(const property of BUILT_IN_PROPERTIES)\n {\n if(property in metadata && !this.propertyHandlers.has(property))\n {\n this._log.warn(\n `Node '${ node.name }' carries '${ property }' and no handler is registered for it. `\n + 'Call registerAllPropertyHandlers on the level manager to install the built-in handlers.'\n );\n }\n }\n }\n\n /**\n * Check a node's metadata for spawn/entity markers and run any registered property handlers.\n */\n private async _processNodeMetadata(node : TransformNode) : Promise<void>\n {\n // Normalize the spawn marker into canonical Babylon space. glTF imports\n // wrap content under a conversion root (RH→LH), and decomposing a\n // mirrored matrix produces poisoned scaling/rotation. getCanonicalTransform\n // cancels the import root before decomposing, giving clean values that\n // work uniformly for primitives and GLB entities.\n if('spawn' in node.metadata)\n {\n const { position, rotation, scaling } = getCanonicalTransform(node);\n\n this._spawnPoints.push({\n name: node.metadata.spawn as string,\n position,\n rotation,\n scaling,\n node,\n });\n }\n\n // Check for entity property\n if('entity' in node.metadata)\n {\n this._entityNodes.push({\n type: node.metadata.entity as string,\n node,\n });\n }\n\n // Run registered property handlers\n await this._runPropertyHandlers(node);\n }\n\n /**\n * Match collected spawn points against the level config's spawn definitions and instantiate entities.\n */\n private async _processSpawnPoints() : Promise<void>\n {\n for(const spawnPoint of this._spawnPoints)\n {\n const spawnDef = this._config.spawns?.[spawnPoint.name];\n\n if(!spawnDef)\n {\n this._log.warn(`No spawn definition found for '${ spawnPoint.name }', skipping`);\n }\n else\n {\n // eslint-disable-next-line no-await-in-loop\n await this._processSpawnPoint(spawnPoint, spawnDef);\n }\n }\n }\n\n /**\n * Spawn an entity at the given spawn point and dispose of the placeholder node.\n */\n private async _processSpawnPoint(spawnPoint : SpawnPointData, spawnDef : SpawnDefinition) : Promise<void>\n {\n try\n {\n const entity = await this._spawnEntity(spawnDef, spawnPoint);\n this._spawnedEntities.push(entity);\n\n // Remove the placeholder node from the scene\n spawnPoint.node.dispose();\n\n this._log.debug(`Spawned entity '${ spawnDef.entity }' at spawn point '${ spawnPoint.name }'`);\n }\n catch (error)\n {\n this._log.error(`Failed to spawn entity at spawn point '${ spawnPoint.name }':`, error);\n }\n }\n\n /**\n * Spawn an entity through the simulation, inheriting position and rotation from the spawn point. The node\n * and its mesh are the simulation's own business, built by NodeEntity's constructor from the class name and\n * state -- the level does no mesh building of its own.\n *\n * A spawn point's scaling has nowhere to go: NodeEntityState declares position and rotation, not scaling,\n * and inventing a state field for it is not this level's call to make. It is dropped.\n */\n private async _spawnEntity(spawnDef : SpawnDefinition, spawnPoint : SpawnPointData) : Promise<NodeEntity>\n {\n const initialState : Record<string, unknown> = {\n ...spawnDef.config,\n tags: spawnDef.tags ?? [],\n position: toVec3Object(spawnPoint.position),\n rotation: toQuatObject(spawnPoint.rotation),\n };\n\n if(spawnDef.displayName !== undefined)\n {\n initialState.displayName = spawnDef.displayName;\n }\n\n if(spawnDef.debugCollider !== undefined)\n {\n initialState.debugCollider = spawnDef.debugCollider;\n }\n\n const state = initialState as unknown as StateBag;\n\n return this.#spawnFromMarker(spawnDef.entity, state);\n }\n\n /**\n * Match collected entity nodes against the level config's entity definitions and spawn entities adopting\n * their marked node.\n */\n private _processEntityNodes() : void\n {\n for(const entityNode of this._entityNodes)\n {\n const entityDef = this._config.entities?.[entityNode.type];\n\n if(!entityDef)\n {\n this._log.warn(`No entity definition found for '${ entityNode.type }', skipping`);\n }\n else\n {\n this._spawnEntityNode(entityDef, entityNode);\n }\n }\n }\n\n /**\n * Spawn an entity adopting an entity marker's node. `nodeName` is the marked node's own name, written by\n * the level rather than by configuration, since it is the binding key adoption resolves through\n * (design/authoring.md#the-binding-key).\n */\n private _spawnEntityNode(entityDef : EntityDefinition, entityNode : EntityNodeData) : void\n {\n try\n {\n const initialState : Record<string, unknown> = {\n ...entityDef.config,\n tags: entityDef.tags ?? [],\n nodeName: entityNode.node.name,\n };\n\n if(entityDef.displayName !== undefined)\n {\n initialState.displayName = entityDef.displayName;\n }\n\n if(entityDef.debugCollider !== undefined)\n {\n initialState.debugCollider = entityDef.debugCollider;\n }\n\n const state = initialState as unknown as StateBag;\n const entity = this.#spawnFromMarker(entityDef.entity, state);\n this._spawnedEntities.push(entity);\n\n this._log.debug(`Spawned entity '${ entityDef.entity }' adopting node '${ entityNode.node.name }'`);\n }\n catch (error)\n {\n this._log.error(`Failed to spawn entity adopting node '${ entityNode.node.name }':`, error);\n }\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Level Sounds\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Create the sounds the configuration names, keeping each by name so it follows the level: paused on\n * deactivation, played again on activation, disposed with the level (design/levels.md#level-sounds).\n */\n private async _processLevelSounds() : Promise<void>\n {\n const soundConfigs = this._config.sounds;\n if(!soundConfigs) { return; }\n\n const { audioManager } = this.context;\n if(!audioManager)\n {\n this._log.warn('No AudioManager configured. Level sounds will not be created.');\n return;\n }\n\n for(const [ name, config ] of Object.entries(soundConfigs))\n {\n try\n {\n // eslint-disable-next-line no-await-in-loop\n const sound = await audioManager.createSound(name, config.url, config.channel, {\n loop: config.loop ?? false,\n volume: config.volume ?? 1,\n });\n\n this._levelSounds.set(name, sound);\n\n if(config.autoplay)\n {\n sound.play();\n }\n\n this._log.debug(`Created level sound: \"${ name }\"`);\n }\n catch (error)\n {\n this._log.error(`Failed to create level sound \"${ name }\":`, error);\n }\n }\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Lifecycle\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Pause all playing level sounds when the level is deactivated.\n * Subclasses that override this must call `super.onDeactivate()`.\n */\n async onDeactivate() : Promise<void>\n {\n for(const [ name, sound ] of this._levelSounds)\n {\n if(sound.state === SoundState.Started)\n {\n this._playingSoundsBeforeDeactivate.add(name);\n sound.pause();\n }\n }\n }\n\n /**\n * Resume previously playing level sounds when the level is reactivated.\n * Subclasses that override this must call `super.onActivate()`.\n */\n async onActivate() : Promise<void>\n {\n for(const name of this._playingSoundsBeforeDeactivate)\n {\n const sound = this._levelSounds.get(name);\n if(sound)\n {\n sound.play();\n }\n }\n\n this._playingSoundsBeforeDeactivate.clear();\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Public Methods\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Dispose of this level's resources\n */\n override async dispose() : Promise<void>\n {\n // Despawn all spawned entities, from the simulation this level built them in -- the engine may\n // already hold the next level's.\n if(this._simulation !== null)\n {\n for(const entity of this._spawnedEntities)\n {\n this._simulation.despawn(entity.id);\n }\n }\n\n this._simulation = null;\n this._spawnedEntities = [];\n this._spawnPoints = [];\n this._entityNodes = [];\n\n // Dispose level sounds\n for(const sound of this._levelSounds.values())\n {\n sound.dispose();\n }\n this._levelSounds.clear();\n this._playingSoundsBeforeDeactivate.clear();\n\n await super.dispose();\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Level Manager\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { LoggerInterface } from '../interfaces/logger.ts';\nimport type {\n LevelConfig,\n LevelConstructor,\n LevelContext,\n LevelLoadOptions,\n PropertyHandler,\n} from '../interfaces/level.ts';\nimport type { Disposable } from '../interfaces/lifecycle.ts';\nimport type {\n LevelCompletePayload,\n LevelErrorPayload,\n LevelProgressPayload,\n} from '../events/payloads.ts';\n\nimport { Level } from '../classes/level.ts';\nimport { GameLevel } from '../classes/gameLevel.ts';\nimport type { EventBus } from '@skewedaspect/sage-core';\nimport type { GameEngine } from '../classes/gameEngine.ts';\nimport type { NodeEntityRegistry, NodeSimulation } from '../entities/nodeSimulation.ts';\n\n// Utils\nimport { LoggingUtility, NullBackend, SAGELogger } from '../utils/logger.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * The two options that make a transition something other than a plain move\n * (design/levels.md#transitions).\n */\nexport interface TransitionOptions extends LevelLoadOptions\n{\n /** Leaves the level being moved from loaded rather than disposing it. */\n keepAlive ?: boolean;\n\n /**\n * Builds the new level's scene and simulation and stops there, leaving the engine and the current level\n * alone until a later `activateLevel` binds what the preload built.\n */\n preloadOnly ?: boolean;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * The factory and the bookkeeping for levels: it registers configurations and `Level` subclasses, creates\n * level instances with the context each is handed, tracks which are loaded, and decides which one is current\n * (design/levels.md#the-level-manager). A game reaches levels through it rather than constructing one.\n *\n * ```typescript\n * levelManager.registerLevelConfig({ name: 'Level1', scene: 'level1.glb' });\n * levelManager.registerLevelClass('custom', MyCustomLevel);\n *\n * await levelManager.activateLevel('Level1');\n * ```\n */\nexport class LevelManager<Registry extends NodeEntityRegistry = NodeEntityRegistry> implements Disposable\n{\n private _bus : EventBus;\n private _gameEngine : GameEngine<Registry> | null = null;\n private _log : LoggerInterface;\n private _logger : LoggingUtility;\n\n /** Map of registered level configurations by name */\n private _levelConfigs = new Map<string, LevelConfig>();\n\n /** Map of registered custom level classes by name */\n private _levelClasses = new Map<string, LevelConstructor<Registry>>();\n\n /** Map of loaded level instances by name */\n private _loadedLevels = new Map<string, Level<Registry>>();\n\n /** Map of registered property handlers for scene node metadata */\n private _propertyHandlers = new Map<string, PropertyHandler>();\n\n /** The currently active level */\n private _currentLevel : Level<Registry> | null = null;\n\n /** The level currently inside load(), if any */\n private _loadingLevel : Level<Registry> | null = null;\n\n /** Unsubscribe functions for event subscriptions */\n private _eventUnsubscribers : (() => void)[] = [];\n\n //------------------------------------------------------------------------------------------------------------------\n\n /** The level the game is in, which is the one whose simulation the engine holds. */\n public get currentLevel() : Level<Registry> | null\n {\n return this._currentLevel;\n }\n\n /**\n * The level inside its own `load`, if any. `currentLevel` still names the outgoing level while an\n * incoming one builds its scene, so anything that needs the level the work is happening in -- a mesh\n * source building a mesh during spawn processing, say -- reads this instead (design/levels.md#loading).\n */\n public get loadingLevel() : Level<Registry> | null\n {\n return this._loadingLevel;\n }\n\n /** The handler map every level this manager loads dispatches its node walk through. */\n public get propertyHandlers() : Map<string, PropertyHandler>\n {\n return this._propertyHandlers;\n }\n\n //------------------------------------------------------------------------------------------------------------------\n\n constructor(\n bus : EventBus,\n logger ?: LoggingUtility\n )\n {\n this._bus = bus;\n this._logger = logger ?? new LoggingUtility('none', new NullBackend());\n this._log = logger?.getLogger('LevelManager') || new SAGELogger('LevelManager');\n\n this._eventUnsubscribers.push(\n this._bus.on('level:progress', (event) =>\n {\n this._handleProgress(event.payload);\n })\n );\n\n this._eventUnsubscribers.push(\n this._bus.on('level:complete', (event) =>\n {\n this._handleComplete(event.payload);\n })\n );\n\n this._eventUnsubscribers.push(\n this._bus.on('level:error', (event) =>\n {\n this._handleError(event.payload);\n })\n );\n\n this._log.info('LevelManager initialized');\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Internal API\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Hands the manager the engine every level context is read off. The engine takes its managers as\n * constructor arguments, so this manager exists before the engine does; `createGameEngine` hands it one\n * the moment the engine is built, before any level can load.\n */\n $setGameEngine(gameEngine : GameEngine<Registry>) : void\n {\n this._gameEngine = gameEngine;\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Private Methods\n //------------------------------------------------------------------------------------------------------------------\n\n private _handleProgress(payload : LevelProgressPayload) : void\n {\n this._log.debug(`Level ${ payload.levelName } progress: ${ payload.progress }%`);\n }\n\n private _handleComplete(payload : LevelCompletePayload) : void\n {\n this._log.info(`Level ${ payload.levelName } loaded successfully`);\n }\n\n private _handleError(payload : LevelErrorPayload) : void\n {\n this._log.error(`Level ${ payload.levelName } error: ${ payload.message }`);\n }\n\n // Every dependency a level has, by name, and no engine (design/levels.md#what-a-level-is-handed).\n private _createContext(config : LevelConfig) : LevelContext<Registry>\n {\n const engine = this._gameEngine;\n\n if(!engine)\n {\n throw new Error('LevelManager: gameEngine not set. Call $setGameEngine() first.');\n }\n\n return {\n bus: this._bus,\n sceneEngine: engine.engines.sceneEngine,\n assetManager: engine.managers.assetManager,\n audioManager: engine.managers.audioManager,\n rebuildSimulation: (scene, meshSource) => engine.rebuildSimulation(scene, config.name, {\n meshSource,\n fixedStepDuration: config.fixedStepDuration,\n }),\n canvas: engine.canvas,\n largeWorldRendering: engine.largeWorldRendering,\n propertyHandlers: this._propertyHandlers,\n logger: this._logger,\n };\n }\n\n // A configuration naming a class is built with the class registered under that name; one naming none is\n // built as a GameLevel (design/levels.md#configurations-and-classes).\n private _createLevelInstance(config : LevelConfig) : Level<Registry>\n {\n const context = this._createContext(config);\n\n if(config.class)\n {\n const LevelClass = this._levelClasses.get(config.class);\n if(!LevelClass)\n {\n throw new Error(`Level class '${ config.class }' is not registered.`);\n }\n\n this._log.debug(`Creating level '${ config.name }' using class '${ config.class }'`);\n return new LevelClass(config, context);\n }\n\n this._log.debug(`Creating level '${ config.name }' using GameLevel`);\n return new GameLevel<Registry>(config, context);\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Configuration Registration API\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Registers a level configuration under its own name. Registering over a name already registered warns\n * and replaces it (design/levels.md#configurations-and-classes).\n */\n public registerLevelConfig(config : LevelConfig) : void\n {\n if(this._levelConfigs.has(config.name))\n {\n this._log.warn(`Level config '${ config.name }' is already registered. Overwriting.`);\n }\n\n this._levelConfigs.set(config.name, config);\n this._log.info(`Registered level config: ${ config.name }`);\n }\n\n /**\n * Registers a `Level` subclass under a name, which a configuration's own `class` field then names.\n * Registering over a name already registered warns and replaces it\n * (design/levels.md#configurations-and-classes).\n */\n public registerLevelClass(name : string, levelClass : LevelConstructor<Registry>) : void\n {\n if(this._levelClasses.has(name))\n {\n this._log.warn(`Level class '${ name }' is already registered. Overwriting.`);\n }\n\n this._levelClasses.set(name, levelClass);\n this._log.info(`Registered level class: ${ name }`);\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Property Handler API\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Registers a handler for one node property, called for every node whose metadata carries that property.\n * The map belongs to the manager, so a handler registered once runs for every level it loads afterwards\n * (design/authoring.md#registering-a-handler).\n */\n public registerPropertyHandler(property : string, handler : PropertyHandler) : void\n {\n if(this._propertyHandlers.has(property))\n {\n this._log.warn(`Property handler '${ property }' is already registered. Overwriting.`);\n }\n\n this._propertyHandlers.set(property, handler);\n this._log.debug(`Registered property handler: ${ property }`);\n }\n\n /** Drops the handler registered under one property name. */\n public unregisterPropertyHandler(property : string) : void\n {\n this._propertyHandlers.delete(property);\n }\n\n /** Drops every registered handler. */\n public clearAllPropertyHandlers() : void\n {\n this._propertyHandlers.clear();\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Level Loading API\n //------------------------------------------------------------------------------------------------------------------\n\n /** The level loaded under this name, or nothing when none is loaded under it. */\n public getLevel(name : string) : Level<Registry> | null\n {\n return this._loadedLevels.get(name) || null;\n }\n\n /** The configuration registered under this name, or nothing when none is. */\n public getLevelConfig(name : string) : LevelConfig | null\n {\n return this._levelConfigs.get(name) || null;\n }\n\n /**\n * Loads the level a registered name points at, or the one a configuration carries, registering that\n * configuration as it goes. A level already loaded answers with the instance already loaded, whatever\n * options the call carries (design/levels.md#loading).\n */\n public async loadLevel(\n configOrName : string | LevelConfig,\n options ?: LevelLoadOptions\n ) : Promise<Level<Registry>>\n {\n let config : LevelConfig;\n\n if(typeof configOrName === 'string')\n {\n const registered = this._levelConfigs.get(configOrName);\n if(!registered)\n {\n throw new Error(`Level config '${ configOrName }' is not registered.`);\n }\n\n config = registered;\n }\n else\n {\n config = configOrName;\n this.registerLevelConfig(config);\n }\n\n const existing = this._loadedLevels.get(config.name);\n if(existing)\n {\n this._log.warn(`Level '${ config.name }' is already loaded`);\n return existing;\n }\n\n this._log.debug(`Loading level: ${ config.name }`);\n const level = this._createLevelInstance(config);\n\n // Loading builds the level's own simulation and binds nothing: only activation binds, and a preloaded\n // level stays inert until then (design/simulation.md#which-simulation-the-engine-holds).\n this._loadingLevel = level;\n try\n {\n await level.load(options);\n }\n catch (error)\n {\n // A load that fails part way has already built a scene and possibly spawned into a simulation;\n // neither is reachable through this manager once the level is not stored, so release both here.\n await level.dispose();\n throw error;\n }\n finally\n {\n this._loadingLevel = null;\n }\n\n this._loadedLevels.set(config.name, level);\n\n return level;\n }\n\n /** Binds the engine to a level's own simulation. A level that built none leaves the engine as it is. */\n private _bindSimulation(level : Level<Registry>) : void\n {\n if(this._gameEngine === null || level.$simulation === null)\n {\n return;\n }\n\n this._gameEngine.bindSimulation(level.$simulation);\n }\n\n /**\n * Hands the current level a simulation built to replace its own, and binds the engine to it. A save load\n * restores into a fresh simulation, and the level that owns the scene has to follow it.\n */\n $bindSimulation(simulation : NodeSimulation<Registry>) : void\n {\n if(this._currentLevel !== null)\n {\n this._currentLevel.$adoptSimulation(simulation);\n }\n\n if(this._gameEngine !== null)\n {\n this._gameEngine.bindSimulation(simulation);\n }\n }\n\n /**\n * Makes a level current, loading it first when it is not loaded, and binds the engine to that level's\n * simulation. The outgoing level's `onDeactivate` and the incoming one's `onActivate` are both awaited\n * (design/levels.md#activation-and-unloading).\n */\n public async activateLevel(configOrName : string | LevelConfig) : Promise<Level<Registry>>\n {\n const level = await this.loadLevel(configOrName);\n\n // Re-activating the level already current rebinds the engine and nothing else: firing the hooks\n // would pause and resume a level that never stopped (design/levels.md#activation-and-unloading).\n if(this._currentLevel === level)\n {\n this._bindSimulation(level);\n return level;\n }\n\n await this._deactivate(this._currentLevel);\n\n this._currentLevel = level;\n this._bindSimulation(level);\n\n if(level.onActivate)\n {\n await level.onActivate();\n }\n\n this._log.info(`Activated level: ${ level.name }`);\n return level;\n }\n\n /** A level's own deactivation, for the two places that leave a level: an activation and a transition. */\n private async _deactivate(level : Level<Registry> | null) : Promise<void>\n {\n if(level?.onDeactivate)\n {\n await level.onDeactivate();\n }\n }\n\n /**\n * Disposes a level and forgets it, clearing the current level when it was the one\n * (design/levels.md#activation-and-unloading).\n */\n public async unloadLevel(name : string) : Promise<void>\n {\n const level = this._loadedLevels.get(name);\n if(level)\n {\n // Unbind the engine with it: a disposed level's simulation left on the engine is one the render\n // loop keeps stepping (design/levels.md#activation-and-unloading).\n if(this._currentLevel === level)\n {\n this._currentLevel = null;\n\n if(this._gameEngine !== null)\n {\n this._gameEngine.bindSimulation(null);\n }\n }\n\n await level.dispose();\n this._loadedLevels.delete(name);\n\n this._log.info(`Unloaded level: ${ name }`);\n }\n }\n\n /** Unloads whichever level is current, and warns when none is. */\n public async unloadCurrentLevel() : Promise<void>\n {\n if(this._currentLevel)\n {\n await this.unloadLevel(this._currentLevel.name);\n }\n else\n {\n this._log.warn('No current level to unload');\n }\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Transitions\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Moves the game from the level that is current to the one named, loading the new level before the old\n * one is disposed and publishing the transition's own events as it goes\n * (design/levels.md#transitions).\n */\n public async transition(levelName : string, options ?: TransitionOptions) : Promise<void>\n {\n const oldLevel = this._currentLevel;\n\n // What the engine is put back on when the transition fails: the old level stays current, so it keeps\n // the simulation it owns (design/simulation.md#which-simulation-the-engine-holds).\n const previousSimulation = this._gameEngine?.simulation ?? null;\n\n // Whether this attempt ran the old level's own onDeactivate, so a later restore knows whether to run\n // its onActivate back (design/levels.md#transitions).\n let deactivatedOldLevel = false;\n\n try\n {\n this._bus.emit({\n type: 'level:transition:start',\n senderID: 'levelManager',\n payload: {\n from: oldLevel?.name,\n to: levelName,\n },\n });\n\n this._bus.emit({\n type: 'level:transition:progress',\n senderID: 'levelManager',\n payload: {\n stage: 'loading',\n levelName,\n },\n });\n\n // A transition to the level already current is a restart, and a restore needs a level with none\n // of its own entities: both mean the loaded instance has to go before the load, or loadLevel\n // answers with the scene the game has been playing in (design/levels.md#transitions).\n const loadedTarget = this._loadedLevels.get(levelName) ?? null;\n const rebuilds = loadedTarget !== null && (options?.forRestore === true || loadedTarget === oldLevel);\n const rebuildsOldLevel = rebuilds && loadedTarget === oldLevel;\n\n if(rebuildsOldLevel)\n {\n // The level being left is the one about to be disposed and built again, so this is the last\n // moment its own deactivation has a level to run on.\n await this._deactivate(oldLevel);\n }\n\n if(rebuilds)\n {\n await this.unloadLevel(levelName);\n }\n\n const newLevel = await this.loadLevel(levelName, options);\n\n if(options?.preloadOnly === true)\n {\n return;\n }\n\n if(!rebuildsOldLevel)\n {\n // Past the point where the transition can still be called off: a preload returns above and a\n // failed load throws before here, and both leave the old level current and playing what it\n // was playing, with nothing behind them to resume it (design/levels.md#transitions).\n await this._deactivate(oldLevel);\n deactivatedOldLevel = true;\n }\n\n // A rebuild already disposed the old level, and the level loaded above carries the same name, so\n // unloading by name here would dispose the new one instead.\n if(oldLevel !== null && !rebuildsOldLevel && options?.keepAlive !== true)\n {\n await this.unloadLevel(oldLevel.name);\n }\n\n this._currentLevel = newLevel;\n this._bindSimulation(newLevel);\n\n if(newLevel.onActivate)\n {\n await newLevel.onActivate();\n }\n\n this._bus.emit({\n type: 'level:transition:complete',\n senderID: 'levelManager',\n payload: { levelName },\n });\n }\n catch (error)\n {\n // Nothing was activated, so the level that was current is restored as current, with its own\n // simulation rebound -- but only while that level is still loaded. A restart's reload and a\n // non-keepAlive transition's onActivate both throw after that level was already disposed, and\n // past that point there is no loaded level to put back (design/levels.md#transitions).\n if(oldLevel !== null && oldLevel.isLoaded)\n {\n this._currentLevel = oldLevel;\n\n if(this._gameEngine !== null)\n {\n this._gameEngine.bindSimulation(previousSimulation);\n }\n\n // A keepAlive transition leaves this level deactivated but loaded: resume what its\n // deactivation paused (design/levels.md#transitions).\n if(deactivatedOldLevel && oldLevel.onActivate)\n {\n await oldLevel.onActivate();\n }\n }\n else\n {\n this._currentLevel = null;\n\n if(this._gameEngine !== null)\n {\n this._gameEngine.bindSimulation(null);\n }\n }\n\n this._bus.emit({\n type: 'level:transition:error',\n senderID: 'levelManager',\n payload: {\n from: oldLevel?.name,\n to: levelName,\n error,\n },\n });\n\n throw error;\n }\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Lifecycle\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * Unsubscribes the manager from the level events it listens to, disposes every loaded level, and forgets\n * the loaded levels, the registered configurations, and the current one (design/levels.md#teardown).\n */\n async dispose() : Promise<void>\n {\n for(const unsubscribe of this._eventUnsubscribers)\n {\n unsubscribe();\n }\n\n this._eventUnsubscribers = [];\n\n for(const level of this._loadedLevels.values())\n {\n // eslint-disable-next-line no-await-in-loop\n await level.dispose();\n }\n\n this._loadedLevels.clear();\n this._levelConfigs.clear();\n this._currentLevel = null;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Audio Manager\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { AudioBus, IStaticSoundOptions, StaticSound } from '@babylonjs/core';\n\n// Engines\nimport type { AudioEngine } from '../engines/audio.ts';\n\n// Interfaces\nimport type { Disposable } from '../interfaces/lifecycle.ts';\nimport type { LoggerInterface } from '../interfaces/logger.ts';\n\n// Utils\nimport type { LoggingUtility } from '../utils/logger.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\ninterface ChannelState\n{\n bus : AudioBus;\n volume : number;\n muted : boolean;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport class AudioManager implements Disposable\n{\n #audioEngine : AudioEngine;\n #channels = new Map<string, ChannelState>();\n #masterMuted = false;\n #masterVolumeBeforeMute = 1;\n #log : LoggerInterface;\n\n constructor(audioEngine : AudioEngine, logger : LoggingUtility)\n {\n this.#audioEngine = audioEngine;\n this.#log = logger.getLogger('AudioManager');\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Lifecycle\n //------------------------------------------------------------------------------------------------------------------\n\n async initialize(channels : string[]) : Promise<void>\n {\n this.#log.info(`Initializing audio channels: ${ channels.join(', ') }`);\n\n for(const name of channels)\n {\n // eslint-disable-next-line no-await-in-loop\n const bus = await this.#audioEngine.createBus(name);\n this.#channels.set(name, { bus, volume: 1, muted: false });\n }\n\n this.#log.info('Audio channels initialized.');\n }\n\n async dispose() : Promise<void>\n {\n this.#log.info('Tearing down audio manager...');\n\n for(const state of this.#channels.values())\n {\n state.bus.dispose();\n }\n\n this.#channels.clear();\n this.#log.info('Audio manager torn down.');\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Sound Creation\n //------------------------------------------------------------------------------------------------------------------\n\n async createSound(\n name : string,\n url : string,\n channel ?: string,\n options ?: Partial<IStaticSoundOptions>\n ) : Promise<StaticSound>\n {\n let bus : AudioBus | undefined;\n\n if(channel)\n {\n const state = this.#channels.get(channel);\n if(!state)\n {\n throw new Error(\n `AudioManager: no channel named \"${ channel }\" is declared; sound \"${ name }\" cannot be created.`\n );\n }\n\n bus = state.bus;\n }\n\n return this.#audioEngine.createSound(name, url, bus, options);\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Master Volume\n //------------------------------------------------------------------------------------------------------------------\n\n get masterVolume() : number\n {\n if(this.#masterMuted)\n {\n return this.#masterVolumeBeforeMute;\n }\n\n return this.#audioEngine.getMasterVolume();\n }\n\n set masterVolume(volume : number)\n {\n if(!this.#masterMuted)\n {\n this.#audioEngine.setMasterVolume(volume);\n }\n else\n {\n // Store for when we unmute\n this.#masterVolumeBeforeMute = volume;\n }\n }\n\n get masterMuted() : boolean\n {\n return this.#masterMuted;\n }\n\n set masterMuted(muted : boolean)\n {\n if(muted === this.#masterMuted) { return; }\n\n this.#masterMuted = muted;\n\n if(muted)\n {\n this.#masterVolumeBeforeMute = this.#audioEngine.getMasterVolume();\n this.#audioEngine.setMasterVolume(0);\n }\n else\n {\n this.#audioEngine.setMasterVolume(this.#masterVolumeBeforeMute);\n }\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Channel Volume\n //------------------------------------------------------------------------------------------------------------------\n\n setChannelVolume(channel : string, volume : number) : void\n {\n const state = this.#requireChannel(channel);\n\n state.volume = volume;\n\n if(!state.muted)\n {\n this.#audioEngine.setBusVolume(state.bus, volume);\n }\n }\n\n getChannelVolume(channel : string) : number\n {\n return this.#requireChannel(channel).volume;\n }\n\n setChannelMuted(channel : string, muted : boolean) : void\n {\n const state = this.#requireChannel(channel);\n\n if(muted === state.muted) { return; }\n\n state.muted = muted;\n\n if(muted)\n {\n this.#audioEngine.setBusVolume(state.bus, 0);\n }\n else\n {\n this.#audioEngine.setBusVolume(state.bus, state.volume);\n }\n }\n\n isChannelMuted(channel : string) : boolean\n {\n return this.#requireChannel(channel).muted;\n }\n\n get channels() : string[]\n {\n return Array.from(this.#channels.keys());\n }\n\n #requireChannel(channel : string) : ChannelState\n {\n const state = this.#channels.get(channel);\n if(!state)\n {\n throw new Error(`AudioManager: no channel named \"${ channel }\" is declared.`);\n }\n\n return state;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Simulation Save Manager\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { GameSimulationSnapshot, Unsubscribe } from '@skewedaspect/sage-core';\n\nimport type { GameEngine } from '../classes/gameEngine.ts';\nimport type { NodeEntityRegistry, NodeSimulation } from '../entities/nodeSimulation.ts';\nimport type { Disposable } from '../interfaces/lifecycle.ts';\nimport type { LevelLoadOptions } from '../interfaces/level.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// Changes whenever the snapshot's fields change (design/simulation.md#saved-games). Version 2 is the one whose\n// snapshot carries `randomState`.\nconst saveFormatVersion = 2;\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport interface LevelLoader<Registry extends NodeEntityRegistry = NodeEntityRegistry>\n{\n readonly currentLevel : { name : string } | null;\n transition(levelName : string, options ?: LevelLoadOptions) : Promise<void>;\n $bindSimulation(simulation : NodeSimulation<Registry>) : void;\n}\n\nexport interface SaveEnvelope\n{\n saveFormatVersion : number;\n engineVersion : string;\n level : string;\n timestamp : number;\n custom : Record<string, unknown>;\n snapshot : GameSimulationSnapshot;\n}\n\nexport type BeforeSaveHook = () => Record<string, unknown>;\n\nexport interface LoadResult<Registry extends NodeEntityRegistry = NodeEntityRegistry>\n{\n simulation : NodeSimulation<Registry>;\n envelope : SaveEnvelope;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport class SimulationSaveManager<Registry extends NodeEntityRegistry = NodeEntityRegistry> implements Disposable\n{\n readonly #levelLoader : LevelLoader<Registry>;\n readonly #engineVersion : string;\n readonly #now : () => number;\n\n readonly #beforeSaveHooks : BeforeSaveHook[] = [];\n\n #gameEngine : GameEngine<Registry> | null = null;\n\n constructor(levelLoader : LevelLoader<Registry>, engineVersion : string, now : () => number = Date.now)\n {\n this.#levelLoader = levelLoader;\n this.#engineVersion = engineVersion;\n this.#now = now;\n }\n\n /**\n * Late-bind the GameEngine after construction. Called by createGameEngine() after the GameEngine\n * instance is created; load() needs it to reach the simulation a transition just rebuilt.\n */\n $setGameEngine(gameEngine : GameEngine<Registry>) : void\n {\n this.#gameEngine = gameEngine;\n }\n\n // Any number of hooks, in registration order, each answering with the fields it owns; the answer is what removes\n // the one just registered (design/engine.md#lifecycle-hooks).\n onBeforeSave(hook : BeforeSaveHook) : Unsubscribe\n {\n this.#beforeSaveHooks.push(hook);\n\n return () : void =>\n {\n const index = this.#beforeSaveHooks.indexOf(hook);\n\n if(index >= 0)\n {\n this.#beforeSaveHooks.splice(index, 1);\n }\n };\n }\n\n // Saves the simulation the engine holds, the one a load restores into (design/simulation.md#saved-games).\n save() : SaveEnvelope\n {\n const simulation = this.#engineSimulation();\n const custom : Record<string, unknown> = {};\n\n for(const hook of this.#beforeSaveHooks)\n {\n for(const [ field, value ] of Object.entries(hook()))\n {\n if(Object.hasOwn(custom, field))\n {\n throw new Error(\n `SimulationSaveManager: two onBeforeSave hooks answered with the field \"${ field }\".`\n );\n }\n\n custom[field] = value;\n }\n }\n\n return {\n saveFormatVersion,\n engineVersion: this.#engineVersion,\n level: this.#levelLoader.currentLevel?.name ?? '',\n timestamp: this.#now(),\n custom,\n snapshot: simulation.snapshot(),\n };\n }\n\n async load(envelope : SaveEnvelope) : Promise<LoadResult<Registry>>\n {\n if(envelope.saveFormatVersion !== saveFormatVersion)\n {\n throw new Error(\n `The save format version \"${ envelope.saveFormatVersion }\" is not one this engine knows how to load.`\n );\n }\n\n this.#requireEngine();\n\n await this.#levelLoader.transition(envelope.level, { forRestore: true });\n\n // The transition just rebuilt the level's scene and, with it, a fresh NodeSimulation bound to that\n // scene (GameEngine.rebuildSimulation) -- buildFromSnapshot has to run on that one, not on whatever\n // simulation was current before the transition, or the restored entities carry a disposed scene.\n const freshSimulation = this.#engineSimulation();\n\n const restored = freshSimulation.buildFromSnapshot(envelope.snapshot);\n\n // The level that owns the scene has to follow the restored simulation, or its own spawns and\n // despawns keep going to the one the transition built\n // (design/simulation.md#which-simulation-the-engine-holds).\n this.#levelLoader.$bindSimulation(restored);\n\n return { simulation: restored, envelope };\n }\n\n async dispose() : Promise<void>\n {\n this.#beforeSaveHooks.length = 0;\n }\n\n #requireEngine() : GameEngine<Registry>\n {\n if(this.#gameEngine === null)\n {\n throw new Error('SimulationSaveManager: gameEngine not set. Call $setGameEngine() first.');\n }\n\n return this.#gameEngine;\n }\n\n #engineSimulation() : NodeSimulation<Registry>\n {\n const simulation = this.#requireEngine().simulation;\n\n if(simulation === null)\n {\n throw new Error('SimulationSaveManager: the engine holds no simulation.');\n }\n\n return simulation;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Collider Debug Manager\n//\n// Wraps BabylonJS PhysicsViewer to provide per-node collider debug visualization with color support.\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { type AbstractMesh, Color3, StandardMaterial, type TransformNode } from '@babylonjs/core';\nimport { PhysicsViewer } from '@babylonjs/core/Debug/physicsViewer';\n\nimport type { Disposable } from '../interfaces/lifecycle.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport type ColliderDebugConfig = boolean | string | { color : string } | { nodes : Record<string, string | false> };\n\n//----------------------------------------------------------------------------------------------------------------------\n\ninterface ShownEntry\n{\n debugMesh : AbstractMesh;\n colorMaterial ?: StandardMaterial;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport class ColliderDebugManager implements Disposable\n{\n private _viewer : PhysicsViewer | null = null;\n private _shown = new Map<TransformNode, ShownEntry>();\n private _materials = new Map<string, StandardMaterial>();\n\n //------------------------------------------------------------------------------------------------------------------\n // Public API\n //------------------------------------------------------------------------------------------------------------------\n\n showColliderForNode(node : TransformNode, color ?: string) : void\n {\n if(!node.physicsBody || this._shown.has(node))\n {\n return;\n }\n\n const viewer = this._getViewer(node);\n const debugMesh = viewer.showBody(node.physicsBody);\n\n if(!debugMesh)\n {\n return;\n }\n\n const entry : ShownEntry = { debugMesh };\n\n if(color)\n {\n const material = this._getColorMaterial(color, node);\n debugMesh.material = material;\n entry.colorMaterial = material;\n }\n\n this._shown.set(node, entry);\n }\n\n hideColliderForNode(node : TransformNode) : void\n {\n const entry = this._shown.get(node);\n if(!entry || !this._viewer)\n {\n return;\n }\n\n if(node.physicsBody)\n {\n this._viewer.hideBody(node.physicsBody);\n }\n\n this._shown.delete(node);\n }\n\n hideAll() : void\n {\n for(const [ node ] of this._shown)\n {\n this.hideColliderForNode(node);\n }\n }\n\n isShown(node : TransformNode) : boolean\n {\n return this._shown.has(node);\n }\n\n async dispose() : Promise<void>\n {\n if(this._viewer)\n {\n this.hideAll();\n this._viewer.dispose();\n this._viewer = null;\n }\n\n this._shown.clear();\n\n for(const mat of this._materials.values())\n {\n mat.dispose();\n }\n\n this._materials.clear();\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Private\n //------------------------------------------------------------------------------------------------------------------\n\n private _getViewer(node : TransformNode) : PhysicsViewer\n {\n if(!this._viewer)\n {\n this._viewer = new PhysicsViewer(node.getScene());\n }\n\n return this._viewer;\n }\n\n private _getColorMaterial(color : string, node : TransformNode) : StandardMaterial\n {\n let material = this._materials.get(color);\n if(material)\n {\n return material;\n }\n\n material = new StandardMaterial(`sage-collider-debug-${ color }`, node.getScene());\n material.emissiveColor = Color3.FromHexString(color);\n material.disableLighting = true;\n material.wireframe = true;\n this._materials.set(color, material);\n\n return material;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Graphics Utility\n//----------------------------------------------------------------------------------------------------------------------\n\nimport {\n AbstractEngine,\n Engine,\n type EngineOptions,\n NullEngine,\n type NullEngineOptions,\n WebGPUEngine,\n type WebGPUEngineOptions,\n} from '@babylonjs/core';\n\n// Interfaces\nimport type { BabylonEngineOptions, GameCanvas, RenderEngineOptions } from '../interfaces/game.ts';\nimport type { LoggerInterface } from '../interfaces/logger.ts';\n\n// Utilities\nimport { hasWebGPU } from './capabilities.ts';\nimport { SAGELogger } from './logger.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Whether a WebGPU engine can be built here.\n *\n * `hasWebGPU()` only reports that the browser exposes the API. Headless and software-rendering browsers\n * expose it and then hand back no adapter, and Babylon's WebGPU engine claims its window listeners in the\n * constructor, before `initAsync()` fails -- so an engine built on the weaker answer and then dropped holds\n * them for the life of the page. `dispose()` cannot take them back: it reaches for a GPU device that a\n * failure this early never created.\n */\nasync function _canBuildWebGPUEngine() : Promise<boolean>\n{\n if(!hasWebGPU())\n {\n return false;\n }\n\n return WebGPUEngine.IsSupportedAsync;\n}\n\n/**\n * Builds and initializes a WebGPU engine.\n */\nasync function _buildWebGPUEngine(\n canvas : HTMLCanvasElement | OffscreenCanvas,\n options : WebGPUEngineOptions\n) : Promise<WebGPUEngine>\n{\n const engine = new WebGPUEngine(canvas, options);\n await engine.initAsync();\n return engine;\n}\n\n/**\n * Builds a WebGL engine.\n */\nfunction _buildWebGLEngine(canvas : GameCanvas, options : BabylonEngineOptions) : Engine\n{\n return new Engine(canvas, options.antialias, options.options, options.adaptToDeviceRatio);\n}\n\n/**\n * Builds a Null engine, which is used when no rendering is required.\n */\nfunction _buildNullEngine(options : NullEngineOptions) : NullEngine\n{\n return new NullEngine(options as NullEngineOptions);\n}\n\n//------------------------------------------------------------------------------------------------------------------\n\n/**\n * Creates an appropriate engine based on the provided canvas and options.\n * Passing null for canvas creates a NullEngine. Otherwise auto-detects WebGPU with WebGL fallback,\n * unless a specific engine type is forced via `options.engine`.\n *\n * @param canvas - Pass null to get a NullEngine (headless/testing)\n */\nexport async function createEngine(\n canvas : GameCanvas | null,\n options : RenderEngineOptions,\n largeWorldRendering = false,\n log : LoggerInterface = new SAGELogger('Boot')\n) : Promise<AbstractEngine>\n{\n // Check if we should use a null engine (no canvas provided)\n if(canvas === null)\n {\n log.debug('Using Null Engine');\n return _buildNullEngine(options as NullEngineOptions);\n }\n\n // Inject large world rendering into engine options if requested. We have to build new objects\n // here because BabylonJS declares useLargeWorldRendering as readonly on its option types.\n if(largeWorldRendering)\n {\n if('options' in options)\n {\n const babylonOpts = options as BabylonEngineOptions;\n options = {\n ...babylonOpts,\n options: { ...babylonOpts.options, useLargeWorldRendering: true },\n } as RenderEngineOptions;\n }\n else\n {\n // WebGPUEngineOptions -- useLargeWorldRendering lives at the top level\n options = { ...options, useLargeWorldRendering: true } as RenderEngineOptions;\n }\n }\n\n // Extract the forceEngine option if available\n const forceEngine = options.engine || 'auto';\n\n // Force WebGPU engine if specified and available\n if(forceEngine === 'webgpu')\n {\n if(await _canBuildWebGPUEngine())\n {\n try\n {\n log.debug('Using forced WebGPU engine');\n return await _buildWebGPUEngine(canvas, options);\n }\n catch (error)\n {\n log.error('Forced WebGPU initialization failed:', error);\n throw new Error(\n 'Forced WebGPU failed to initialize. If WebGPU is required, check browser compatibility.',\n { cause: error }\n );\n }\n }\n else\n {\n throw new Error('WebGPU was forced but is not available in this browser.');\n }\n }\n\n // Force WebGL engine if specified\n if(forceEngine === 'webgl')\n {\n log.debug('Using forced WebGL engine');\n return _buildWebGLEngine(canvas, options as BabylonEngineOptions);\n }\n\n // Auto detection (default behavior)\n if(await _canBuildWebGPUEngine())\n {\n try\n {\n log.debug('Using WebGPU');\n\n return await _buildWebGPUEngine(canvas, options);\n }\n catch (error)\n {\n log.warn('WebGPU initialization failed, falling back to WebGL:', error);\n }\n }\n else\n {\n log.warn('WebGPU not supported, falling back to WebGL.');\n }\n\n log.debug('Using WebGL');\n\n return _buildWebGLEngine(canvas, options as EngineOptions);\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n","//----------------------------------------------------------------------------------------------------------------------\n// GameTimer - Pause-aware timer utilities\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * A pollable cooldown handle.\n */\nexport interface CooldownHandle\n{\n readonly ready : boolean;\n reset() : void;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\ninterface DelayEntry\n{\n id : number;\n remaining : number;\n callback : () => void;\n}\n\ninterface IntervalEntry\n{\n id : number;\n periodMs : number;\n elapsed : number;\n callback : () => void;\n}\n\ninterface CooldownEntry\n{\n id : number;\n periodMs : number;\n remaining : number;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Pause-aware game timer providing delay, interval, and cooldown primitives.\n *\n * All times are in game-time milliseconds. When the game is paused, `tick()` is simply not called, so all timers\n * freeze automatically.\n */\nexport class GameTimer\n{\n private _nextId = 0;\n private _delays : DelayEntry[] = [];\n private _intervals : IntervalEntry[] = [];\n private _cooldowns : CooldownEntry[] = [];\n\n //------------------------------------------------------------------------------------------------------------------\n // Public API\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * One-shot timer. Fires the callback after `ms` milliseconds of game time.\n *\n * @returns A cancel function that prevents the callback from firing.\n */\n delay(ms : number, callback : () => void) : () => void\n {\n if(ms < 0)\n {\n throw new RangeError(`delay() requires ms >= 0, got ${ ms }`);\n }\n\n const id = this._nextId++;\n this._delays.push({ id, remaining: ms, callback });\n\n return () =>\n {\n this._delays = this._delays.filter((entry) => entry.id !== id);\n };\n }\n\n /**\n * Repeating timer. Fires the callback every `ms` milliseconds of game time.\n *\n * @returns A cancel function that stops the interval.\n */\n interval(ms : number, callback : () => void) : () => void\n {\n if(ms <= 0)\n {\n throw new RangeError(`interval() requires ms > 0, got ${ ms }`);\n }\n\n const id = this._nextId++;\n this._intervals.push({ id, periodMs: ms, elapsed: 0, callback });\n\n return () =>\n {\n this._intervals = this._intervals.filter((entry) => entry.id !== id);\n };\n }\n\n /**\n * Pollable cooldown. Starts ready; after calling `reset()`, becomes not-ready until `ms` elapses.\n */\n cooldown(ms : number) : CooldownHandle\n {\n const id = this._nextId++;\n const entry : CooldownEntry = { id, periodMs: ms, remaining: 0 };\n this._cooldowns.push(entry);\n\n return {\n get ready() : boolean\n {\n return entry.remaining <= 0;\n },\n reset() : void\n {\n entry.remaining = entry.periodMs;\n },\n };\n }\n\n /**\n * Cancel all active delays and intervals, and ready every cooldown.\n */\n cancelAll() : void\n {\n this._delays = [];\n this._intervals = [];\n\n // A cooldown outlives this call: only its remaining time resets, so one taken before this call still\n // counts down after it.\n for(const entry of this._cooldowns)\n {\n entry.remaining = 0;\n }\n }\n\n /**\n * Advance all timers by `dtMs` milliseconds. Called once per frame by the game loop.\n */\n tick(dtMs : number) : void\n {\n // Process delays\n const firedDelays : number[] = [];\n for(const entry of this._delays)\n {\n entry.remaining -= dtMs;\n if(entry.remaining <= 0)\n {\n entry.callback();\n firedDelays.push(entry.id);\n }\n }\n\n if(firedDelays.length > 0)\n {\n this._delays = this._delays.filter((entry) => !firedDelays.includes(entry.id));\n }\n\n // Process intervals\n for(const entry of this._intervals)\n {\n entry.elapsed += dtMs;\n while(entry.elapsed >= entry.periodMs)\n {\n entry.callback();\n entry.elapsed -= entry.periodMs;\n }\n }\n\n // Process cooldowns\n for(const entry of this._cooldowns)\n {\n if(entry.remaining > 0)\n {\n entry.remaining = Math.max(0, entry.remaining - dtMs);\n }\n }\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Built-in Debug Console Commands\n//----------------------------------------------------------------------------------------------------------------------\n\n/* eslint-disable no-console */\n\nimport type { Vector3 } from '@babylonjs/core';\nimport { NamedBehavior } from '@skewedaspect/sage-core';\nimport type { EventSubscription, GameEntity, Unsubscribe } from '@skewedaspect/sage-core';\n\n// Classes\nimport type { DebugConsole } from '../classes/debugConsole.ts';\nimport type { GameEngine } from '../classes/gameEngine.ts';\n\n// Entities\nimport { NodeEntity } from '../entities/nodeEntity.ts';\nimport type { NodeEntityState } from '../entities/nodeEntity.ts';\nimport type { NodeEntityRegistry, NodeSimulation } from '../entities/nodeSimulation.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nfunction requireSimulation<Registry extends NodeEntityRegistry>(\n ge : GameEngine<Registry>\n) : NodeSimulation<Registry> | undefined\n{\n if(ge.simulation === null)\n {\n console.warn('[sage] No simulation yet -- nothing has given the engine a scene to build one against.');\n return undefined;\n }\n\n return ge.simulation;\n}\n\nfunction resolveEntity<Registry extends NodeEntityRegistry>(\n idOrEntity : string | GameEntity,\n ge : GameEngine<Registry>\n) : GameEntity | undefined\n{\n if(typeof idOrEntity === 'string')\n {\n return requireSimulation(ge)?.getEntity(idOrEntity);\n }\n\n return idOrEntity;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport function registerBuiltins<Registry extends NodeEntityRegistry>(\n dc : DebugConsole,\n ge : GameEngine<Registry>\n) : void\n{\n const gm = ge.managers.gameManager;\n const lm = ge.managers.levelManager;\n\n const activeLoggers : Unsubscribe[] = [];\n\n //------------------------------------------------------------------------------------------------------------------\n // Exposed references\n //------------------------------------------------------------------------------------------------------------------\n\n dc.expose('engine', ge);\n dc.expose('events', ge.bus);\n // The current scene's own engine, since physics belongs to a scene rather than to the game\n // (design/physics.md#the-physics-engine). Null on a scene that never asked for it.\n dc.expose('physics', { get: () => lm.currentLevel?.scene?.getPhysicsEngine() ?? null });\n dc.expose('colliders', ge.debug.colliders);\n\n dc.expose('entities', new Proxy({} as Record<string, unknown>, {\n get(_target : Record<string, unknown>, prop : string | symbol) : unknown\n {\n if(typeof prop === 'symbol')\n {\n return undefined;\n }\n\n const entity = requireSimulation(ge)?.getEntity(prop);\n if(!entity)\n {\n console.warn(`[sage] Entity not found: ${ prop }`);\n }\n\n return entity;\n },\n }));\n\n dc.expose('level', { get: () => lm.currentLevel });\n dc.expose('scene', { get: () => lm.currentLevel?.scene ?? null });\n\n //------------------------------------------------------------------------------------------------------------------\n // Commands\n //------------------------------------------------------------------------------------------------------------------\n\n dc.expose('tp', (idOrEntity : string | GameEntity, x : number, y : number, z : number) =>\n {\n const entity = resolveEntity(idOrEntity, ge);\n if(!entity)\n {\n console.warn(`[sage] Entity not found: ${ idOrEntity }`);\n return;\n }\n\n if(!(entity instanceof NodeEntity))\n {\n console.warn(`[sage] Entity \"${ entity.id }\" has no node`);\n return;\n }\n\n // Written through state, which is the node's own position: a computed field, so the write lands on the\n // node as it is made, for every entity alike (design/debugging.md#the-built-in-commands).\n const spatial = entity.state as NodeEntityState;\n\n spatial.position = { x, y, z };\n\n console.log(`[sage] Teleported \"${ entity.id }\" to (${ x }, ${ y }, ${ z })`);\n });\n\n dc.expose('spawn', (name : string, x : number, y : number, z : number) =>\n {\n const simulation = requireSimulation(ge);\n if(!simulation)\n {\n return undefined;\n }\n\n // A name typed at the console is checked by the registry at run time (design/simulation.md#spawn-and-despawn).\n const entity = simulation.spawnByName(name, { position: { x, y, z } });\n console.log(`[sage] Spawned \"${ name }\" (${ entity.id }) at (${ x }, ${ y }, ${ z })`);\n return entity;\n });\n\n dc.expose('list', (name ?: string, tag ?: string) : GameEntity[] =>\n {\n const simulation = requireSimulation(ge);\n if(!simulation)\n {\n return [];\n }\n\n let entities : readonly GameEntity[] = simulation.entities;\n\n if(name !== undefined)\n {\n entities = entities.filter((ent) => ent.name === name);\n }\n\n if(tag !== undefined)\n {\n entities = entities.filter((ent) => (ent.state as { tags : string[] }).tags.includes(tag));\n }\n\n const rows = entities.map((ent) =>\n {\n let nodeName = '(no node)';\n\n if(ent instanceof NodeEntity)\n {\n nodeName = ent.node.name;\n }\n\n const row : Record<string, unknown> = {\n id: ent.id,\n name: ent.name,\n node: nodeName,\n };\n\n if(ent.hasBehavior(NamedBehavior))\n {\n row.displayName = ent.state.displayName;\n }\n\n return row;\n });\n\n console.table(rows);\n return [ ...entities ];\n });\n\n dc.expose('inspect', (idOrEntity : string | GameEntity) =>\n {\n const entity = resolveEntity(idOrEntity, ge);\n if(!entity)\n {\n console.warn(`[sage] Entity not found: ${ idOrEntity }`);\n return undefined;\n }\n\n let nodeName : string | null = null;\n let position : Vector3 | null = null;\n\n if(entity instanceof NodeEntity)\n {\n nodeName = entity.node.name;\n position = entity.node.position;\n }\n\n const info = {\n id: entity.id,\n state: entity.state,\n node: nodeName,\n position,\n };\n\n console.log('[sage] Entity info:', info);\n return info;\n });\n\n dc.expose('logEvents', (pattern ?: string) =>\n {\n if(pattern === undefined)\n {\n throw new Error('logEvents takes an event type or a pattern, such as \"level:*\".');\n }\n\n // The console takes any string; a pattern outside the map hears nothing.\n const resolvedPattern = pattern as EventSubscription;\n\n const unsub = ge.bus.on(resolvedPattern, (event) =>\n {\n console.log(`[sage] Event: ${ event.type }`, event.payload);\n });\n\n activeLoggers.push(unsub);\n console.log(`[sage] Logging events matching \"${ resolvedPattern }\"`);\n return unsub;\n });\n\n dc.expose('stopLogging', () =>\n {\n for(const unsub of activeLoggers)\n {\n unsub();\n }\n\n console.log(`[sage] Stopped ${ activeLoggers.length } event logger(s)`);\n activeLoggers.length = 0;\n });\n\n dc.expose('pause', () =>\n {\n gm.pause('debug console');\n console.log('[sage] Game paused');\n });\n\n dc.expose('resume', () =>\n {\n gm.resume('debug console');\n console.log('[sage] Game resumed');\n });\n\n dc.expose('slow', (factor : number) =>\n {\n gm.timeScale = factor;\n console.log(`[sage] Time scale set to ${ factor }`);\n });\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Logger Interfaces\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Defines the available logging levels as string literals.\n */\nexport const LogLevels = [\n 'trace',\n 'debug',\n 'info',\n 'warn',\n 'error',\n 'none',\n] as const;\n\n/**\n * Type for LogLevel values\n */\nexport type LogLevel = typeof LogLevels[number];\n\n/**\n * Interface for a logging backend implementation.\n * Backends handle the actual output of log messages.\n */\nexport interface LoggingBackend\n{\n /**\n * Log a message at TRACE level\n *\n * @param category - Typically the class or module name\n */\n trace(category : string, message : string, ...args : unknown[]) : void;\n\n /**\n * Log a message at DEBUG level\n *\n * @param category - Typically the class or module name\n */\n debug(category : string, message : string, ...args : unknown[]) : void;\n\n /**\n * Log a message at INFO level\n *\n * @param category - Typically the class or module name\n */\n info(category : string, message : string, ...args : unknown[]) : void;\n\n /**\n * Log a message at WARN level\n *\n * @param category - Typically the class or module name\n */\n warn(category : string, message : string, ...args : unknown[]) : void;\n\n /**\n * Log a message at ERROR level\n *\n * @param category - Typically the class or module name\n */\n error(category : string, message : string, ...args : unknown[]) : void;\n\n /**\n * Start a timer with the specified label.\n *\n * @param category - Typically the class or module name\n * @param label - Must be unique; used to match with timeEnd()\n */\n time(category : string, label : string) : void;\n\n /**\n * End a timer with the specified label and log the time elapsed.\n *\n * @param category - Typically the class or module name\n * @param label - Must match a previous time() call\n */\n timeEnd(category : string, label : string) : void;\n}\n\n/**\n * Interface for a logger instance.\n * Each instance is typically associated with a specific category/component.\n */\nexport interface LoggerInterface\n{\n /** Log a message at TRACE level */\n trace(message : string, ...args : unknown[]) : void;\n\n /** Log a message at DEBUG level */\n debug(message : string, ...args : unknown[]) : void;\n\n /** Log a message at INFO level */\n info(message : string, ...args : unknown[]) : void;\n\n /** Log a message at WARN level */\n warn(message : string, ...args : unknown[]) : void;\n\n /** Log a message at ERROR level */\n error(message : string, ...args : unknown[]) : void;\n\n /** Start a timer with the given label */\n time(label : string) : void;\n\n /** End a timer and log the elapsed time */\n timeEnd(label : string) : void;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// defineEntity\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type {\n Action, BehaviorClass, ComposedOf, GameEntityType, StateBag, StateFor, ValidTuple,\n} from '@skewedaspect/sage-core';\n\nimport { NodeEntity } from './nodeEntity.ts';\n\nimport type { MeshConfig } from './meshConfig.ts';\nimport type { NodeSimulationContext } from './nodeSimulationContext.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport interface GameEntityDefinition<Behaviors extends readonly BehaviorClass[]>\n{\n type : string;\n tags ?: string[];\n defaultState ?: Partial<StateFor<Behaviors>>;\n behaviors : Behaviors;\n mesh ?: MeshConfig;\n actions ?: readonly Action[];\n}\n\n// The class a definition answers with: NodeEntity's statics, the definition's own behaviors tuple, and instances\n// typed by that tuple, so a subclass reads its state and operations with no declaration of its own. The construct\n// signature is abstract because the constructor is protected: a game extends the class or hands it to the entity\n// registry, and never constructs one directly (design/entities.md#creation-and-restoration).\nexport type GameEntityDefinitionClass<Behaviors extends readonly BehaviorClass[]>\n = Omit<typeof NodeEntity, 'prototype'>\n & {\n readonly behaviors : Behaviors;\n readonly prototype : NodeEntity<Behaviors>;\n }\n & (abstract new (\n id : string,\n context : NodeSimulationContext,\n providedState : object,\n runCreateHooks : boolean,\n name ?: string\n ) => NodeEntity<Behaviors>);\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// A supplied field replaces the definition's own default whole (design/entities.md#state); tags merge as a\n// deduplicated union instead, since a spawn's own extra tags are additions to a definition's baseline, not a\n// replacement for it (design/authoring.md#composition-and-the-entity-registry).\nfunction foldCreateState<Behaviors extends readonly BehaviorClass[]>(\n definition : GameEntityDefinition<Behaviors>,\n supplied : Partial<StateFor<Behaviors>> | undefined\n) : Partial<StateFor<Behaviors>>\n{\n const suppliedState = (supplied ?? {}) as StateBag;\n const definitionTags = definition.tags ?? [];\n const suppliedTags = (suppliedState['tags'] as string[] | undefined) ?? [];\n\n return {\n ...definition.defaultState,\n ...suppliedState,\n tags: [ ...new Set([ ...definitionTags, ...suppliedTags ]) ],\n } as Partial<StateFor<Behaviors>>;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport function defineEntity<Behaviors extends readonly BehaviorClass[]>(\n definition : GameEntityDefinition<Behaviors> & { behaviors : ValidTuple<Behaviors, NodeEntity<Behaviors>> }\n) : GameEntityDefinitionClass<Behaviors>\n{\n class DefinedEntity extends NodeEntity<Behaviors>\n {\n static override readonly behaviors = definition.behaviors;\n static override readonly meshConfig = definition.mesh;\n static override readonly actions = definition.actions;\n\n static override create<Type extends GameEntityType>(\n this : Type & { behaviors : ValidTuple<Type['behaviors'], Type['prototype']> },\n id : string,\n context : NodeSimulationContext,\n state ?: Partial<StateFor<Type['behaviors']>>,\n name ?: string\n ) : ComposedOf<Type>\n {\n const folded = foldCreateState(definition, state as Partial<StateFor<Behaviors>> | undefined);\n\n return super.create(id, context, folded as Partial<StateFor<Type['behaviors']>>, name);\n }\n }\n\n // The class is named after the definition's type, so a stack trace and a debugger read the registry key\n // rather than this local name (design/authoring.md#what-a-definition-is).\n Object.defineProperty(DefinedEntity, 'name', { value: definition.type });\n\n return DefinedEntity as unknown as GameEntityDefinitionClass<Behaviors>;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Standard Mesh Source\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { Color3, MeshBuilder, PBRMaterial, StandardMaterial } from '@babylonjs/core';\n\nimport type { AbstractMesh, AssetContainer, Mesh, Scene } from '@babylonjs/core';\nimport type { GameEntityType } from '@skewedaspect/sage-core';\n\nimport type { ColorConfig } from '../interfaces/level.ts';\nimport type { MeshLoader } from './nodeSimulationContext.ts';\n\nimport type { MeshConfig, MeshMaterialConfig } from './meshConfig.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// What the standard mesh source needs from the asset manager: loading a glb (or a fragment naming one of its\n// meshes), and cloning a fragment's cached source mesh into a fresh, independent one in a scene it names.\nexport interface MeshAssetSource\n{\n load(path : string) : Promise<AssetContainer | AbstractMesh>;\n clone(path : string, into : Scene) : Mesh;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nfunction toColor3(color : ColorConfig) : Color3\n{\n return new Color3(color.r, color.g, color.b);\n}\n\nfunction buildPrimitiveMesh(scene : Scene, source : string, params : Record<string, unknown> | undefined) : Mesh\n{\n const builderName = `Create${ source.charAt(0).toUpperCase() }${ source.slice(1) }`;\n const builders = MeshBuilder as unknown as Record<string, (name : string, options : object, scene : Scene) => Mesh>;\n const builder = builders[builderName];\n\n if(typeof builder !== 'function')\n {\n throw new Error(`No Babylon mesh builder provides the primitive shape \"${ source }\".`);\n }\n\n return builder(source, params ?? {}, scene);\n}\n\nfunction applyMaterial(scene : Scene, mesh : Mesh, config : MeshMaterialConfig) : void\n{\n if(config.type === 'pbr')\n {\n const material = new PBRMaterial(`${ mesh.name }-material`, scene);\n\n if(config.color !== undefined)\n {\n material.albedoColor = toColor3(config.color);\n }\n\n if(config.emissive !== undefined)\n {\n material.emissiveColor = toColor3(config.emissive);\n }\n\n if(config.metallic !== undefined)\n {\n material.metallic = config.metallic;\n }\n\n if(config.roughness !== undefined)\n {\n material.roughness = config.roughness;\n }\n\n mesh.material = material;\n }\n else\n {\n const material = new StandardMaterial(`${ mesh.name }-material`, scene);\n\n if(config.color !== undefined)\n {\n material.diffuseColor = toColor3(config.color);\n }\n\n if(config.emissive !== undefined)\n {\n material.emissiveColor = toColor3(config.emissive);\n }\n\n mesh.material = material;\n }\n}\n\nasync function buildMesh(scene : Scene, config : MeshConfig, assets : MeshAssetSource) : Promise<Mesh>\n{\n let mesh : Mesh;\n\n if(config.source.includes('#'))\n {\n await assets.load(config.source);\n mesh = assets.clone(config.source, scene);\n }\n else\n {\n mesh = buildPrimitiveMesh(scene, config.source, config.params);\n }\n\n mesh.isVisible = config.visible ?? true;\n\n if(config.material !== undefined)\n {\n applyMaterial(scene, mesh, config.material);\n }\n\n return mesh;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport function buildStandardMeshSource(\n entityClasses : Readonly<Record<string, GameEntityType>>,\n assets : MeshAssetSource\n) : MeshLoader\n{\n return async (scene, name) : Promise<AbstractMesh | undefined> =>\n {\n const entityClass = entityClasses[name] as { meshConfig ?: MeshConfig } | undefined;\n const config = entityClass?.meshConfig;\n\n if(config === undefined)\n {\n return undefined;\n }\n\n return buildMesh(scene, config, assets);\n };\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// SoundBehavior\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { Behavior } from '@skewedaspect/sage-core';\n\nimport type { NodeBehaviorHooks, NodeEntity } from '../entities/nodeEntity.ts';\n\nimport type { StaticSound } from '@babylonjs/core';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport interface SoundConfig\n{\n url : string;\n volume ?: number;\n loop ?: boolean;\n spatial ?: boolean;\n maxDistance ?: number;\n channel ?: string;\n}\n\nexport interface SoundState\n{\n sounds : Record<string, SoundConfig>;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport class SoundBehavior extends Behavior<SoundState, object, NodeEntity> implements NodeBehaviorHooks\n{\n static readonly ops = [ 'play', 'stop' ] as const;\n\n readonly defaults : SoundState = { sounds: {} };\n\n readonly #sounds = new Map<string, StaticSound>();\n #destroyed = false;\n\n onMeshLoaded() : void\n {\n const entity = this.entity;\n const audio = entity.audio;\n\n if(audio === undefined)\n {\n return;\n }\n\n for(const [ name, config ] of Object.entries(this.state.sounds))\n {\n audio.createSound(name, config.url, config.channel, {\n volume: config.volume ?? 1,\n loop: config.loop ?? false,\n }).then((sound) =>\n {\n this.#onSoundBuilt(name, config, entity, sound);\n });\n }\n }\n\n onDestroy() : void\n {\n this.#destroyed = true;\n\n for(const sound of this.#sounds.values())\n {\n sound.stop();\n sound.dispose();\n }\n\n this.#sounds.clear();\n }\n\n play(name : string) : void\n {\n this.#find(name)?.play();\n }\n\n stop(name : string) : void\n {\n this.#find(name)?.stop();\n }\n\n // A subsystem that is missing says so; one that is still starting stays quiet (design/audio.md#operations).\n #requireAudio() : void\n {\n if(this.entity.audio === undefined)\n {\n throw new Error(\n `SoundBehavior: entity \"${ this.entity.id }\" carries no AudioManager; the game never named audio `\n + 'channels.'\n );\n }\n }\n\n // A sound whose entity is already gone by the time it finishes building is disposed on arrival rather\n // than kept -- destruction is not undone by a late arrival, unlike a mesh\n // (design/audio.md#pooling-and-destruction).\n #onSoundBuilt(name : string, config : SoundConfig, entity : NodeEntity, sound : StaticSound) : void\n {\n if(this.#destroyed)\n {\n sound.dispose();\n return;\n }\n\n if(config.spatial ?? false)\n {\n sound.spatial.attach(entity.node);\n sound.spatial.maxDistance = config.maxDistance ?? 100;\n }\n\n this.#sounds.set(name, sound);\n\n if(config.loop ?? false)\n {\n sound.play();\n }\n }\n\n #find(name : string) : StaticSound | undefined\n {\n this.#requireAudio();\n\n if(!Object.hasOwn(this.state.sounds, name))\n {\n throw new Error(`SoundBehavior: no sound named \"${ name }\" is declared.`);\n }\n\n return this.#sounds.get(name);\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// HavokPhysicsBodyBehavior\n//\n// Every field except `shape` and `parts` is a getter and a setter over the body once there is one, so state is the\n// body rather than a copy of it (design/physics.md#state-computed-from-the-body).\n//----------------------------------------------------------------------------------------------------------------------\n\nimport {\n Mesh, PhysicsAggregate, PhysicsMotionType, PhysicsPrestepType, PhysicsShapeBox, PhysicsShapeContainer,\n PhysicsShapeCylinder, PhysicsShapeSphere, PhysicsShapeType, Quaternion, Vector3,\n} from '@babylonjs/core';\nimport { Behavior } from '@skewedaspect/sage-core';\n\nimport { computeHierarchyBounds } from '../utils/bounds.ts';\nimport { quaternionView, toQuaternion, toVector3, vectorView } from '../utils/vectors.ts';\n\nimport type {\n HavokPlugin, PhysicsAggregateParameters, PhysicsBody, PhysicsMaterial, PhysicsShape, Scene, TransformNode,\n} from '@babylonjs/core';\nimport type {\n NodeBehaviorHooks, NodeEntity, NodeEntityState, QuaternionState, Vector3State,\n} from '../entities/nodeEntity.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/** What a body's outline can be (design/physics.md#shapes). */\nexport type BodyShape = 'box' | 'sphere' | 'cylinder' | 'convex' | 'mesh' | 'compound';\n\n/** Who decides where a body goes (design/physics.md#static-dynamic-and-kinematic). */\nexport type BodyMotion = 'static' | 'dynamic' | 'kinematic';\n\n/** One child of a compound shape, placed relative to the entity's node. */\nexport interface CompoundPart\n{\n shape : 'box' | 'sphere' | 'cylinder';\n position : Vector3State;\n rotation : QuaternionState;\n\n /** Full size: a box's width, height, depth; a sphere's diameter in x; a cylinder's diameter in x, height in y. */\n extents : Vector3State;\n}\n\nexport interface PhysicsBodyState\n{\n shape : BodyShape;\n\n /** The children of a `compound` shape. Unused by every other shape. */\n parts : CompoundPart[];\n\n motion : BodyMotion;\n\n /** In kilograms. */\n mass : number;\n\n friction : number;\n restitution : number;\n position : Vector3State;\n rotation : QuaternionState;\n\n /** Metres per second. */\n velocity : Vector3State;\n\n /** Radians per second. */\n angularVelocity : Vector3State;\n /** Whether the body is out of the physics world (design/physics.md#a-suspended-body). */\n suspended : boolean;\n}\n\n// What a shape is sized from: the full size of the geometry's box, and where the middle of that box sits in\n// the node's own frame.\ninterface BodyMeasurement\n{\n extents : Vector3;\n center : Vector3;\n}\n\ntype ShapeParameters = Omit<PhysicsAggregateParameters, 'mass' | 'friction' | 'restitution'>;\n\n//----------------------------------------------------------------------------------------------------------------------\n// Shapes and motion\n//----------------------------------------------------------------------------------------------------------------------\n\nfunction shapeTypeFor(shape : BodyShape) : PhysicsShapeType\n{\n if(shape === 'sphere')\n {\n return PhysicsShapeType.SPHERE;\n }\n\n if(shape === 'cylinder')\n {\n return PhysicsShapeType.CYLINDER;\n }\n\n if(shape === 'convex')\n {\n return PhysicsShapeType.CONVEX_HULL;\n }\n\n if(shape === 'mesh')\n {\n return PhysicsShapeType.MESH;\n }\n\n return PhysicsShapeType.BOX;\n}\n\nfunction shapeParametersFor(shape : BodyShape, measurement : BodyMeasurement) : ShapeParameters\n{\n const { extents, center } = measurement;\n\n if(shape === 'sphere')\n {\n // The largest half-extent, so the sphere contains the geometry rather than cutting through its\n // corners (design/physics.md#shapes).\n return { radius: Math.max(extents.x, extents.y, extents.z) / 2, center };\n }\n\n if(shape === 'cylinder')\n {\n return {\n radius: extents.x / 2,\n pointA: new Vector3(center.x, center.y - (extents.y / 2), center.z),\n pointB: new Vector3(center.x, center.y + (extents.y / 2), center.z),\n };\n }\n\n return { extents, center };\n}\n\nfunction buildPartShape(part : CompoundPart, scene : Scene) : PhysicsShape\n{\n const { extents } = part;\n\n if(part.shape === 'sphere')\n {\n return new PhysicsShapeSphere(Vector3.Zero(), extents.x / 2, scene);\n }\n\n if(part.shape === 'cylinder')\n {\n const pointA = new Vector3(0, -extents.y / 2, 0);\n const pointB = new Vector3(0, extents.y / 2, 0);\n\n return new PhysicsShapeCylinder(pointA, pointB, extents.x / 2, scene);\n }\n\n return new PhysicsShapeBox(Vector3.Zero(), Quaternion.Identity(), toVector3(extents), scene);\n}\n\n// Babylon spells the kinematic motion type `ANIMATED`; the design's word is kinematic on both sides, and\n// this is the line that maps one to the other (design/physics.md#static-dynamic-and-kinematic).\nfunction motionTypeFor(motion : BodyMotion) : PhysicsMotionType\n{\n if(motion === 'static')\n {\n return PhysicsMotionType.STATIC;\n }\n\n if(motion === 'kinematic')\n {\n return PhysicsMotionType.ANIMATED;\n }\n\n return PhysicsMotionType.DYNAMIC;\n}\n\nfunction motionFor(motionType : PhysicsMotionType) : BodyMotion\n{\n if(motionType === PhysicsMotionType.STATIC)\n {\n return 'static';\n }\n\n if(motionType === PhysicsMotionType.ANIMATED)\n {\n return 'kinematic';\n }\n\n return 'dynamic';\n}\n\n// A kinematic body is told where it should be and reaches it during the physics step, carrying the velocity\n// that motion implies; every other body is placed outright (design/physics.md#static-dynamic-and-kinematic).\nfunction prestepFor(motion : BodyMotion) : PhysicsPrestepType\n{\n if(motion === 'kinematic')\n {\n return PhysicsPrestepType.ACTION;\n }\n\n return PhysicsPrestepType.TELEPORT;\n}\n\nfunction hasGeometry(node : TransformNode) : boolean\n{\n for(const child of node.getChildMeshes(false))\n {\n if(child.getTotalVertices() > 0)\n {\n return true;\n }\n }\n\n return false;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport class HavokPhysicsBodyBehavior\n extends Behavior<PhysicsBodyState, object, NodeEntity>\n implements NodeBehaviorHooks\n{\n static readonly ops = [ 'applyImpulse' ] as const;\n\n // `position` and `rotation` are the fields NodeEntity already declares, shared rather than declared\n // beside them, so the defaults here are NodeEntity's own (design/physics.md#state).\n readonly defaults : PhysicsBodyState = {\n shape: 'box',\n parts: [],\n motion: 'dynamic',\n mass: 1,\n friction: 0.5,\n restitution: 0,\n position: { x: 0, y: 0, z: 0 },\n rotation: { x: 0, y: 0, z: 0, w: 1 }, // eslint-disable-line id-length\n velocity: { x: 0, y: 0, z: 0 },\n angularVelocity: { x: 0, y: 0, z: 0 },\n suspended: false,\n };\n\n #aggregate : PhysicsAggregate | undefined;\n #plugin : HavokPlugin | undefined;\n #suspended = false;\n\n // What the body resumes as, and the collision masks it gets back (design/physics.md#a-suspended-body).\n #resumeMotion : PhysicsMotionType = PhysicsMotionType.DYNAMIC;\n #savedMasks = new Map<PhysicsShape, { membership : number; collide : number }>();\n\n // A shape handed to an aggregate ready-made is not disposed with it, so a compound's container and its\n // children are kept here to be disposed by hand (design/physics.md#lifecycle).\n #builtShapes : PhysicsShape[] = [];\n\n //------------------------------------------------------------------------------------------------------------------\n // Hooks\n //------------------------------------------------------------------------------------------------------------------\n\n onMeshLoaded() : void\n {\n const entity = this.entity;\n const scene = entity.node.getScene();\n const engine = scene.getPhysicsEngine();\n\n if(engine === null)\n {\n throw new Error(\n `HavokPhysicsBodyBehavior: entity \"${ entity.id }\" wants a physics body, and the scene it was `\n + 'spawned into has no physics engine.'\n );\n }\n\n // What the level authored wins: it was there first, and the level's geometry is built against it\n // (design/physics.md#bodies-no-entity-owns).\n if(entity.node.physicsBody)\n {\n throw new Error(\n `HavokPhysicsBodyBehavior: entity \"${ entity.id }\" adopted a node that already carries a physics `\n + 'body, built for it by the level. Drop the node\\'s `collider` property to let the behavior own it.'\n );\n }\n\n // A body's transform is its node's own, and a parented node's own transform is not where the node is in\n // the world (design/physics.md#where-a-body-goes).\n if((entity.state as NodeEntityState).parentID !== undefined)\n {\n throw new Error(\n `HavokPhysicsBodyBehavior: entity \"${ entity.id }\" carries a parent ID, and a body is never built for `\n + 'an entity parented to another.'\n );\n }\n\n this.#plugin = engine.getPhysicsPlugin() as HavokPlugin;\n this.#aggregate = this.#buildAggregate(entity, scene);\n\n this.#seedBody();\n\n // Built from restored state, or from a spawn that says so, the body starts out of the physics world\n // (design/physics.md#a-suspended-body).\n if(this.state.suspended === true)\n {\n this.#suspend();\n }\n\n this.#installAccessors();\n }\n\n onDestroy() : void\n {\n // Every field computed from the body was asked for the last time before this hook ran, by the entity's\n // own destroy() (design/physics.md#lifecycle).\n this.#aggregate?.dispose();\n this.#aggregate = undefined;\n this.#plugin = undefined;\n\n for(const shape of this.#builtShapes)\n {\n shape.dispose();\n }\n\n this.#builtShapes = [];\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Operations\n //------------------------------------------------------------------------------------------------------------------\n\n // A shove is a verb, answered immediately, rather than a fact one entity records in another's state\n // (design/physics.md#state). A body that has not been built yet has nothing to shove\n // (design/physics.md#a-missing-subsystem-throws).\n applyImpulse(impulse : Vector3State, location ?: Vector3State) : void\n {\n if(this.#aggregate === undefined)\n {\n return;\n }\n\n const body = this.#aggregate.body;\n\n // Through the middle it only moves the body; against a corner it turns it too\n // (design/physics.md#state).\n let where = body.getObjectCenterWorld();\n if(location !== undefined)\n {\n where = toVector3(location);\n }\n\n body.applyImpulse(toVector3(impulse), where);\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Building the body\n //------------------------------------------------------------------------------------------------------------------\n\n // The body goes on the entity's node, never on its mesh (design/physics.md#where-a-body-goes).\n #buildAggregate(entity : NodeEntity, scene : Scene) : PhysicsAggregate\n {\n const node = entity.node;\n const shape = this.state.shape;\n\n const mass = this.state.mass;\n\n if(shape === 'compound')\n {\n return new PhysicsAggregate(node, this.#buildCompound(scene), { mass }, scene);\n }\n\n if(shape === 'convex' || shape === 'mesh')\n {\n const mesh = entity.mesh;\n\n if(!(mesh instanceof Mesh) || mesh.getTotalVertices() === 0)\n {\n throw new Error(\n `HavokPhysicsBodyBehavior: entity \"${ entity.id }\" asked for a \"${ shape }\" body, which reads `\n + 'vertices, and it has no mesh geometry to read.'\n );\n }\n\n return new PhysicsAggregate(node, shapeTypeFor(shape), { mass, mesh }, scene);\n }\n\n const parameters = shapeParametersFor(shape, this.#measure(entity));\n\n return new PhysicsAggregate(node, shapeTypeFor(shape), { mass, ...parameters }, scene);\n }\n\n #measure(entity : NodeEntity) : BodyMeasurement\n {\n const mesh = entity.mesh;\n\n // The box's own middle, in the node's frame, so geometry that does not sit centred on the mesh's origin\n // gets a shape over it rather than beside it (design/physics.md#shapes).\n if(mesh !== undefined && mesh.getTotalVertices() > 0)\n {\n const box = mesh.getBoundingInfo().boundingBox;\n\n return { extents: box.extendSize.scale(2), center: box.center.clone() };\n }\n\n // Babylon reads a bare TransformNode's own bounding info as a unit cube, so a node with no mesh is\n // measured across everything hanging under it, offset and all\n // (design/physics.md#sizing-from-a-bare-transform-node).\n if(!hasGeometry(entity.node))\n {\n throw new Error(\n `HavokPhysicsBodyBehavior: entity \"${ entity.id }\" has neither a mesh nor anything under its node `\n + 'to size a body from.'\n );\n }\n\n return computeHierarchyBounds(entity.node);\n }\n\n // One Havok shape per part, each placed in the container at its part's offset and rotation, so the body\n // keeps the concave outline the parts describe rather than the hull around them\n // (design/physics.md#shapes).\n #buildCompound(scene : Scene) : PhysicsShapeContainer\n {\n const container = new PhysicsShapeContainer(scene);\n\n for(const part of this.state.parts)\n {\n const child = buildPartShape(part, scene);\n\n container.addChild(child, toVector3(part.position), toQuaternion(part.rotation));\n this.#builtShapes.push(child);\n }\n\n this.#builtShapes.push(container);\n\n return container;\n }\n\n // The aggregate picks a motion type from the mass it was handed; the design decides motion from its own\n // field instead, so what the body carries is written here from state, mass aside -- that one the\n // aggregate took (design/physics.md#static-dynamic-and-kinematic).\n #seedBody() : void\n {\n const body = this.#body();\n\n body.setMotionType(motionTypeFor(this.state.motion));\n body.setLinearVelocity(toVector3(this.state.velocity));\n body.setAngularVelocity(toVector3(this.state.angularVelocity));\n\n this.#writeMaterial({ friction: this.state.friction, restitution: this.state.restitution });\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // The state the body backs\n //------------------------------------------------------------------------------------------------------------------\n\n // Every field but `shape` and `parts` becomes a view onto the body: reading asks it and writing tells it\n // (design/physics.md#state-computed-from-the-body, design/entities.md#computed-state).\n #installAccessors() : void\n {\n // The four transform fields each answer with one view of the body's own, so a write to a component\n // reaches the body through the same write the whole field takes\n // (design/physics.md#state-computed-from-the-body).\n const writePosition = (value : Vector3State) : void => { this.#writeTransform(value, this.state.rotation); };\n const writeRotation = (value : QuaternionState) : void => { this.#writeTransform(this.state.position, value); };\n const writeVelocity = (value : Vector3State) : void => { this.#body().setLinearVelocity(toVector3(value)); };\n\n const writeAngularVelocity = (value : Vector3State) : void =>\n {\n this.#body().setAngularVelocity(toVector3(value));\n };\n\n const position = vectorView(() => this.#body().transformNode.position, writePosition);\n const rotation = quaternionView(() => this.#rotation(), writeRotation);\n const velocity = vectorView(() => this.#body().getLinearVelocity(), writeVelocity);\n const angularVelocity = vectorView(() => this.#body().getAngularVelocity(), writeAngularVelocity);\n\n this.computed('position', { get: () => position, set: writePosition });\n\n this.computed('rotation', { get: () => rotation, set: writeRotation });\n\n this.computed('velocity', { get: () => velocity, set: writeVelocity });\n\n this.computed('angularVelocity', { get: () => angularVelocity, set: writeAngularVelocity });\n\n // While suspended the body is held static, so the motion a game sees and writes is what it resumes as\n // (design/physics.md#a-suspended-body).\n this.computed('motion', {\n get: () =>\n {\n if(this.#suspended)\n {\n return motionFor(this.#resumeMotion);\n }\n\n return motionFor(this.#body().getMotionType());\n },\n set: (value) =>\n {\n if(this.#suspended)\n {\n this.#resumeMotion = motionTypeFor(value);\n return;\n }\n\n this.#body().setMotionType(motionTypeFor(value));\n },\n });\n\n this.computed('suspended', {\n get: () => this.#suspended,\n set: (value) => { this.#writeSuspended(value); },\n });\n\n this.computed('mass', {\n get: () => this.#body().getMassProperties().mass ?? 0,\n set: (value) => { this.#body().setMassProperties({ mass: value }); },\n });\n\n this.computed('friction', {\n get: () => this.#material().friction ?? 0,\n set: (value) => { this.#writeMaterial({ friction: value, restitution: this.state.restitution }); },\n });\n\n this.computed('restitution', {\n get: () => this.#material().restitution ?? 0,\n set: (value) => { this.#writeMaterial({ friction: this.state.friction, restitution: value }); },\n });\n }\n\n #writeTransform(position : Vector3State, rotation : QuaternionState) : void\n {\n const body = this.#body();\n const node = body.transformNode;\n\n node.position = toVector3(position);\n node.rotationQuaternion = toQuaternion(rotation);\n\n // A suspended body is not told where its node went; it catches up when it is put back\n // (design/physics.md#a-suspended-body).\n if(this.#suspended)\n {\n return;\n }\n\n // Havok reads a body's transform from its node during a prestep, and a body's default is not to\n // prestep at all, so the write is carried over by hand and the default put back.\n body.setPrestepType(prestepFor(motionFor(body.getMotionType())));\n this.#havok().setPhysicsBodyTransformation(body, node);\n body.setPrestepType(PhysicsPrestepType.DISABLED);\n }\n\n #writeSuspended(value : boolean) : void\n {\n if(value === this.#suspended)\n {\n return;\n }\n\n if(value)\n {\n this.#suspend();\n return;\n }\n\n this.#resume();\n }\n\n // Out of the physics world: held static, so nothing integrates it and the plugin stops writing it into the\n // node, and masked out of every collision and query (design/physics.md#a-suspended-body).\n #suspend() : void\n {\n const body = this.#body();\n\n this.#resumeMotion = body.getMotionType();\n body.setMotionType(PhysicsMotionType.STATIC);\n\n for(const shape of this.#shapes())\n {\n this.#savedMasks.set(shape, { membership: shape.filterMembershipMask, collide: shape.filterCollideMask });\n shape.filterMembershipMask = 0;\n shape.filterCollideMask = 0;\n }\n\n this.#suspended = true;\n }\n\n // Back where the node now stands, as the motion it had or was given meanwhile, carrying no velocity\n // (design/physics.md#a-suspended-body).\n #resume() : void\n {\n const body = this.#body();\n\n for(const shape of this.#shapes())\n {\n const saved = this.#savedMasks.get(shape);\n\n if(saved !== undefined)\n {\n shape.filterMembershipMask = saved.membership;\n shape.filterCollideMask = saved.collide;\n }\n }\n\n this.#savedMasks.clear();\n this.#suspended = false;\n\n body.setMotionType(this.#resumeMotion);\n body.setPrestepType(PhysicsPrestepType.TELEPORT);\n this.#havok().setPhysicsBodyTransformation(body, body.transformNode);\n body.setPrestepType(PhysicsPrestepType.DISABLED);\n body.setLinearVelocity(Vector3.Zero());\n body.setAngularVelocity(Vector3.Zero());\n }\n\n // The aggregate's own shape and, for a compound, every part under it.\n #shapes() : PhysicsShape[]\n {\n const shapes : PhysicsShape[] = [ ...this.#builtShapes ];\n const own = this.#body().shape;\n\n if(own !== null && !shapes.includes(own))\n {\n shapes.push(own);\n }\n\n return shapes;\n }\n\n // A compound's children are what Havok collides against, so they carry the material the container does.\n #writeMaterial(material : PhysicsMaterial) : void\n {\n const shape = this.#body().shape;\n\n if(shape !== null)\n {\n shape.material = material;\n }\n\n for(const child of this.#builtShapes)\n {\n child.material = material;\n }\n }\n\n #rotation() : Quaternion\n {\n const rotation = this.#body().transformNode.rotationQuaternion;\n\n if(rotation === null)\n {\n return Quaternion.Identity();\n }\n\n return rotation;\n }\n\n #material() : PhysicsMaterial\n {\n const shape = this.#body().shape;\n\n if(shape === null)\n {\n return {};\n }\n\n return shape.material;\n }\n\n #body() : PhysicsBody\n {\n if(this.#aggregate === undefined)\n {\n throw new Error(\n `HavokPhysicsBodyBehavior: entity \"${ this.entity.id }\" has no physics body to answer for.`\n );\n }\n\n return this.#aggregate.body;\n }\n\n #havok() : HavokPlugin\n {\n if(this.#plugin === undefined)\n {\n throw new Error(\n `HavokPhysicsBodyBehavior: entity \"${ this.entity.id }\" has no physics plugin to answer for.`\n );\n }\n\n return this.#plugin;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// CharacterControllerBehavior\n//\n// The capsule is Babylon's PhysicsCharacterController, which works in capsule centres; `position` is the character's\n// foot, and the conversion between the two lives here (design/character.md#state).\n//----------------------------------------------------------------------------------------------------------------------\n\nimport {\n CharacterSupportedState, PhysicsCharacterController, PhysicsShapeCapsule, PhysicsShapeSphere, ProximityCastResult,\n Quaternion, ShapeCastResult, Vector3,\n} from '@babylonjs/core';\nimport { Behavior } from '@skewedaspect/sage-core';\n\nimport { defaultGravity } from '../utils/physics.ts';\nimport { toVec3Object } from '../utils/vectors.ts';\n\nimport type { CharacterSurfaceInfo, HavokPlugin, PhysicsBody, Scene } from '@babylonjs/core';\nimport type { NodeBehaviorHooks, NodeEntity, Vector3State } from '../entities/nodeEntity.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/** A capsule height and a speed under a name (design/character.md#stances). */\nexport interface StanceConfig\n{\n height : number;\n radius : number;\n\n /** Metres per second. */\n speed : number;\n}\n\nexport interface CharacterControllerState\n{\n /** How hard a game is asking to move right, -1 to 1. */\n moveX : number;\n\n /** How hard a game is asking to move forward, -1 to 1. */\n moveZ : number;\n\n wantsJump : boolean;\n wantsSprint : boolean;\n\n /** The stance a game is asking for, by name. */\n wantsStance : string;\n\n stances : Record<string, StanceConfig>;\n\n /** Take the tallest stance's capsule from the mesh instead. */\n measureCapsule : boolean;\n\n /** What sprinting multiplies the current stance's speed by. */\n sprintScale : number;\n\n /** Upward metres per second a jump starts with. */\n jumpSpeed : number;\n\n /** How long a jump request survives, in fixed steps. */\n jumpBufferSteps : number;\n\n /** How long after leaving the ground a jump is still allowed, in fixed steps. */\n coyoteSteps : number;\n\n /** Metres per second. */\n velocity : Vector3State;\n\n /** The character's foot, not the middle of its capsule (design/character.md#state). */\n position : Vector3State;\n\n /** True only when the last step's support query answered `SUPPORTED`. */\n grounded : boolean;\n\n /** The stance the character is actually in, which names an entry in `stances`. */\n stance : string;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nconst DOWN = new Vector3(0, -1, 0);\n\n// What `stance` reads before a capsule exists to answer for it. The stance the capsule is actually built in\n// is the tallest one (design/character.md#stances).\nconst FIRST_STANCE = 'standing';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nfunction stanceNamesByHeight(stances : Record<string, StanceConfig>) : string[]\n{\n const names = Object.keys(stances);\n\n names.sort((left, right) => stances[right].height - stances[left].height);\n\n return names;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport class CharacterControllerBehavior\n extends Behavior<CharacterControllerState, object, NodeEntity>\n implements NodeBehaviorHooks\n{\n readonly defaults : CharacterControllerState = {\n moveX: 0,\n moveZ: 0,\n wantsJump: false,\n wantsSprint: false,\n wantsStance: 'standing',\n stances: {\n standing: { height: 1.8, radius: 0.3, speed: 3 },\n crouching: { height: 0.9, radius: 0.3, speed: 1.5 },\n },\n measureCapsule: false,\n sprintScale: 2,\n jumpSpeed: 5,\n jumpBufferSteps: 6,\n coyoteSteps: 6,\n velocity: { x: 0, y: 0, z: 0 },\n position: { x: 0, y: 0, z: 0 },\n grounded: false,\n stance: FIRST_STANCE,\n };\n\n #controller : PhysicsCharacterController | undefined;\n #plugin : HavokPlugin | undefined;\n\n // The controller's own body. `shapeProximity` reports a shape it starts inside and has to be told to skip\n // this one (design/character.md#the-capsule); a shape cast never reports the shape it starts inside, so\n // the ground cast filters this one out of what it answers with instead of asking Havok to skip it\n // (design/character.md#standing-on-something-that-moves).\n #capsuleBody : PhysicsBody | undefined;\n\n #probe : PhysicsShapeSphere | undefined;\n #probeRadius = 0;\n\n #stanceShapes = new Map<string, { shape : PhysicsShapeCapsule; height : number; radius : number }>();\n\n #gravity = defaultGravity();\n\n // Measuring happens when a character is created and not when one is restored (design/character.md#the-capsule),\n // and onCreate is the one hook restore does not run (design/behaviors.md#hooks).\n #created = false;\n\n #stance = FIRST_STANCE;\n #grounded = false;\n\n // What the character was standing on when the last step ended, which a join between two surfaces is decided by\n // (design/character.md#standing-on-something-that-moves).\n #ground : PhysicsBody | undefined;\n\n // What was added for the surface last step, so the character's own velocity can be read back out of what the\n // sweep actually achieved.\n #carried = Vector3.Zero();\n\n // What the live capsule was last built or resized to, which is what a write to the current entry is\n // compared against.\n #builtHeight = 0;\n #builtRadius = 0;\n\n #jumpBuffer = 0;\n #stepsSinceSupported = Number.MAX_SAFE_INTEGER;\n\n // A jump is still reported supported on the step after it starts, because the capsule has not cleared the\n // surface yet. Holding the rise across that step is what keeps the jump from being cancelled by its own floor.\n #rising = false;\n\n //------------------------------------------------------------------------------------------------------------------\n // Hooks\n //------------------------------------------------------------------------------------------------------------------\n\n onCreate() : void\n {\n this.#created = true;\n }\n\n onMeshLoaded() : void\n {\n const entity = this.entity;\n const scene = entity.node.getScene();\n const engine = scene.getPhysicsEngine();\n\n if(engine === null)\n {\n throw new Error(\n `CharacterControllerBehavior: entity \"${ entity.id }\" wants a character controller, and the scene `\n + 'it was spawned into has no physics engine.'\n );\n }\n\n if(this.#created && this.state.measureCapsule)\n {\n this.#measureCapsule(entity);\n }\n\n this.#plugin = engine.getPhysicsPlugin() as HavokPlugin;\n this.#gravity = engine.gravity.clone();\n this.#stance = this.#tallest();\n\n this.#checkStance(this.#stance);\n\n this.#buildController(entity, scene);\n this.#installAnswers();\n }\n\n onUpdate(duration : number) : void\n {\n const controller = this.#controller;\n\n if(controller === undefined)\n {\n return;\n }\n\n const surface = controller.checkSupport(duration, DOWN);\n\n this.#grounded = surface.supportedState === CharacterSupportedState.SUPPORTED;\n\n if(this.#grounded)\n {\n this.#stepsSinceSupported = 0;\n }\n else\n {\n this.#stepsSinceSupported += 1;\n }\n\n // The stance is resolved before the ground is asked about, since the probe the ground cast sweeps is\n // sized to the current stance's radius (design/character.md#the-step).\n this.#resolveStance();\n this.#ground = this.#findGround();\n\n if(this.#ownVelocity().y <= 0)\n {\n this.#rising = false;\n }\n\n const velocity = this.desiredVelocity(duration, surface);\n\n // Overriding desiredVelocity is how a game adds air control or a dash\n // (design/character.md#what-a-game-overrides); none of that implies giving up the jump, so the jump,\n // its buffer and its coyote window stay here.\n if(this.#takesAJump(surface))\n {\n this.#rising = true;\n velocity.y = this.state.jumpSpeed;\n }\n\n this.#carried = this.#surfaceVelocity();\n velocity.addInPlace(this.#carried);\n\n controller.setVelocity(velocity);\n controller.integrate(duration, surface, this.#gravity);\n\n // Written as a world position, since that is the space the capsule was swept in; the node under a parent\n // keeps its own transform in that parent's space, which is what `position` reads\n // (design/character.md#the-step).\n this.entity.node.setAbsolutePosition(this.#footOf(controller.getPosition()));\n this.state.velocity = toVec3Object(controller.getVelocity());\n }\n\n onDestroy() : void\n {\n // Disposing the controller takes its body and the node it built with it (design/character.md#lifecycle).\n this.#controller?.dispose();\n this.#controller = undefined;\n this.#capsuleBody = undefined;\n\n this.#probe?.dispose();\n this.#probe = undefined;\n\n for(const held of this.#stanceShapes.values())\n {\n held.shape.dispose();\n }\n\n this.#stanceShapes.clear();\n\n this.#plugin = undefined;\n this.#ground = undefined;\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // What a game overrides\n //------------------------------------------------------------------------------------------------------------------\n\n /**\n * The velocity the character wants this step, in world space, before the surface it is standing on is added\n * (design/character.md#what-a-game-overrides). The jump is not this method's: an override answers for\n * intent, and the behavior takes the jump around it.\n */\n protected desiredVelocity(duration : number, surface : CharacterSurfaceInfo) : Vector3\n {\n const supported = surface.supportedState === CharacterSupportedState.SUPPORTED;\n const velocity = this.#groundVelocityWanted();\n\n if(supported && !this.#rising)\n {\n velocity.y = 0;\n\n return velocity;\n }\n\n velocity.y = this.#ownVelocity().y + (this.#gravity.y * duration);\n\n return velocity;\n }\n\n /**\n * Whether that stance's capsule fits where the character is standing\n * (design/character.md#what-a-game-overrides).\n */\n protected stanceFits(stance : string) : boolean\n {\n const wanted = this.state.stances[stance];\n\n if(wanted === undefined)\n {\n return false;\n }\n\n // A sweep cannot answer this: a wall beside a character asking to widen already overlaps where the\n // sweep would begin, and a sweep reports what it runs into rather than what it starts inside.\n return !this.#capsuleOverlaps(stance, wanted);\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Building the capsule\n //------------------------------------------------------------------------------------------------------------------\n\n #isACapsule(config : StanceConfig | undefined) : boolean\n {\n if(config === undefined)\n {\n return false;\n }\n\n return config.height >= config.radius * 2;\n }\n\n // A capsule is two half-spheres around a segment, and a stance with no segment left is not one. Quietly\n // building the shortest capsule that radius allows is a shape nobody chose (design/character.md#state).\n // An entry nobody enters has shaped nothing, so this is asked where a stance is taken and nowhere else.\n #checkStance(stance : string) : void\n {\n const config = this.state.stances[stance];\n\n if(config === undefined || this.#isACapsule(config))\n {\n return;\n }\n\n throw new Error(\n `CharacterControllerBehavior: entity \"${ this.entity.id }\" declares the stance \"${ stance }\" at a `\n + `height of ${ config.height } and a radius of ${ config.radius }, which is shorter than twice its `\n + 'radius and is not a capsule.'\n );\n }\n\n #buildController(entity : NodeEntity, scene : Scene) : void\n {\n const height = this.#currentHeight();\n const foot = this.#spawnFoot(entity);\n const centre = new Vector3(foot.x, foot.y + (height / 2), foot.z);\n\n // The controller builds a transform node of its own and an ANIMATED body on it\n // (design/character.md#character). It is the node the scene did not have a moment ago, and that body is\n // what a query cast from inside the capsule has to be told to ignore.\n const before = new Set(scene.transformNodes);\n\n this.#controller = new PhysicsCharacterController(\n centre,\n { capsuleHeight: height, capsuleRadius: this.#currentRadius() },\n scene\n );\n\n this.#builtHeight = height;\n this.#builtRadius = this.#currentRadius();\n\n for(const node of scene.transformNodes)\n {\n if(!before.has(node) && node.physicsBody !== null)\n {\n this.#capsuleBody = node.physicsBody;\n }\n }\n }\n\n // The controller sweeps in world space, and a node may sit under a parent, so where it stands in the scene is\n // its world position rather than its own (design/character.md#the-capsule).\n #spawnFoot(entity : NodeEntity) : Vector3\n {\n return entity.node.getAbsolutePosition().clone();\n }\n\n #measureCapsule(entity : NodeEntity) : void\n {\n const mesh = entity.mesh;\n\n if(mesh === undefined || mesh.getTotalVertices() === 0)\n {\n throw new Error(\n `CharacterControllerBehavior: entity \"${ entity.id }\" asked to measure its capsule from a mesh, `\n + 'and has nothing to measure.'\n );\n }\n\n const half = mesh.getBoundingInfo().boundingBox.extendSize;\n\n const tallest = this.#stanceConfig(this.#tallest());\n\n tallest.height = half.y * 2;\n\n // The larger of x and z, because a character is rarely as deep as it is wide (design/character.md#the-capsule).\n tallest.radius = Math.max(half.x, half.z);\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // The step\n //------------------------------------------------------------------------------------------------------------------\n\n #resolveStance() : void\n {\n this.#checkStance(this.state.wantsStance);\n\n let next = this.#stance;\n\n if(this.stanceFits(this.state.wantsStance))\n {\n next = this.state.wantsStance;\n }\n else\n {\n for(const name of stanceNamesByHeight(this.state.stances))\n {\n if(this.#isACapsule(this.state.stances[name]) && this.stanceFits(name))\n {\n next = name;\n break;\n }\n }\n }\n\n this.#stance = next;\n\n const config = this.#stanceConfig(next);\n\n // A game changes what a stance means by writing that entry, so what the capsule follows is the numbers\n // rather than the name (design/character.md#the-capsule).\n if(config.height === this.#builtHeight && config.radius === this.#builtRadius)\n {\n return;\n }\n\n this.#checkStance(next);\n\n // Resizing holds the foot still and lets the head move (design/character.md#the-capsule).\n this.#character().setShapeOptions({ capsuleHeight: config.height, capsuleRadius: config.radius }, true);\n\n this.#builtHeight = config.height;\n this.#builtRadius = config.radius;\n }\n\n #groundVelocityWanted() : Vector3\n {\n const asked = new Vector3(this.state.moveX, 0, this.state.moveZ);\n\n if(asked.lengthSquared() > 1)\n {\n asked.normalize();\n }\n\n let speed = this.state.stances[this.#stance].speed;\n\n if(this.state.wantsSprint)\n {\n speed = speed * this.state.sprintScale;\n }\n\n asked.scaleInPlace(speed);\n\n // moveX and moveZ are in the character's own frame (design/character.md#state).\n const facing = this.entity.node.rotationQuaternion ?? Quaternion.Identity();\n\n return asked.rotateByQuaternionToRef(facing, new Vector3());\n }\n\n // A request that cannot be spent is held rather than thrown away (design/character.md#jump).\n #takesAJump(surface : CharacterSurfaceInfo) : boolean\n {\n if(this.state.wantsJump)\n {\n // Six is six steps on which the jump still works, counting the step it was asked on -- the same\n // count the coyote window keeps (design/character.md#jump). The asking step is always one of them,\n // because a jump is taken on the step it is asked for when one is allowed.\n this.#jumpBuffer = Math.max(this.state.jumpBufferSteps, 1);\n }\n\n if(this.#jumpBuffer <= 0)\n {\n return false;\n }\n\n if(!this.#jumpAllowed(surface))\n {\n this.#jumpBuffer -= 1;\n\n return false;\n }\n\n this.#jumpBuffer = 0;\n\n // The character is on its way up, so the coyote window it just left is spent.\n this.#stepsSinceSupported = Number.MAX_SAFE_INTEGER;\n\n return true;\n }\n\n #jumpAllowed(surface : CharacterSurfaceInfo) : boolean\n {\n // Sliding is touching ground the character cannot stand on, and a jump from it would climb a slope the\n // slope is meant to refuse (design/character.md#grounded-and-stance-are-answers).\n if(surface.supportedState === CharacterSupportedState.SLIDING)\n {\n return false;\n }\n\n if(this.#stance !== this.#tallest())\n {\n return false;\n }\n\n return this.#grounded || this.#stepsSinceSupported <= this.state.coyoteSteps;\n }\n\n #ownVelocity() : Vector3\n {\n return this.#character().getVelocity()\n .subtract(this.#carried);\n }\n\n #surfaceVelocity() : Vector3\n {\n const ground = this.#ground;\n\n if(ground === undefined)\n {\n return Vector3.Zero();\n }\n\n return ground.getLinearVelocity();\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // What the character is standing on\n //------------------------------------------------------------------------------------------------------------------\n\n // A short shape cast straight down from the foot answers what is there and whose it is. The character keeps the\n // body it was already standing on while that body is still under it, so stepping onto a platform commits once\n // instead of flickering between it and the floor it left (design/character.md#standing-on-something-that-moves).\n #findGround() : PhysicsBody | undefined\n {\n if(!this.#grounded)\n {\n return undefined;\n }\n\n const foot = this.#foot();\n const to = new Vector3(foot.x, foot.y - this.#castLength(), foot.z);\n const nearest = this.#castProbe(foot, to);\n const remembered = this.#ground;\n\n if(remembered === undefined || remembered === nearest || remembered.isDisposed)\n {\n return nearest;\n }\n\n // Two surfaces can be under one foot at a join. Casting again past the nearer one is what asks whether the\n // one the character was standing on is still there.\n if(nearest !== undefined && this.#castProbe(foot, to, nearest) === remembered)\n {\n return remembered;\n }\n\n return nearest;\n }\n\n // The candidate capsule is built at the character's own foot, so its bottom sits where the character's does\n // and the floor underneath is not what refuses a stance.\n #capsuleOverlaps(stance : string, config : StanceConfig) : boolean\n {\n const plugin = this.#havok();\n const foot = this.#foot();\n const input = new ProximityCastResult();\n const hit = new ProximityCastResult();\n\n plugin.shapeProximity(\n {\n shape: this.#stanceShape(stance, config),\n position: new Vector3(foot.x, foot.y + (config.height / 2), foot.z),\n rotation: Quaternion.Identity(),\n maxDistance: 0,\n shouldHitTriggers: false,\n ignoreBody: this.#capsuleBody,\n },\n input,\n hit\n );\n\n return hit.hasHit;\n }\n\n #stanceShape(stance : string, config : StanceConfig) : PhysicsShapeCapsule\n {\n const held = this.#stanceShapes.get(stance);\n\n if(held !== undefined && held.height === config.height && held.radius === config.radius)\n {\n return held.shape;\n }\n\n held?.shape.dispose();\n\n const half = (config.height / 2) - config.radius;\n const shape = new PhysicsShapeCapsule(\n new Vector3(0, half, 0),\n new Vector3(0, -half, 0),\n config.radius,\n this.entity.node.getScene()\n );\n\n this.#stanceShapes.set(stance, { shape, height: config.height, radius: config.radius });\n\n return shape;\n }\n\n // The cast reaches a little past the gap a settled capsule holds itself at, which is the controller's own\n // contact distance rather than a number chosen here (design/character.md#standing-on-something-that-moves).\n #castLength() : number\n {\n const controller = this.#character();\n\n return controller.keepDistance + controller.keepContactTolerance;\n }\n\n #castProbe(from : Vector3, to : Vector3, ignore ?: PhysicsBody) : PhysicsBody | undefined\n {\n const plugin = this.#havok();\n\n const input = new ShapeCastResult();\n const hit = new ShapeCastResult();\n\n plugin.shapeCast(\n {\n shape: this.#probeShape(this.#currentRadius()),\n rotation: Quaternion.Identity(),\n startPosition: from,\n endPosition: to,\n shouldHitTriggers: false,\n ignoreBody: ignore,\n },\n input,\n hit\n );\n\n // The capsule is filtered out here on every cast alike, whether or not this call also asked Havok to\n // skip something else (design/character.md#standing-on-something-that-moves).\n if(!hit.hasHit || hit.body === undefined || hit.body === this.#capsuleBody)\n {\n return undefined;\n }\n\n return hit.body;\n }\n\n #probeShape(radius : number) : PhysicsShapeSphere\n {\n if(this.#probe === undefined || this.#probeRadius !== radius)\n {\n this.#probe?.dispose();\n this.#probe = new PhysicsShapeSphere(\n Vector3.Zero(),\n radius,\n this.entity.node.getScene()\n );\n this.#probeRadius = radius;\n }\n\n return this.#probe;\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // Answers\n //------------------------------------------------------------------------------------------------------------------\n\n // Both are recomputed at the top of every step, so a write could not survive to mean anything. It is discarded\n // rather than refused because a getter with no setter throws in strict mode\n // (design/character.md#grounded-and-stance-are-answers).\n // The last step's answers, held as ordinary values once there is no step left to recompute them. State stays\n // total, and neither field reaches a controller that is gone.\n // Both are computed from what the last step worked out, with nothing under them, and destroying the entity\n // leaves each holding that last answer (design/character.md#grounded-and-stance-are-answers).\n #installAnswers() : void\n {\n this.computed('grounded', {\n get: () => this.#grounded,\n set: () => { /* an answer, not a field */ },\n });\n\n this.computed('stance', {\n get: () => this.#stance,\n set: () => { /* an answer, not a field */ },\n });\n }\n\n //------------------------------------------------------------------------------------------------------------------\n // The capsule, in the two frames it is measured in\n //------------------------------------------------------------------------------------------------------------------\n\n #currentHeight() : number\n {\n return this.#stanceConfig(this.#stance).height;\n }\n\n #currentRadius() : number\n {\n return this.#stanceConfig(this.#stance).radius;\n }\n\n // `stances` is state and a game may write anything into it, this included (design/character.md#state).\n #stanceConfig(stance : string) : StanceConfig\n {\n const config = this.state.stances[stance];\n\n if(config === undefined)\n {\n throw new Error(\n `CharacterControllerBehavior: entity \"${ this.entity.id }\" is in the stance \"${ stance }\", and `\n + 'its `stances` holds no entry under that name.'\n );\n }\n\n return config;\n }\n\n #tallest() : string\n {\n const names = stanceNamesByHeight(this.state.stances);\n\n if(names.length === 0)\n {\n throw new Error(\n `CharacterControllerBehavior: entity \"${ this.entity.id }\" has no stances to be in, and a `\n + 'character is always in one.'\n );\n }\n\n return names[0];\n }\n\n #foot() : Vector3\n {\n return this.#footOf(this.#character().getPosition());\n }\n\n #footOf(centre : Vector3) : Vector3\n {\n return new Vector3(centre.x, centre.y - this.#character().footOffset, centre.z);\n }\n\n // Havok answers both queries, and it is here from the moment the capsule is until the moment it is not, so\n // asking either of them outside that window is a question with no answer rather than a \"no\"\n // (design/physics.md#a-missing-subsystem-throws).\n #havok() : HavokPlugin\n {\n if(this.#plugin === undefined)\n {\n throw new Error(\n `CharacterControllerBehavior: entity \"${ this.entity.id }\" has no capsule to answer for.`\n );\n }\n\n return this.#plugin;\n }\n\n #character() : PhysicsCharacterController\n {\n if(this.#controller === undefined)\n {\n throw new Error(\n `CharacterControllerBehavior: entity \"${ this.entity.id }\" has no capsule to answer for.`\n );\n }\n\n return this.#controller;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Input Capture\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { BindingInput, DeviceType } from './binding.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport interface CaptureOptions\n{\n deviceTypes ?: readonly DeviceType[];\n sourceTypes ?: readonly string[];\n signal ?: AbortSignal;\n}\n\nexport interface CandidateInput\n{\n device : { type : DeviceType; index : number };\n sourceType : string;\n sourceKey : string;\n value : boolean | number;\n}\n\n// Waits for the next deliberate input and answers with the definition that would bind it (input.md#input-capture).\nexport class InputCapture\n{\n readonly #deviceTypes ?: readonly DeviceType[];\n readonly #sourceTypes ?: readonly string[];\n\n #resolveWait : ((input : BindingInput) => void) | undefined;\n #rejectWait : ((reason : unknown) => void) | undefined;\n\n constructor(options : CaptureOptions = {})\n {\n this.#deviceTypes = options.deviceTypes;\n this.#sourceTypes = options.sourceTypes;\n\n options.signal?.addEventListener('abort', () =>\n {\n this.#rejectWait?.(options.signal?.reason ?? new Error('The capture was aborted.'));\n this.#resolveWait = undefined;\n this.#rejectWait = undefined;\n });\n }\n\n // A rebinding screen feeds every candidate input it sees here as it arrives.\n feed(candidate : CandidateInput) : void\n {\n if(this.#resolveWait === undefined)\n {\n return;\n }\n\n if(!this.#isDeliberate(candidate.value))\n {\n return;\n }\n\n if(this.#deviceTypes !== undefined && !this.#deviceTypes.includes(candidate.device.type))\n {\n return;\n }\n\n if(this.#sourceTypes !== undefined && !this.#sourceTypes.includes(candidate.sourceType))\n {\n return;\n }\n\n this.#resolveWait({\n device: candidate.device,\n sourceType: candidate.sourceType,\n sourceKey: candidate.sourceKey,\n });\n\n this.#resolveWait = undefined;\n this.#rejectWait = undefined;\n }\n\n wait() : Promise<BindingInput>\n {\n return new Promise((resolve, reject) =>\n {\n this.#resolveWait = resolve;\n this.#rejectWait = reject;\n });\n }\n\n #isDeliberate(value : boolean | number) : boolean\n {\n if(value === true)\n {\n return true;\n }\n\n if(typeof value === 'number' && value !== 0)\n {\n return true;\n }\n\n return false;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Configuration\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { Action } from '@skewedaspect/sage-core';\n\nimport type { ActionContextRegistry } from './actionContext.ts';\nimport type { ActionRegistry } from './actionRegistry.ts';\nimport type { Binding, BindingDefinition } from './binding.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport interface ContextDefinition\n{\n name : string;\n exclusive : boolean;\n}\n\n// The whole set of actions, bindings, and contexts, JSON-compatible (input.md#bindings).\nexport interface Configuration\n{\n actions : readonly Action[];\n bindings : readonly BindingDefinition[];\n contexts : readonly ContextDefinition[];\n}\n\nexport function exportConfiguration(\n actions : readonly Action[],\n bindings : readonly Binding<unknown>[],\n contexts : readonly ContextDefinition[]\n) : Configuration\n{\n return {\n actions: [ ...actions ],\n bindings: bindings.map((binding) => binding.toDefinition()),\n contexts: [ ...contexts ],\n };\n}\n\n// Registers every action and every context a configuration names. Binding definitions are handed back\n// for the caller to resolve, since resolution needs a device reader the configuration itself does not carry.\nexport function importConfiguration(\n config : Configuration,\n actions : ActionRegistry,\n contexts : ActionContextRegistry\n) : readonly BindingDefinition[]\n{\n for(const action of config.actions)\n {\n actions.register(action);\n }\n\n for(const context of config.contexts)\n {\n contexts.registerContext(context.name, context.exclusive);\n }\n\n return [ ...config.bindings ];\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// SkewedAspect Game Engine\n//----------------------------------------------------------------------------------------------------------------------\n\n// Interfaces\nimport type { GameCanvas, RenderEngineOptions, SageOptions } from './interfaces/game.ts';\n\n// Base Classes\nimport { DebugConsole } from './classes/debugConsole.ts';\nimport { GameEngine, type RebuildSimulationOptions, type SimulationFactory } from './classes/gameEngine.ts';\n\n// Entities\nimport { NodeSimulation } from './entities/nodeSimulation.ts';\nimport type { NodeEntityRegistry } from './entities/nodeSimulation.ts';\nimport { NodePool } from './entities/nodePool.ts';\nimport type { MeshLoader } from './entities/nodeSimulationContext.ts';\n\nimport { EventBus } from '@skewedaspect/sage-core';\nimport type { ValidEntityRegistry } from '@skewedaspect/sage-core';\nimport type { Scene } from '@babylonjs/core';\n\n// Engines\nimport { AudioEngine } from './engines/audio.ts';\nimport { SceneEngine } from './engines/scene.ts';\n\n// Managers\nimport { AssetManager } from './managers/asset.ts';\nimport { GameManager } from './managers/game.ts';\nimport { InputManager } from './managers/input.ts';\nimport { LevelManager } from './managers/level.ts';\nimport { AudioManager } from './managers/audio.ts';\nimport { SimulationSaveManager } from './managers/simulationSave.ts';\nimport { ColliderDebugManager } from './managers/colliderDebug.ts';\n\n// Utils\nimport { createEngine } from './utils/graphics.ts';\nimport { LoggingUtility } from './utils/logger.ts';\nimport { GameTimer } from './utils/timer.ts';\nimport { VERSION } from './utils/version.ts';\n\n// Debug\nimport { registerBuiltins } from './debug/builtins.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Creates and initializes a fully-wired GameEngine with rendering, physics, and entity management. The\n * engine owns one InputManager (`engine.managers.inputManager`): its action registry, its action context\n * registry, device state capture, and the routing loop that runs before each frame's fixed steps\n * (docs/design/input.md). A game declares its own actions, contexts, and bindings onto it.\n *\n * @param entityClasses - The entity registry, keyed by class name, that the simulation spawns from\n */\nexport async function createGameEngine<Registry extends NodeEntityRegistry = NodeEntityRegistry>(\n canvas : GameCanvas,\n entityClasses : Registry & ValidEntityRegistry<Registry>,\n options : SageOptions = {}\n) : Promise<GameEngine<Registry>>\n{\n // Initialize logging\n const logger = new LoggingUtility(options.logLevel || 'debug');\n const engineLogger = logger.getLogger('SAGE');\n\n engineLogger.info(`Initializing SAGE Game Engine v${ VERSION }...`);\n\n // Initialize BabylonJS stuff\n engineLogger.debug('Creating rendering engine...');\n\n const renderOptions : RenderEngineOptions = {\n antialias: true,\n adaptToDeviceRatio: true,\n enableAllFeatures: true,\n ...options.renderOptions,\n };\n\n const engine = await createEngine(canvas, renderOptions, options.largeWorldRendering ?? false, engineLogger);\n\n // Initialize internals\n engineLogger.debug('Creating event bus...');\n const bus = new EventBus();\n\n // Initialize SAGE Engines\n engineLogger.debug('Creating scene engine...');\n const sceneEngine = new SceneEngine(engine, logger, options.havokBinary);\n\n // Initialize SAGE Managers\n engineLogger.debug('Creating managers...');\n const assetManager = new AssetManager(bus, sceneEngine, logger);\n const levelManager = new LevelManager<Registry>(bus, logger);\n const saveManager = new SimulationSaveManager<Registry>(levelManager, VERSION);\n const gameManager = new GameManager<Registry>(engine, bus, levelManager, logger);\n const inputManager = new InputManager(logger);\n gameManager.setInputManager(inputManager);\n\n // Initialize audio system (opt-in)\n let audioEngine : AudioEngine | undefined;\n let audioManager : AudioManager | undefined;\n\n if(options.audioChannels && options.audioChannels.length > 0)\n {\n engineLogger.debug('Creating audio engine...');\n audioEngine = new AudioEngine(logger);\n await audioEngine.initialize();\n\n engineLogger.debug('Creating audio manager...');\n audioManager = new AudioManager(audioEngine, logger);\n await audioManager.initialize(options.audioChannels);\n }\n\n // A factory rather than an instance: no scene exists yet at boot, so there is nothing to build a\n // NodeSimulation against until a level (GameLevel.buildScene) or the game itself calls rebuildSimulation.\n const fixedStepDuration = options.fixedStepDuration ?? 1 / 60;\n const noMesh : MeshLoader = async () => undefined;\n\n engineLogger.debug('Creating collider debug manager...');\n const colliderDebugManager = new ColliderDebugManager();\n\n const buildSimulation : SimulationFactory<Registry> = (\n scene : Scene,\n simulationName : string,\n buildOptions ?: RebuildSimulationOptions\n ) => new NodeSimulation<Registry>({\n id: simulationName,\n bus,\n fixedStepDuration: buildOptions?.fixedStepDuration ?? fixedStepDuration,\n entityClasses,\n scene,\n pool: new NodePool(),\n loadMesh: buildOptions?.meshSource ?? noMesh,\n\n audio: audioManager,\n colliders: colliderDebugManager,\n logger: logger.getLogger('NodeSimulation'),\n });\n\n // Initialize debug tools\n let debugConsole : DebugConsole | null = null;\n\n if(options.debug !== false)\n {\n engineLogger.debug('Creating debug console...');\n let namespaceName = 'sage';\n\n if(typeof options.debug === 'object' && options.debug.namespace !== undefined)\n {\n namespaceName = options.debug.namespace;\n }\n\n debugConsole = new DebugConsole(namespaceName);\n }\n\n // Initialize timer\n engineLogger.debug('Creating game timer...');\n const timer = new GameTimer();\n gameManager.registerFrameCallback((dt) => timer.tick(dt * 1000));\n\n engineLogger.info(`SAGE Game Engine v${ VERSION } initialized successfully.`);\n\n const ge = new GameEngine<Registry>(\n canvas,\n engine,\n bus,\n logger,\n timer,\n buildSimulation,\n\n // Engines\n {\n sceneEngine,\n audioEngine,\n },\n\n // Managers\n {\n assetManager,\n gameManager,\n inputManager,\n levelManager,\n saveManager,\n audioManager,\n },\n\n options.largeWorldRendering ?? false,\n\n // Debug tools\n {\n colliders: colliderDebugManager,\n console: debugConsole,\n expose: (name : string, value : unknown) =>\n {\n debugConsole?.expose(name, value);\n },\n }\n );\n\n // Late-bind: these managers are constructed before GameEngine (because GameEngine takes them as\n // constructor args), but they need a back-reference at runtime. This must happen before any level\n // loads or entity attaches, so do it immediately after construction. Do not reorder.\n levelManager.$setGameEngine(ge);\n gameManager.$setGameEngine(ge);\n saveManager.$setGameEngine(ge);\n\n // Register debug console built-ins\n if(debugConsole)\n {\n registerBuiltins(debugConsole, ge);\n }\n\n return ge;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n// Exports\n//----------------------------------------------------------------------------------------------------------------------\n\nexport * from './interfaces/game.ts';\nexport * from './interfaces/logger.ts';\n\n// Export base classes\nexport { AssetManager, AudioEngine, GameEngine, GameManager, LevelManager, SceneEngine };\nexport type {\n DebugTools,\n Engines,\n GameHook,\n Managers,\n RebuildSimulationOptions,\n SimulationFactory,\n} from './classes/gameEngine.ts';\n\nexport * from './classes/level.ts';\nexport * from './classes/gameLevel.ts';\nexport type {\n AssetCompletePayload, AssetProgressPayload, GamePausedPayload, GameResumedPayload, LevelCompletePayload,\n LevelErrorPayload, LevelProgressPayload, LevelTransitionCompletePayload, LevelTransitionErrorPayload,\n LevelTransitionProgressPayload, LevelTransitionStartPayload, TriggerEnterPayload, TriggerExitPayload,\n} from './events/payloads.ts';\nexport * from './handlers/index.ts';\nexport * from './interfaces/level.ts';\nexport * from './interfaces/lifecycle.ts';\nexport * from './utils/logger.ts';\nexport * from './utils/timer.ts';\nexport * from './utils/version.ts';\n\n// Export the entity layer -- sage-core's own base classes, so a game can author entities and behaviors\n// without a direct sage-core dependency. A `declare module` against either package augments `EventMap`: a\n// re-exported interface is the interface itself.\nexport {\n Behavior, EventBus, GameEntity, GameRandom, GameSimulation, NamedBehavior, StateMachineBehavior, defineStates,\n dilationLimit, drainPassLimit, operationCallDepthLimit,\n} from '@skewedaspect/sage-core';\nexport type {\n BehaviorClass, ComposedOf, Computation, DeepReadonly, EventCallback, EventMap, EventSubscription, EventsMatching,\n GameEntityClass, GameEntityRegistry, GameEntitySnapshot, GameEntityState, GameEntityType, GameEvent,\n GameSimulationOptions, GameSimulationSnapshot, GameSimulationView, IDAllocator, NamedState, NarrowedTo,\n OpsFor, OpsOf, RandomSeed, RandomState, SimulationFor, StateBag, StateDefinition, StateFor, StateSliceOf,\n StateValue, Subscriber, Unsubscribe, ValidEntityRegistry, ValidTuple,\n} from '@skewedaspect/sage-core';\nexport { NodeEntity } from './entities/nodeEntity.ts';\nexport type { NodeBehaviorHooks, NodeEntityState, QuaternionState, Vector3State } from './entities/nodeEntity.ts';\nexport { NodePool } from './entities/nodePool.ts';\nexport { poolSizeLimit } from './entities/constants.ts';\nexport { NodeSimulation } from './entities/nodeSimulation.ts';\nexport type { NodeEntityRegistry, NodeSimulationOptions } from './entities/nodeSimulation.ts';\nexport type { MeshLoader, NodeSet, NodeSimulationContext } from './entities/nodeSimulationContext.ts';\nexport type { EntityPickFilter, EntityPickResult } from './entities/entityPicking.ts';\n\n// Export the authoring surface: entity definitions compiling onto the entity registry, and the standard mesh\n// source built from it (docs/design/authoring.md).\nexport { defineEntity } from './entities/defineEntity.ts';\nexport type { GameEntityDefinition, GameEntityDefinitionClass } from './entities/defineEntity.ts';\nexport { buildStandardMeshSource } from './entities/standardMeshSource.ts';\nexport type { MeshAssetSource } from './entities/standardMeshSource.ts';\nexport type { MeshConfig, MeshMaterialConfig } from './entities/meshConfig.ts';\n\n// Export the provided behaviors (docs/design/audio.md, docs/design/physics.md,\n// docs/design/character.md).\nexport { SoundBehavior } from './behaviors/sound.ts';\nexport type { SoundConfig, SoundState } from './behaviors/sound.ts';\nexport { HavokPhysicsBodyBehavior } from './behaviors/physicsBody.ts';\nexport type { BodyMotion, BodyShape, CompoundPart, PhysicsBodyState } from './behaviors/physicsBody.ts';\nexport { CharacterControllerBehavior } from './behaviors/characterController.ts';\nexport type { CharacterControllerState, StanceConfig } from './behaviors/characterController.ts';\n\n// Export the input system: the action registry, action contexts, the four binding kinds, device readers,\n// binding resolution and delivery, capture, and configuration import/export (docs/design/input.md,\n// docs/design/events.md#delivery). `engine.managers.inputManager` is the one instance a game declares its\n// own actions, contexts, and bindings onto; these exports are the pieces it builds them from.\nexport { ActionRegistry } from './input/actionRegistry.ts';\nexport { ActionContextRegistry } from './input/actionContext.ts';\nexport type {\n Binding, BindingDefinition, BindingFire, BindingInput, BindingKind, DeviceID, DeviceType,\n} from './input/binding.ts';\nexport { schemeOf } from './input/scheme.ts';\nexport type { InputScheme } from './input/scheme.ts';\nexport type { DeviceReader } from './input/deviceReader.ts';\nexport type { GamepadState, KeyboardState, MouseState } from './input/deviceState.ts';\nexport { KeyboardReader } from './input/deviceReaders/keyboard.ts';\nexport { MouseReader } from './input/deviceReaders/mouse.ts';\nexport type { MouseSourceType } from './input/deviceReaders/mouse.ts';\nexport { GamepadReader } from './input/deviceReaders/gamepad.ts';\nexport type { GamepadSourceType } from './input/deviceReaders/gamepad.ts';\nexport { TriggerBinding } from './input/bindings/trigger.ts';\nexport type { EdgeMode, TriggerOptions } from './input/bindings/trigger.ts';\nexport { ToggleBinding } from './input/bindings/toggle.ts';\nexport type { ToggleOptions } from './input/bindings/toggle.ts';\nexport { ValueBinding } from './input/bindings/value.ts';\nexport type { ValueOptions } from './input/bindings/value.ts';\nexport { DeltaBinding } from './input/bindings/delta.ts';\nexport type { DeltaOptions } from './input/bindings/delta.ts';\nexport { resolveBinding } from './input/resolveBinding.ts';\nexport { readerFor } from './input/readerFor.ts';\nexport { deliverAction } from './input/deliverAction.ts';\nexport { InputCapture } from './input/inputCapture.ts';\nexport type { CandidateInput, CaptureOptions } from './input/inputCapture.ts';\nexport { exportConfiguration, importConfiguration } from './input/configuration.ts';\nexport type { Configuration, ContextDefinition } from './input/configuration.ts';\nexport type { Action, AnalogAction, DigitalAction } from '@skewedaspect/sage-core';\n\n// Export manager types\nexport type { FrameCallback } from './managers/game.ts';\nexport { InputManager, ROUTING_LOOP_SENDER_ID } from './managers/input.ts';\nexport type { DeviceChangedPayload, SchemeChangedPayload } from './managers/input.ts';\nexport type { TransitionOptions } from './managers/level.ts';\nexport { AudioManager } from './managers/audio.ts';\nexport { ColliderDebugManager } from './managers/colliderDebug.ts';\nexport type { ColliderDebugConfig } from './managers/colliderDebug.ts';\nexport { OutlineManager } from './managers/outline.ts';\nexport { SimulationSaveManager } from './managers/simulationSave.ts';\nexport type { BeforeSaveHook, LoadResult, SaveEnvelope } from './managers/simulationSave.ts';\n\n// Export debug tools\nexport { DebugConsole } from './classes/debugConsole.ts';\nexport { registerBuiltins } from './debug/builtins.ts';\n\n// Export utilities\nexport { computeHierarchyBounds } from './utils/bounds.ts';\nexport { isBrowser } from './utils/capabilities.ts';\nexport type { HierarchyBounds } from './utils/bounds.ts';\nexport {\n getCanonicalTransform,\n toQuatObject, toQuaternion, toVec3Object, toVector3,\n} from './utils/vectors.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n"],"mappings":";;;;;;;;AAIA,SAAgB,IAChB;CACI,OAAO,OAAO,SAAW,OAAsB,OAAO,aAAa;AACvE;AAEA,SAAgB,KAChB;CACI,OAAO,EAAU,KACV,CAAC,CAAC,OAAO,UAAU;AAC9B;;;ACWA,IAAa,KAAb,MACA;CACI;CACA,qBAAW,IAAI,IAA0B;CACzC,KAAY;CAEZ,YAAY,IAAgB,QAC5B;EAEI,AADA,KAAK,KAAiB,GACtB,KAAK,GAAkB;CAC3B;CAYA,OAAO,GAAe,GACtB;EACO,KAAK,OAKL,KAAK,GAAiB,CAAK,KAE1B,KAAK,GAAS,IAAI,GAAM,EAAE,QAAQ,EAAM,IAAI,CAAC,GAC7C,KAAK,GAAc,GAAM,EAAM,GAAG,MAIlC,KAAK,GAAS,IAAI,GAAM,EAAE,SAAM,CAAC,GACjC,KAAK,GAAa,GAAM,CAAK;CAErC;CAEA,aAAa,GACb;EACO,WAAK,MAAa,MAAS,KAAK,KAUnC;GAJA,KAAK,GAAqB,CAAI,GAE9B,KAAK,GAAiB,GACtB,KAAK,KAAiB,GACtB,KAAK,GAAkB;GAEvB,KAAI,IAAM,CAAE,GAAW,MAAW,KAAK,IAEnC,AAAG,EAAM,SAEL,KAAK,GAAc,GAAW,EAAM,MAAM,IAI1C,KAAK,GAAa,GAAW,EAAM,KAAK;EAVzB;CAa3B;CAEA,IAAI,gBACJ;EACI,OAAO,KAAK;CAChB;CAEA,UACA;EACO,AAOH,KAAK,QAFL,KAAK,GAAiB,GACtB,KAAK,GAAS,MAAM,GACH;CACrB;CAMA,GAAiB,GACjB;EACI,OAAO,OAAO,KAAU,cACjB,KACA,SAAS,KACT,OAAQ,EAAwB,OAAQ;CACnD;CAEA,KACA;EACQ,MAAU,GAKd,OAAO,QAAQ,IAAI,QAAQ,KAAK,EAAc;CAClD;CAEA,GAAqB,GACrB;EACQ,MAAU,KAKX,QAAQ,IAAI,QAAQ,CAAI,GAEvB,MAAU,MACN,UAAW,EAAM,+HAErB;CAER;CAEA,KACA;EACQ,EAAU,MAKd,KAAK,GAAqB,KAAK,EAAc,GAE7C,QAAQ,IAAI,QAAQ,KAAK,IAAgB,CAAC,CAAC;CAC/C;CAEA,KACA;EACQ,EAAU,KAKd,QAAQ,eAAe,QAAQ,KAAK,EAAc;CACtD;CAEA,GAAc,GAAe,GAC7B;EACI,IAAM,IAAK,KAAK,GAAc;EAE1B,KAKJ,OAAO,eAAe,GAAI,GAAM;GAC5B,KAAK;GACL,YAAY;GACZ,cAAc;EAClB,CAAC;CACL;CAEA,GAAa,GAAe,GAC5B;EACI,IAAM,IAAK,KAAK,GAAc;EAE1B,KAKJ,OAAO,eAAe,GAAI,GAAM;GAC5B;GACA,UAAU;GACV,YAAY;GACZ,cAAc;EAClB,CAAC;CACL;AACJ,GCnMa,IAAA,UC6FA,KAAb,MAAa,EACb;CACI;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA,UAAiB;CAEjB,WAAmB;CAEnB;CACA;CAKA,KAA2C;CAG3C,KAAoD,CAAC;CACrD,KAA8C,CAAC;CAC/C,KAAiD,CAAC;CAMlD,YACI,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,IAAsB,IACtB,GAEJ;EAYI,AAXA,KAAK,SAAS,GACd,KAAK,eAAe,GACpB,KAAK,MAAM,GACX,KAAK,SAAS,GACd,KAAK,QAAQ,GACb,KAAK,mBAAmB,GACxB,KAAK,UAAU,GACf,KAAK,WAAW,GAChB,KAAK,QAAQ,GACb,KAAK,sBAAsB,GAE3B,KAAK,OAAO,EAAO,UAAU,YAAY;CAC7C;CAKA,IAAI,aACJ;EACI,OAAO,KAAK;CAChB;CAQA,kBAAkB,GAAe,GAAe,GAChD;EACI,OAAO,KAAK,iBAAiB,GAAO,GAAM,CAAO;CACrD;CAOA,eAAe,GACf;EACO,KAAK,OAAW,MAKnB,KAAK,IAAQ,OAAO,GACpB,KAAK,KAAS,GACd,GAAY,OAAO;CACvB;CAOA,cAAc,GACd;EACI,OAAO,EAAW,GAAU,KAAK,IAAmB,CAAI;CAC5D;CAGA,QAAQ,GACR;EACI,OAAO,EAAW,GAAU,KAAK,IAAa,CAAI;CACtD;CAGA,WAAW,GACX;EACI,OAAO,EAAW,GAAU,KAAK,IAAgB,CAAI;CACzD;CAEA,OAAO,GACH,GACA,GAEJ;EAGI,OAFA,EAAM,KAAK,CAAI,SAGf;GACI,IAAM,IAAQ,EAAM,QAAQ,CAAI;GAEhC,AAAG,KAAS,KAER,EAAM,OAAO,GAAO,CAAC;EAE7B;CACJ;CAUA,MAAM,QACN;EACI,IAAG,KAAK,UAEJ,MAAU,MAAM,mFAAmF;EAGvG,IAAG,KAAK,SACR;GACI,KAAK,KAAK,KAAK,iDAAiD;GAChE;EACJ;EAEA,KAAK,KAAK,KAAK,+CAAgD,EAAS,KAAK;EAE7E,KAAI,IAAM,KAAQ,CAAE,GAAG,KAAK,EAAkB,GAG1C,MAAM,EAAK,IAAI;EAGnB,MAAM,KAAK,SAAS,YAAY,MAAM;EAEtC,KAAI,IAAM,KAAQ,CAAE,GAAG,KAAK,EAAY,GAGpC,MAAM,EAAK,IAAI;EAInB,AADA,KAAK,UAAU,IACf,KAAK,KAAK,KAAK,kCAAkC;CACrD;CAQA,MAAM,OACN;EACI,IAAG,KAAK,UACR;GACI,KAAK,KAAK,KAAK,gDAAgD;GAC/D;EACJ;EAGA,AADA,KAAK,WAAW,IAChB,KAAK,KAAK,KAAK,+CAAgD,EAAS,KAAK;EAE7E,IAAI,IAAuB,MAErB,IAAU,OAAO,GAAgB,MACvC;GACI,IACA;IACI,MAAM,EAAI;GACd,SACM,GACN;IAGI,AAFA,KAAK,KAAK,MAAM,sBAAuB,EAAO,IAAK,GAAQ,GAExD,MAAe,SAEd,IAAa;GAErB;EACJ;EAMA,AAHA,MAAM,EAAQ,wBAAwB,KAAK,SAAS,YAAY,KAAK,CAAC,GAGtE,MAAM,EAAQ,4BAA4B,YAAY,KAAK,eAAe,IAAI,CAAC;EAE/E,KAAI,IAAM,KAAQ,CAAE,GAAG,KAAK,EAAe,GAGvC,MAAM,EAAQ,4BAA4B,EAAK,IAAI,CAAC;EAIxD,AADA,MAAM,EAAQ,qBAAqB,YAAY,KAAK,MAAM,SAAS,QAAQ,CAAC,GAC5E,MAAM,EAAQ,oCAAoC,KAAK,MAAM,UAAU,QAAQ,CAAC;EAEhF,KAAI,IAAM,KAAc,OAAO,KAAK,KAAK,QAAQ,GACjD;GACI,IAAM,IAAU,KAAK,SAAS;GAE9B,AAAG,KAGC,MAAM,EAAQ,WAAY,WAAqB,EAAQ,QAAQ,CAAC;EAExE;EAEA,KAAI,IAAM,KAAa,OAAO,KAAK,KAAK,OAAO,GAC/C;GACI,IAAM,IAAS,KAAK,QAAQ;GAE5B,AAAG,KAGC,MAAM,EAAQ,UAAW,WAAoB,EAAO,QAAQ,CAAC;EAErE;EAKA,IAHA,KAAK,UAAU,IACf,KAAK,KAAK,KAAK,kCAAkC,GAE9C,MAAe,MAEd,MAAM;CAEd;AACJ;;;ACvVA,SAAgB,EAAa,GAC7B;CACI,OAAO;EAAE,GAAG,EAAO;EAAG,GAAG,EAAO;EAAG,GAAG,EAAO;CAAE;AACnD;AAKA,SAAgB,EAAU,GAC1B;CACI,OAAO,IAAI,EAAQ,EAAI,GAAG,EAAI,GAAG,EAAI,CAAC;AAC1C;AAKA,SAAgB,EAAa,GAC7B;CACI,OAAO;EAAE,GAAG,EAAK;EAAG,GAAG,EAAK;EAAG,GAAG,EAAK;EAAG,GAAG,EAAK;CAAE;AACxD;AAKA,SAAgB,EAAa,GAC7B;CACI,OAAO,IAAI,EAAW,EAAK,GAAG,EAAK,GAAG,EAAK,GAAG,EAAK,CAAC;AACxD;AAUA,SAAgB,EAAW,GAAsB,GACjD;CACI,IAAM,IAAO,CAAC;CAEd,KAAI,IAAM,KAAa;EAAE;EAAK;EAAK;CAAI,GAEnC,OAAO,eAAe,GAAM,GAAW;EACnC,WAAoB,EAAK,CAAC,CAAC;EAC3B,MAAM,MACN;GACI,IAAM,IAAO,EAAa,EAAK,CAAC;GAGhC,AAFA,EAAK,KAAa,GAElB,EAAM,CAAI;EACd;EACA,YAAY;EACZ,cAAc;CAClB,CAAC;CAGL,OAAO;AACX;AAGA,SAAgB,GAAe,GAAyB,GACxD;CACI,IAAM,IAAO,CAAC;CAEd,KAAI,IAAM,KAAa;EAAE;EAAK;EAAK;EAAK;CAAI,GAExC,OAAO,eAAe,GAAM,GAAW;EACnC,WAAoB,EAAK,CAAC,CAAC;EAC3B,MAAM,MACN;GACI,IAAM,IAAO,EAAa,EAAK,CAAC;GAGhC,AAFA,EAAK,KAAa,GAElB,EAAM,CAAI;EACd;EACA,YAAY;EACZ,cAAc;CAClB,CAAC;CAGL,OAAO;AACX;AAUA,SAAS,GAAY,GACrB;CACI,EAAK,mBAAmB,EAAI;CAC5B,IAAM,IAAQ,EAAK,eAAe,GAC5B,IAAS,EAAK;CASpB,OAPI,KAKJ,EAAO,mBAAmB,EAAI,GAEvB,EAAM,SAAS,EAAO,OAAO,EAAO,eAAe,CAAC,CAAC,KALjD,EAAM,MAAM;AAM3B;AAKA,SAAS,GAAW,GACpB;CACI,IAAM,IAAS,EAAO,MAAM;CAG5B,OAFA,EAAO,eAAe,EAAQ,KAAK,CAAC,GAE7B;AACX;AAUA,SAAgB,GAAsB,GAKtC;CACI,EAAK,mBAAmB,EAAI;CAC5B,IAAI,IAAY,EAAK,eAAe,CAAC,CAAC,MAAM,GAExC,IAAW,EAAK;CACpB,OAAM,IACN;EACI,IAAM,IAAgB,GAAY,CAAQ;EAU1C,AALG,EAAc,YAAY,IAAI,MAE7B,IAAY,EAAO,OAAO,GAAW,CAAa,CAAC,CAAC,CAAC,SAAS,CAAS,IAG3E,IAAW,EAAS;CACxB;CAEA,IAAM,IAAW,IAAI,EAAQ,GACvB,IAAW,IAAI,EAAW,GAC1B,IAAU,IAAI,EAAQ;CAG5B,OAFA,EAAU,UAAU,GAAS,GAAU,CAAQ,GAExC;EAAE;EAAU;EAAU;CAAQ;AACzC;;;AChHA,IAAM,oBAAmB,IAAI,QAA6B,GAKpD,qBAAgB,IAAI,QAA4C;AAEtE,SAAS,GAAsB,GAC/B;CAEI,IAAM,IADW,EAAK,UACG,MAAM;CAO/B,OALG,KAAmC,OAE3B,CAAC,IAGL,OAAO,KAAK,CAAM;AAC7B;AAEA,SAAS,GACL,GACA,GACA,GACA,GAEJ;CACI,IAAI,IAAS,GAAc,IAAI,CAAO;CAQtC,IANG,MAAW,KAAA,MAEV,oBAAS,IAAI,IAAI,GACjB,GAAc,IAAI,GAAS,CAAM,IAGlC,EAAO,IAAI,CAAI,GAEd;CAGJ,IAAM,IAAsB,CAAC;CAE7B,KAAI,IAAM,KAAQ,CAAE,GAAM,GAAG,EAAK,eAAe,EAAK,CAAE,GAEpD,IAAG,aAAgB,GACnB;EACI,IAAM,IAAQ,GAAsB,CAAI;EAExC,AAAG,EAAM,SAAS,KAEd,EAAS,KAAK,IAAK,EAAK,KAAM,YAAa,EAAM,KAAK,IAAI,GAAI;CAEtE;CAGD,EAAS,WAAW,MAKvB,EAAO,IAAI,CAAI,GACf,EAAQ,OAAO,KACX,sBAAuB,EAAM,IAAK,EAAI,4DAChC,EAAS,KAAK,IAAI,EAAG,+FAE/B;AACJ;AAeA,IAAM,qBAAY,IAAI,QAA8B;AAEpD,SAAS,EAAW,GACpB;CACI,IAAM,IAAO,GAAU,IAAI,CAAM;CAEjC,IAAG,MAAS,KAAA,GAER,MAAU,MAAM,uBAAwB,EAAO,GAAI,yDAAyD;CAGhH,OAAO;AACX;AAEA,SAAS,GAAU,GACnB;CACI,OAAO;AACX;AAEA,SAAS,GAAiB,GAAqC,GAC/D;CACI,IAAM,IAAQ,EAAY,MAAM,cAAc,CAAQ;CAEtD,IAAG,EAAE,aAAiB,IAElB,MAAU,MAAM,uCAAwC,EAAU,uBAAuB;CAG7F,OAAO;AACX;AAKA,SAAS,GAAY,GAAqB,GAA2B,GACrE;CACI,IAAG,EAAQ,aAAa,KAAA,GAEpB,OAAO;EACH,KAAK;GAAE,MAAM,GAAiB,GAAa,EAAQ,QAAQ;GAAG,MAAM,KAAA;EAAU;EAC9E,aAAa;CACjB;CAeJ,IAZG,EAAQ,aAAa,KAAA,MAEpB,EAAQ,WAAW;EAAE,GAAG;EAAG,GAAG;EAAG,GAAG;CAAE,IAGvC,EAAQ,aAAa,KAAA,MAEpB,EAAQ,WAAW;EAAE,GAAG;EAAG,GAAG;EAAG,GAAG;EAAG,GAAG;CAAE,IAG5B,EAAO,YAEZ,UACf;EACI,IAAM,IAAS,EAAY,KAAK,KAAK,EAAO,IAAI;EAEhD,IAAG,MAAW,KAAA,GAMV,OAFA,EAAO,KAAK,WAAW,EAAI,GAEpB;GAAE,KAAK;GAAQ,aAAa;EAAU;CAErD;CAEA,OAAO;EACH,KAAK;GAAE,MAAM,IAAI,EAAc,EAAO,IAAI,EAAY,KAAK;GAAG,MAAM,KAAA;EAAU;EAC9E,aAAa;CACjB;AACJ;AAKA,SAAS,GAAuB,GAA2B,GAC3D;CACI,IAAM,IAAW;CAOjB,AALG,EAAS,aAAa,KAAA,MAErB,EAAQ,WAAW,EAAS,WAG7B,EAAS,aAAa,KAAA,MAErB,EAAQ,WAAW,EAAS;AAEpC;AAKA,SAAS,EAAa,GACtB;CAMI,OALG,EAAK,uBAAuB,SAE3B,EAAK,qBAAqB,EAAW,gBAAgB,EAAK,QAAQ,IAG/D,EAAK;AAChB;AAMA,IAAsB,IAAtB,MAAsB,UACV,GACZ;CACI,OAAgB,WAAqB;CACrC,OAAgB;CAChB,OAAgB;CAChB,OAAgB;CAQhB,OAAgB,OAEZ,GACA,GACA,GACA,GAEJ;EACI,OAAO,MAAM,OAAO,GAAI,GAAS,GAAO,CAAI;CAChD;CAEA,OAAgB,QAEZ,GACA,GACA,GACA,GAEJ;EACI,OAAO,MAAM,QAAQ,GAAI,GAAS,GAAO,CAAI;CACjD;CAEA,IAAI,OACJ;EACI,OAAO,EAAW,IAAI,CAAC,CAAC,IAAI;CAChC;CAEA,IAAI,OACJ;EACI,OAAO,EAAW,IAAI,CAAC,CAAC,IAAI;CAChC;CAEA,IAAI,KAAK,GACT;EACI,EAAW,IAAI,CAAC,CAAC,IAAI,OAAO;CAChC;CAEA,IAAI,QACJ;EACI,OAAO,KAAK,QAAQ;CACxB;CAEA,aAAa,GAA+B,GAC5C;EACI,IAAM,IAAY,KAAK,QAAQ,WACzB,IAAQ,KAAK,GAAc;EAEjC,IAAG,MAAW,KAAA,KAAa,MAAW,IACtC;GACI,KAAI,IAAM,KAAQ,GAEd,EAAU,oBAAoB,CAAI;GAGtC;EACJ;EAEG,UAAW,IAKd;OAAG,OAAO,KAAW,UACrB;IACI,IAAM,IAAS,EAAM,MAAM,MAAS,EAAK,SAAS,CAAM;IAExD,AAAG,KAEC,EAAU,oBAAoB,GAAQ,CAAK;IAG/C;GACJ;GAEA,IAAG,WAAW,GACd;IACI,KAAI,IAAM,KAAQ,GAEd,EAAU,oBAAoB,GAAM,EAAO,KAAK;IAGpD;GACJ;GAEA,KAAI,IAAM,KAAQ,GAClB;IACI,IAAM,IAAQ,EAAO,MAAM,EAAK;IAEhC,AAAG,KAEC,EAAU,oBAAoB,GAAM,CAAK;GAEjD;EApBA;CAqBJ;CAEA,aAAa,GACb;EACI,IAAM,IAAY,KAAK,QAAQ,WACzB,IAAQ,KAAK,GAAc;EAEjC,IAAG,MAAS,KAAA,GACZ;GACI,KAAI,IAAM,KAAQ,GAEd,EAAU,oBAAoB,CAAI;GAGtC;EACJ;EAEA,IAAM,IAAS,EAAM,MAAM,MAAS,EAAK,SAAS,CAAI;EAEtD,AAAG,KAEC,EAAU,oBAAoB,CAAM;CAE5C;CAIA,KACA;EACI,OAAO,GAAU,KAAK,KAAK;CAC/B;CAMA,qBAAwC,GACxC;EACI,IAAM,IAAU,GAAU,KAAK,KAAK,GAC9B,IAAO,GAAY,MAAM,GAAS,KAAK,OAAO;EAEpD,GAAU,IAAI,MAAM,CAAI;EAExB,IAAM,IAAO,EAAK,IAAI,MAChB,IAAW;GAAE,UAAU,EAAQ;GAAU,UAAU,EAAQ;EAAS;EAE1E,EAAa,CAAI;EAEjB,IAAM,KAAiB,MACvB;GACI,EAAK,SAAS,IAAI,EAAM,GAAG,EAAM,GAAG,EAAM,CAAC;EAC/C,GAEM,KAAiB,MACvB;GACI,EAAa,CAAI,CAAC,CAAC,IAAI,EAAM,GAAG,EAAM,GAAG,EAAM,GAAG,EAAM,CAAC;EAC7D,GAIM,IAAW,QAAiB,EAAK,UAAU,CAAa,GACxD,IAAW,SAAqB,EAAa,CAAI,GAAG,CAAa;EAKvE,IAHA,KAAK,SAAsC,YAAY;GAAE,WAAW;GAAU,KAAK;EAAc,CAAC,GAClG,KAAK,SAAsC,YAAY;GAAE,WAAW;GAAU,KAAK;EAAc,CAAC,GAE/F,EAAK,gBAAgB,WACxB;GACI,GAAuB,GAAS,CAAa;GAE7C;EACJ;EAGA,AADA,EAAQ,WAAW,EAAS,UAC5B,EAAQ,WAAW,EAAS;CAChC;CAGA,KACA;EACI,IAAM,IAA0B,CAAC;EAEjC,AAAG,KAAK,KAAK,eAET,EAAM,KAAK,KAAK,IAAI;EAGxB,KAAI,IAAM,KAAS,KAAK,KAAK,uBAAuB,EAAK,GAErD,AAAG,EAAM,eAEL,EAAM,KAAK,CAAK;EAIxB,OAAO;CACX;CAEA,YACI,GACA,GACA,GACA,GACA,GAEJ;EACI,MAAM,GAAI,GAAS,GAAe,GAAgB,CAAI;EAEtD,IAAM,IAAO,EAAW,IAAI,GACtB,IAAM,EAAK,KAIX,IAAU,KAAK,GAAS;EAE9B,IAAG,EAAQ,aAAa,KAAA,GACxB;GACI,IAAM,IAAe,EAAQ,UAAU,EAAQ,QAAQ;GAEvD,AAAG,aAAwB,MAEvB,KAAK,KAAK,SAAS,EAAa;EAExC;EAEA,AAAG,EAAK,gBAAgB,WAEpB,EAAiB,IAAI,SAAW;GAAE,KAAK,GAAoB;EAAG,CAAC,GAI/D,EAAQ,SAAS,EAAQ,OAAO,KAAK,MAAM,KAAK,KAAiB,CAAC,CAC7D,OAAO,MACR;GACI,EAAQ,OAAO,MACX,wBAAyB,KAAK,KAAM,IAAK,KAAK,GAAI,2DAElD,CACJ;EAGJ,CAAC,CAAC,CACD,MAAM,MACP;GAOI,AANG,MAAS,KAAA,MAER,EAAK,SAAS,EAAI,MAClB,GAA4B,GAAS,KAAK,MAAM,KAAK,IAAI,CAAI,IAGjE,EAAI,OAAO;GAEX,IAAM,IAAS,EAAiB,IAAI,CAAG;GAEvC,AADA,EAAiB,OAAO,CAAG,GAC3B,IAAS;EACb,CAAC,KAED,EAAiB,IAAI,CAAG,IAE5B,EAAiB,IAAI,SAAW;GAAE,KAAK,GAAoB;EAAG,CAAC,IAI/D,KAAK,GAAqB;CAElC;CAMA,KACA;EACI,IACA;GACI,KAAK,GAAoB;EAC7B,SACM,GACN;GAEI,MADA,MAAM,QAAQ,GACR;EACV;CACJ;CAIA,KACA;EACI,KAAI,IAAM,KAAY,KAAK,kBAAkB,GAEzC,EAAgC,eAAe;EAGnD,IAAM,IAAU,KAAK,GAAS;EAE9B,AAAG,EAAQ,kBAAkB,KAAA,KAEzB,KAAK,aAAa,EAAQ,aAAa;CAE/C;CAEA,KACA;EACI,KAAI,IAAM,KAAY,KAAK,kBAAkB,GAEzC,EAAgC,YAAY;CAEpD;CAEA,UACA;EASI,AARA,KAAK,aAAa,GAIlB,KAAK,oBAAoB,GAEzB,KAAK,GAAiB,GAEtB,MAAM,QAAQ;EAEd,IAAM,IAAO,EAAW,IAAI;EAW5B,IAPG,EAAiB,IAAI,EAAK,GAAG,KAE5B,EAAiB,IAAI,EAAK,WAAW,CAAoD,CAAC,GAK3F,EAAK,gBAAgB,WAEpB;EAGJ,IAAM,IAAc,KAAK;EAEzB,AAAG,EAAY,WAEX,KAAK,QAAQ,KAAK,KAAK,KAAK,MAAM,EAAK,KAAK,EAAY,QAAQ,IAIhE,EAAK,IAAI,KAAK,QAAQ;CAE9B;AACJ;;;AC9jBA,SAAgB,GAAkB,GAAkC,GACpE;CACI,IAAM,IAA8B,CAAC;CAErC,KAAI,IAAM,KAAU,GAEhB,AAAG,aAAkB,KAEjB,EAAa,KAAK,CAAM;CAIhC,IAAI,IAAwB;CAE5B,OAAM,MAAY,OAClB;EACI,KAAI,IAAM,KAAU,GAEhB,IAAG,EAAO,SAAS,GAEf,OAAO;EAIf,IAAU,EAAQ;CACtB;AAGJ;AAEA,SAAS,GAAa,GAAqB,GAC3C;CACI,IAAG,GAAQ,SAAS,KAAA,GAEhB,OAAO;CAGX,IAAM,IAAQ,EAAO,MAA0B;CAE/C,KAAI,IAAM,KAAO,EAAO,MAEpB,IAAG,CAAC,EAAK,SAAS,CAAG,GAEjB,OAAO;CAIf,OAAO;AACX;AAEA,SAAgB,GACZ,GACA,GACA,GAEJ;CACI,IAAG,MAAS,QAAQ,CAAC,EAAK,OAAO,EAAK,eAAe,QAAQ,EAAK,gBAAgB,MAE9E;CAGJ,IAAM,IAAS,GAAkB,GAAU,EAAK,UAAU;CAE1D,IAAG,MAAW,KAAA,KAAa,CAAC,GAAa,GAAQ,CAAM,GAEnD;CAGJ,IAAM,IAAS,EAAK,UAAU,IAAM,EAAI;CAExC,OAAO;EACH;EACA,MAAM,EAAK;EACX,OAAO,EAAK;EACZ,UAAU,EAAK;EACf,QAAQ,KAAU,EAAQ,KAAK;CACnC;AACJ;;;AChGA,IAAa,KAAb,MACA;CACI;CAEA,cACA;EACI,KAAK,yBAAS,IAAI,IAAI;CAC1B;CAKA,iBAAyB,GACzB;EAEI,IAAM,IAAsC;GACxC,OAAO;GACP,OAAO;GACP,MAAM;GACN,MAAM;GACN,OAAO;GACP,OAAO;GACP,MAAM;EACV;EAEA,OAAO,EAAO,MAAU,EAAO;CACnC;CAKA,cAAsB,GAAmB,GACzC;EAEI,IAAM,oBAAM,IAAI,KAAK;EAYrB,OAAO;GACH,GAAI,IAZM,EAAI,SAAS,IAAI,MAAM,GAMP,GALd,EAAI,WAAW,CAAC,CAAC,SAAS,CAAC,CACtC,SAAS,GAAG,GAIiB,EAAS,GAH3B,EAAI,WAAW,CAAC,CAAC,SAAS,CAAC,CACtC,SAAS,GAAG,GAE8B,EAAS,GAD3C,EAAI,SAAS,KAAK,KAAK,OAAO,KACuB,GAM/C,KAJA,EAAM,YAIA,EAAY,MAAO,EAAU;GAClD,KAAK,iBAAiB,CAAK;GAC3B;GACA;EACJ;CACJ;CAEA,MAAM,GAAmB,GAAkB,GAAG,GAC9C;EACI,IAAM,CAAE,GAAQ,GAAc,GAAY,KAAe,KAAK,cAAc,GAAU,OAAO;EAC7F,QAAQ,MAAM,GAAQ,GAAc,GAAY,GAAY,GAAS,GAAG,CAAI;CAChF;CAEA,MAAM,GAAmB,GAAkB,GAAG,GAC9C;EACI,IAAM,CAAE,GAAQ,GAAc,GAAY,KAAe,KAAK,cAAc,GAAU,OAAO;EAC7F,QAAQ,MAAM,GAAQ,GAAc,GAAY,GAAY,GAAS,GAAG,CAAI;CAChF;CAEA,KAAK,GAAmB,GAAkB,GAAG,GAC7C;EACI,IAAM,CAAE,GAAQ,GAAc,GAAY,KAAe,KAAK,cAAc,GAAU,MAAM;EAC5F,QAAQ,KAAK,GAAQ,GAAc,GAAY,GAAY,GAAS,GAAG,CAAI;CAC/E;CAEA,KAAK,GAAmB,GAAkB,GAAG,GAC7C;EACI,IAAM,CAAE,GAAQ,GAAc,GAAY,KAAe,KAAK,cAAc,GAAU,MAAM;EAC5F,QAAQ,KAAK,GAAQ,GAAc,GAAY,GAAY,GAAS,GAAG,CAAI;CAC/E;CAEA,MAAM,GAAmB,GAAkB,GAAG,GAC9C;EACI,IAAM,CAAE,GAAQ,GAAc,GAAY,KAAe,KAAK,cAAc,GAAU,OAAO;EAC7F,QAAQ,MAAM,GAAQ,GAAc,GAAY,GAAY,GAAS,GAAG,CAAI;CAChF;CAKA,KAAK,GAAmB,GACxB;EACI,IAAM,IAAW,GAAI,EAAU,GAAI;EACnC,KAAK,OAAO,IAAI,GAAU,YAAY,IAAI,CAAC;CAC/C;CAKA,QAAQ,GAAmB,GAC3B;EACI,IAAM,IAAW,GAAI,EAAU,GAAI,KAC7B,IAAY,KAAK,OAAO,IAAI,CAAQ;EAE1C,IAAG,MAAc,KAAA,GACjB;GACI,IAAM,IAAW,YAAY,IAAI,IAAI;GACrC,KAAK,OAAO,OAAO,CAAQ;GAE3B,IAAM,CAAE,GAAQ,GAAc,GAAY,KAAe,KAAK,cAAc,GAAU,OAAO;GAC7F,QAAQ,KACJ,GAAI,EAAQ,UAAW,EAAO,iBAAkB,EAAS,QAAQ,CAAC,EAAG,KACrE,GACA,GACA,CACJ;EACJ,OAGI,QAAQ,KAAK,IAAK,EAAU,YAAa,EAAO,iBAAiB;CAEzE;AACJ,GCxHa,IAAb,MACA;CACI,MAAM,GAAoB,GAAmB,GAAG,GAChD,CAEA;CAEA,MAAM,GAAoB,GAAmB,GAAG,GAChD,CAEA;CAEA,KAAK,GAAoB,GAAmB,GAAG,GAC/C,CAEA;CAEA,KAAK,GAAoB,GAAmB,GAAG,GAC/C,CAEA;CAEA,MAAM,GAAoB,GAAmB,GAAG,GAChD,CAEA;CAEA,KAAK,GAAoB,GACzB,CAEA;CAEA,QAAQ,GAAoB,GAC5B,CAEA;AACJ,GC7Ba,IAAb,MACA;CACI;CACA;CACA;CAEA,YAAY,GAAmB,IAAsB,QAAQ,IAA2B,IAAI,EAAY,GACxG;EAGI,AAFA,KAAK,WAAW,GAChB,KAAK,UAAU,GACf,KAAK,WAAW;CACpB;CAKA,eAAsB,GAA0B,GAChD;EAEI,AADA,KAAK,UAAU,GACf,KAAK,WAAW;CACpB;CAEA,MAAM,GAAkB,GAAG,GAC3B;EACI,AAAG,KAAK,UAAU,OAAO,KAErB,KAAK,QAAQ,MAAM,KAAK,UAAU,GAAS,GAAG,CAAI;CAE1D;CAEA,MAAM,GAAkB,GAAG,GAC3B;EACI,AAAG,KAAK,UAAU,OAAO,KAErB,KAAK,QAAQ,MAAM,KAAK,UAAU,GAAS,GAAG,CAAI;CAE1D;CAEA,KAAK,GAAkB,GAAG,GAC1B;EACI,AAAG,KAAK,UAAU,MAAM,KAEpB,KAAK,QAAQ,KAAK,KAAK,UAAU,GAAS,GAAG,CAAI;CAEzD;CAEA,KAAK,GAAkB,GAAG,GAC1B;EACI,AAAG,KAAK,UAAU,MAAM,KAEpB,KAAK,QAAQ,KAAK,KAAK,UAAU,GAAS,GAAG,CAAI;CAEzD;CAEA,MAAM,GAAkB,GAAG,GAC3B;EACI,AAAG,KAAK,UAAU,OAAO,KAErB,KAAK,QAAQ,MAAM,KAAK,UAAU,GAAS,GAAG,CAAI;CAE1D;CAEA,KAAK,GACL;EACI,AAAG,KAAK,aAAa,UAEjB,KAAK,QAAQ,KAAK,KAAK,UAAU,CAAK;CAE9C;CAEA,QAAQ,GACR;EACI,AAAG,KAAK,aAAa,UAEjB,KAAK,QAAQ,QAAQ,KAAK,UAAU,CAAK;CAEjD;CAKA,UAAkB,GAClB;EACI,IAAG,KAAK,aAAa,QAEjB,OAAO;EAGX,QAAQ,KAAK,UAAb;GAEI,KAAK,SACD,OAAO;GACX,KAAK,SACD,OAAO,MAAU;GACrB,KAAK,QACD,OAAO,MAAU,UAAU,MAAU,UAAU,MAAU;GAC7D,KAAK,QACD,OAAO,MAAU,UAAU,MAAU;GACzC,KAAK,SACD,OAAO,MAAU;GACrB,SACI,OAAO;EACf;CACJ;AACJ,GAOa,IAAb,MACA;CACI;CACA;CACA;CAQA,YAAY,IAAmB,SAAS,IAA2B,IAAI,GAAe,GACtF;EAGI,AAFA,KAAK,UAAU,GACf,KAAK,QAAQ,GACb,KAAK,0BAAU,IAAI,IAAI;CAC3B;CAKA,WAAkB,GAClB;EAII,AAHA,KAAK,UAAU,GAGf,KAAK,QAAQ,SAAS,MACtB;GACI,AAAG,aAAkB,KAEjB,EAAO,eAAe,KAAK,SAAS,KAAK,KAAK;EAEtD,CAAC;CACL;CAKA,SAAgB,GAChB;EAII,AAHA,KAAK,QAAQ,GAGb,KAAK,QAAQ,SAAS,MACtB;GACI,AAAG,aAAkB,KAEjB,EAAO,eAAe,KAAK,SAAS,KAAK,KAAK;EAEtD,CAAC;CACL;CAKA,WACA;EACI,OAAO,KAAK;CAChB;CAOA,UAAiB,GACjB;EACI,IAAI,IAAS,KAAK,QAAQ,IAAI,CAAQ;EAOtC,OANI,MAEA,IAAS,IAAI,EAAW,GAAU,KAAK,OAAO,KAAK,OAAO,GAC1D,KAAK,QAAQ,IAAI,GAAU,CAAM,IAG9B;CACX;AACJ,GCxKa,KAAb,MAAa,UACD,GAEZ;CACI;CACA;CACA;CACA;CACA;CACA;CAEA;CAEA,YAAY,GACZ;EAeI,AAdA,MAAM,CAAO,GAEb,KAAK,QAAQ,EAAQ,OACrB,KAAK,OAAO,EAAQ,MACpB,KAAK,WAAW,EAAQ,UACxB,KAAK,QAAQ,EAAQ,OACrB,KAAK,YAAY,EAAQ,WACzB,KAAK,SAAS,EAAQ,UAAU,IAAI,EAAW,gBAAgB,GAC/D,KAAK,KAAiB,EAAQ,eAM9B,KAAK,MAAM,iBAAiB;CAChC;CAQA,iBAAoC,GACpC;EACI,KAAK,MAAM,iBAAiB,CAAC,EAAE,MAAM,CAAQ;CACjD;CAEA,MAAM,QAAQ,GAAe,GAAgB,IAAmB,CAAC,GACjE;EACI,IAAM,IAAc,KAAK,GAAe,IAElC,IAAS,MAAM,QAAQ,IACzB,MAAM,KAAK,EAAE,QAAQ,EAAM,SAAS,KAAK,SAAS,KAAK,OAAO,GAAM,CAAK,CAAC,CAC9E;EAEA,KAAI,IAAM,KAAQ,GAClB;GACI,IAAM,IAAO,IAAI,EAAc,GAAM,KAAK,KAAK;GAO/C,AALG,MAAS,KAAA,MAER,EAAK,SAAS,IAGlB,KAAK,KAAK,KAAK,GAAM;IAAE;IAAM;GAAK,GAAG,GAAa,QAAQ;EAC9D;CACJ;CAEA,kBAAkB,GAAW,GAC7B;EACI,OAAO,GAAmB,KAAK,mBAAmB,CAAU,GAAG,KAAK,MAAM,YAAY,CAAG,GAAG,CAAM;CACtG;CAEA,WAAW,GAAY,GAAY,GACnC;EACI,OAAO,GAAmB,KAAK,mBAAmB,CAAU,GAAG,KAAK,MAAM,KAAK,GAAG,CAAC,GAAG,CAAM;CAChG;CAEA,kBAAkB,GAAiB,GAAgB,GACnD;EACI,OAAO,KAAK,kBAAkB,EAAO,cAAc,CAAK,GAAG,CAAM;CACrE;CAEA,iBAAiB,GACjB;EACI,OAAO,GAAkB,KAAK,mBAAmB,CAAU,GAAG,CAAI;CACtE;CAMA,kBAA2B,GAC3B;EACI,OAAO,MAAM,kBAAkB,CAAQ;CAC3C;CAEA,2BAA8C,GAC9C;EACI,OAAO,IAAI,EAAyB;GAChC,GAAI;GACJ,OAAO,KAAK;GACZ,MAAM,KAAK;GACX,UAAU,KAAK;GACf,OAAO,KAAK;GACZ,WAAW,KAAK;GAChB,QAAQ,KAAK;EACjB,CAAC;CACL;AACJ,GC5Ia,KAAgB,GCOhB,KAAb,MACA;CACI,qBAAiB,IAAI,IAAuB;CAE5C,KAAK,GACL;EACI,IAAM,IAAO,KAAK,GAAM,IAAI,CAAI;EAE7B,UAAS,KAAA,GAKZ,OAAO,EAAK,IAAI;CACpB;CAEA,KAAK,GAAe,GAAe,IAAA,GACnC;EAEI,AADA,EAAI,KAAK,SAAS,MAClB,EAAI,KAAK,WAAW,EAAK;EAEzB,IAAI,IAAO,KAAK,GAAM,IAAI,CAAI;EAQ9B,IANG,MAAS,KAAA,MAER,IAAO,CAAC,GACR,KAAK,GAAM,IAAI,GAAM,CAAI,IAG1B,EAAK,UAAU,GAClB;GACI,EAAI,KAAK,QAAQ;GACjB;EACJ;EAEA,EAAK,KAAK,CAAG;CACjB;CAEA,MAAM,GACN;EACI,IAAM,IAAO,KAAK,GAAM,IAAI,CAAI;EAE7B,UAAS,KAAA,GAKZ;QAAI,IAAM,KAAO,GAEb,EAAI,KAAK,QAAQ;GAGrB,KAAK,GAAM,OAAO,CAAI;EAHD;CAIzB;CAEA,WACA;EACI,KAAI,IAAM,KAAQ,CAAE,GAAG,KAAK,GAAM,KAAK,CAAE,GAErC,KAAK,MAAM,CAAI;CAEvB;AACJ,GClDa,KAAb,MACA;CACI,KAAiC;CACjC,KAAiC;CACjC,qBAAS,IAAI,IAAsB;CACnC;CAEA,YAAY,GACZ;EACI,KAAK,KAAO,EAAO,UAAU,aAAa;CAC9C;CAMA,MAAM,aACN;EAMI,AALA,KAAK,GAAK,KAAK,8BAA8B,GAE7C,KAAK,KAAU,MAAM,EAAuB,GAC5C,KAAK,KAAW,MAAM,KAAK,GAAQ,mBAAmB,MAAM,GAE5D,KAAK,GAAK,KAAK,2BAA2B;CAC9C;CAEA,MAAM,UACN;EAYI,AAXA,KAAK,GAAK,KAAK,8BAA8B,GAE7C,KAAK,GAAO,MAAM,GAClB,KAAK,KAAW,MAEhB,AAGI,KAAK,QADL,KAAK,GAAQ,QAAQ,GACN,OAGnB,KAAK,GAAK,KAAK,yBAAyB;CAC5C;CAMA,MAAM,UAAU,GAChB;EACI,IAAG,CAAC,KAAK,MAAW,CAAC,KAAK,IAEtB,MAAU,MAAM,6BAA6B;EAGjD,KAAK,GAAK,MAAM,uBAAwB,GAAO;EAC/C,IAAM,IAAM,MAAM,KAAK,GAAQ,eAAe,GAAM,EAAE,QAAQ,KAAK,GAAS,CAAC;EAE7E,OADA,KAAK,GAAO,IAAI,GAAM,CAAG,GAClB;CACX;CAEA,OAAO,GACP;EACI,OAAO,KAAK,GAAO,IAAI,CAAI;CAC/B;CAMA,MAAM,YACF,GACA,GACA,GACA,GAEJ;EACI,IAAG,CAAC,KAAK,IAEL,MAAU,MAAM,6BAA6B;EAGjD,IAAM,IAAuC,KAAO,KAAK,MAAY,KAAA,GAE/D,IAA8C,EAAE,GAAG,EAAQ;EAOjE,OALG,MAAW,KAAA,MAEV,EAAa,SAAS,IAGnB,KAAK,GAAQ,iBAAiB,GAAM,GAAK,CAAY;CAChE;CAMA,gBAAgB,GAChB;EACI,IAAG,CAAC,KAAK,IAEL,MAAU,MAAM,6BAA6B;EAGjD,KAAK,GAAQ,SAAS;CAC1B;CAEA,kBACA;EAMI,OALI,KAAK,KAKF,KAAK,GAAQ,SAHT;CAIf;CAEA,aAAa,GAAgB,GAC7B;EACI,EAAI,SAAS;CACjB;AACJ;;;AC/HA,SAAgB,IAChB;CACI,OAAO,IAAI,EAAQ,GAAG,OAAO,CAAC;AAClC;AAWA,eAAsB,GAAU,GAChC;CAQI,OAPG,MAAe,KAAA,IAEP,MAAM,GAAa,IAKvB,MAAM,GAAa,EAAc,cAA0B,CAAC;AACvE;;;ACbA,GAAuB;AAavB,IAAa,KAAb,MACA;CACI;CAIA;CAIA;CAGA;CAEA;CAEA,YACI,GACA,GACA,GAEJ;EAGI,AAFA,KAAK,KAAU,GACf,KAAK,KAAe,GACpB,KAAK,KAAO,EAAO,UAAU,aAAa;CAC9C;CAGA,IAAW,eACX;EACI,OAAO,KAAK,OAAW,KAAA;CAC3B;CAGA,IAAW,eACX;EACI,OAAO,KAAK;CAChB;CAKA,cACA;EACI,OAAO,IAAI,GAAM,KAAK,EAAO;CACjC;CAEA,MAAa,cACT,GACA,IAA0B,EAAe,GACzC,GAEJ;EACI,IAAM,IAAQ,MAAM,KAAK,GAAW;EAEpC,KAAK,GAAK,MACN,kCAAmC,EAAc,EAAG,IAAK,EAAc,EAAG,IAAK,EAAc,EAAG,KACpG;EAEA,IAAI;EAEJ,AAAG,MAA8B,KAAA,MAE7B,IAAgB,EAAE,6BAA0B;EAIhD,IAAM,IAAS,IAAI,EAAY,IAAM,GAAO,CAAa;EACzD,EAAM,cAAc,GAAe,CAAM;CAC7C;CAMA,MAAM,KACN;EACI,AAAG,KAAK,OAAkB,KAAA,MAEtB,KAAK,KAAgB,GAAU,KAAK,EAAY;EAGpD,IAAM,IAAU,KAAK;EAErB,IACA;GACI,IAAM,IAAQ,MAAM;GASpB,OALG,KAAK,OAAkB,MAEtB,KAAK,KAAS,IAGX;EACX,SACM,GACN;GAMI,MALG,KAAK,OAAkB,MAEtB,KAAK,KAAgB,KAAA,IAGnB;EACV;CACJ;CAMA,uBACI,GACA,GACA,GACA,IAAY,IAEhB;EACI,IAAM,IAAQ,IAAI,EAAiB,GAAM,GAAW,CAAK;EAEzD,OADA,EAAM,YAAY,GACX;CACX;CAKA,uBACI,GACA,GACA,GACA,IAAY,GAEhB;EACI,IAAM,IAAQ,IAAI,EAAiB,GAAM,GAAW,CAAK;EAEzD,OADA,EAAM,YAAY,GACX;CACX;CAKA,iBACI,GACA,GACA,GACA,IAAY,GAEhB;EACI,IAAM,IAAQ,IAAI,GAAW,GAAM,GAAU,CAAK;EAElD,OADA,EAAM,YAAY,GACX;CACX;CAOA,gBACI,GACA,GACA,GACA,GACA,GACA,GACA,IAAY,GAEhB;EACI,IAAM,IAAQ,IAAI,GAAU,GAAM,GAAU,GAAW,GAAO,GAAU,CAAK;EAE7E,OADA,EAAM,YAAY,GACX;CACX;CAKA,oBACI,GACA,GACA,GACA,GACA,GACA,IAAY,GAEhB;EACI,IAAM,IAAQ,IAAI,GAAc,GAAM,GAAU,GAAO,GAAQ,CAAK;EAEpE,OADA,EAAM,YAAY,GACX;CACX;CAMA,aACI,GACA,IAAuD,CAAC,GACxD,GAEJ;EAEI,OAAO,EAAY,aAAa,GAAM;GADb,UAAU;GAAG,UAAU;GACW,GAAG;EAAQ,GAAG,CAAK;CAClF;CAKA,UACI,GACA,IAAmF,CAAC,GACpF,GAEJ;EAEI,OAAO,EAAY,UAAU,GAAM;GADV,MAAM;GACyB,GAAG;EAAQ,GAAG,CAAK;CAC/E;CAKA,aACI,GACA,IAA0E,CAAC,GAC3E,GAEJ;EAEI,OAAO,EAAY,aAAa,GAAM;GADb,OAAO;GAAG,QAAQ;GAAG,cAAc;GACD,GAAG;EAAQ,GAAG,CAAK;CAClF;CAKA,eACI,GACA,IAAkF,CAAC,GACnF,GAEJ;EAEI,OAAO,EAAY,eAAe,GAAM;GADf,QAAQ;GAAG,aAAa;GAAG,gBAAgB;GACP,GAAG;EAAQ,GAAG,CAAK;CACpF;CAMA,MAAa,UACT,GACA,GAEJ;EACI,KAAK,GAAK,MAAM,kBAAmB,GAAgB;EAEnD,IACA;GACI,IAAM,IAAS,MAAM,GAAwB,GAAe,CAAK;GAEjE,OADA,KAAK,GAAK,MAAM,8BAA+B,GAAgB,GACxD;EACX,SACO,GACP;GAEI,MADA,KAAK,GAAK,MAAM,yBAA0B,KAAkB,CAAK,GAC3D;EACV;CACJ;CAMA,MAAa,aACT,GACA,GACA,GAUJ;EACI,KAAK,GAAK,MAAM,0BAA2B,GAAgB;EAG3D,IAAI,GACA,IAAW;EAEf,AAAG,EAAU,SAAS,MAElB,IAAU,EAAE,aAAU,GACtB,IAAW,EAAU,KAAK,IAAI;EAGlC,IACA;GACI,IAAM,IAAS,MAAM,EAAgB,GAAe,GAAO,CAAO;GAElE,OADA,KAAK,GAAK,MAAM,iCAAkC,GAAW,GACtD;EACX,SACO,GACP;GAEI,MADA,KAAK,GAAK,MAAM,iCAAkC,KAAkB,CAAK,GACnE;EACV;CACJ;CAMA,MAAM,UACN;EAMI,AALA,KAAK,GAAK,KAAK,0BAA0B,GAEzC,KAAK,KAAS,KAAA,GACd,KAAK,KAAgB,KAAA,GAErB,KAAK,GAAK,KAAK,oCAAoC;CACvD;AACJ,GCjVM,KAAuB,GAUhB,KAAb,MACA;CACI;CACA;CACA;CAGA,8BAAsB,IAAI,IAA6B;CAGvD,gCAAwB,IAAI,IAA0B;CAGtD,qCAA6B,IAAI,IAAqC;CAMtE,mCAA2B,IAAI,QAAqD;CAIpF,YAAY,GAAgB,GAA2B,GACvD;EAGI,AAFA,KAAK,OAAO,GACZ,KAAK,eAAe,GACpB,KAAK,OAAO,GAAQ,UAAU,cAAc,KAAK,IAAI,EAAW,cAAc;CAClF;CASA,WAAmB,GACnB;EACI,IAAM,IAAU,EAAK,QAAQ,GAAG;EAMhC,OALG,MAAY,KAEJ,EAAE,UAAU,EAAK,IAGrB;GACH,UAAU,EAAK,UAAU,GAAG,CAAO;GACnC,UAAU,EAAK,UAAU,IAAU,CAAC;EACxC;CACJ;CAOA,MAAc,eAAe,GAC7B;EACI,IAAM,IAAS,KAAK,YAAY,IAAI,CAAQ;EAC5C,IAAG,GAGC,OADA,EAAO,YACA,EAAO;EAGlB,IAAI,IAAW,KAAK,mBAAmB,IAAI,CAAQ;EACnD,AAAI,MAEA,IAAW,KAAK,gBAAgB,CAAQ,GACxC,KAAK,mBAAmB,IAAI,GAAU,CAAQ;EAGlD,IAAM,IAAY,MAAM,GAIlB,IAAQ,KAAK,YAAY,IAAI,CAAQ;EAM3C,OALG,KAEC,EAAM,YAGH;CACX;CAUA,MAAc,gBAAgB,GAC9B;EACI,KAAK,KAAK,MAAM,4BAA6B,GAAW;EAExD,IAAM,IAAQ,KAAK,aAAa,YAAY;EAE5C,IACA;GACI,IAAM,IAAY,MAAM,KAAK,aAAa,UAAU,GAAU,CAAK;GAKnE,OAHA,KAAK,YAAY,IAAI,GAAU;IAAE;IAAW;IAAO,UAAU;GAAE,CAAC,GAChE,KAAK,KAAK,MAAM,2BAA4B,GAAW,GAEhD;EACX,SACO,GACP;GAEI,MADA,EAAM,QAAQ,GACR;EACV,UAEA;GACI,KAAK,mBAAmB,OAAO,CAAQ;EAC3C;CACJ;CAMA,mBAA2B,GAAmB,GAC9C;EACI,IAAI,IAAa,KAAK,iBAAiB,IAAI,CAAK;EAChD,AAAI,MAEA,oBAAa,IAAI,IAAqC,GACtD,KAAK,iBAAiB,IAAI,GAAO,CAAU;EAG/C,IAAM,IAAS,EAAW,IAAI,CAAQ;EACtC,IAAG,GAEC,OAAO;EAGX,IAAM,IAAU,KAAK,aAAa,UAAU,GAAU,CAAK;EAC3D,EAAW,IAAI,GAAU,CAAO;EAEhC,IAAM,IAAkB;EAMxB,OALA,EAAQ,YACR;GACI,EAAgB,OAAO,CAAQ;EACnC,CAAC,GAEM;CACX;CAKA,qBAA6B,GAA4B,GAAmB,GAC5E;EACI,IAAM,IAAO,EAAU,OAAO,MAAM,MAAU,EAAM,SAAS,CAAQ;EACrE,IAAG,CAAC,GACJ;GACI,IAAM,IAAY,EAAU,OAAO,KAAK,MAAU,EAAM,IAAI,CAAC,CAAC,KAAK,IAAI;GACvE,MAAU,MACN,SAAU,EAAU,gCAAiC,EAAU,uBACvC,GAC5B;EACJ;EAEA,OAAO;CACX;CAcA,MAAM,KAAK,GACX;EACI,IAAM,EAAE,aAAU,gBAAa,KAAK,WAAW,CAAI;EAEnD,IAAG,GACH;GAEI,IAAM,IAAa,KAAK,cAAc,IAAI,CAAI;GAC9C,IAAG,GACH;IAEI,IAAM,IAAS,KAAK,YAAY,IAAI,CAAQ;IAM5C,OALG,KAEC,EAAO,YAGJ;GACX;GAGA,IAAM,IAAY,MAAM,KAAK,eAAe,CAAQ,GAG9C,IAAO,KAAK,qBAAqB,GAAW,GAAU,CAAI;GAGhE,OAFA,KAAK,cAAc,IAAI,GAAM,CAAI,GAE1B;EACX;EAGA,OAAO,KAAK,eAAe,CAAQ;CACvC;CAKA,SAAS,GACT;EACI,IAAM,IAAa,KAAK,cAAc,IAAI,CAAI;EAC9C,IAAG,CAAC,GAEA,MAAU,MAAM,UAAW,EAAM,oCAAoC;EAGzE,OAAQ,EAAoB,eAAe,GAAI,EAAW,KAAM,UAAU;CAC9E;CAYA,MAAM,GAAe,GACrB;EACI,IAAM,IAAa,KAAK,cAAc,IAAI,CAAI;EAC9C,IAAG,CAAC,GAEA,MAAU,MAAM,UAAW,EAAM,oCAAoC;EAGzE,IAAM,IAAO,IAAI,EAAK,GAAI,EAAW,KAAM,SAAS,GAAM;GACtD,QAAQ;GACR,oBAAoB;EACxB,CAAC;EAMD,OAFA,EAAK,WAAW,MAET;CACX;CAMA,MAAM,YAAY,GAAe,GACjC;EACI,IAAM,EAAE,aAAU,gBAAa,KAAK,WAAW,CAAI;EACnD,IAAG,GAEC,MAAU,MACN,uBAAwB,EAAM,mBAAoB,EAAU,wCAChE;EAIJ,IAAM,KAAU,MADQ,KAAK,mBAAmB,GAAU,CAAK,EAAA,CACrC,yBAAyB,KAAA,GAAW,IAAO,EAAE,kBAAkB,GAAK,CAAC;EAI/F,KAAI,IAAM,KAAQ,EAAQ,WAC1B;GACI,EAAK,WAAW;GAEhB,KAAI,IAAM,KAAQ,EAAK,eAAe,GAElC,EAAK,WAAW;EAExB;EAEA,OAAO;CACX;CAYA,MAAM,QAAQ,GACd;EACI,IAAM,IAAQ,EAAM,QACd,IAAkD,CAAC,GACrD,IAAY,GACZ,IAAS,GAKP,IAAY,YAClB;GACI,OAAM,IAAY,EAAM,SACxB;IACI,IAAM,IAAO,EAAM;IACnB;IAEA,IAAI,IAAY;IAEhB,IACA;KAEI,MAAM,KAAK,KAAK,CAAI;IACxB,SACO,GACP;KAEI,AADA,IAAY,IACZ,EAAS,KAAK;MAAE;MAAM;KAAM,CAAC;IACjC;IAEA,AAAG,MAEC,KAEA,KAAK,KAAK,KAAK;KACX,MAAM;KACN,UAAU;KACV,SAAS;MACL;MACA;MACA;KACJ;IACJ,CAAC;GAET;EACJ,GAEM,IAA4B,CAAC,GAC7B,IAAc,KAAK,IAAI,IAAsB,CAAK;EACxD,KAAI,IAAI,IAAI,GAAG,IAAI,GAAa,KAE5B,EAAQ,KAAK,EAAU,CAAC;EAK5B,IAFA,MAAM,QAAQ,IAAI,CAAO,GAEtB,EAAS,SAAS,GACrB;GACI,IAAM,IAAc,EAAS,KAAK,MAAY,EAAQ,IAAI,CAAC,CAAC,KAAK,IAAI;GAErE,MAAU,eACN,EAAS,KAAK,MAAY,EAAQ,KAAK,GACvC,sBAAuB,EAAS,OAAQ,MAAO,EAAO,WAAY,GACtE;EACJ;EAEA,KAAK,KAAK,KAAK;GACX,MAAM;GACN,UAAU;GACV,SAAS,EAAE,SAAM;EACrB,CAAC;CACL;CAMA,QAAQ,GACR;EACI,IAAM,EAAE,gBAAa,KAAK,WAAW,CAAI,GACnC,IAAS,KAAK,YAAY,IAAI,CAAQ;EACxC,UAKJ,EAAO,YAEJ,EAAO,YAAY,IACtB;GACI,KAAK,KAAK,MAAM,8BAA+B,EAAU,sBAAsB;GAG/E,KAAI,IAAM,CAAE,GAAc,MAAU,KAAK,eAErC,AAAG,EAAa,WAAW,GAAI,EAAU,EAAE,MAEvC,EAAK,QAAQ,GACb,KAAK,cAAc,OAAO,CAAY;GAM9C,AAFA,EAAO,UAAU,QAAQ,GACzB,EAAO,MAAM,QAAQ,GACrB,KAAK,YAAY,OAAO,CAAQ;EACpC;CACJ;CAEA,MAAM,UACN;EACI,KAAI,IAAM,KAAU,KAAK,YAAY,OAAO,GAGxC,AADA,EAAO,UAAU,QAAQ,GACzB,EAAO,MAAM,QAAQ;EAIzB,AADA,KAAK,YAAY,MAAM,GACvB,KAAK,cAAc,MAAM;CAC7B;AACJ,GC9Za,KAAb,MACA;CACI;CACA;CACA;CACA;CAEA,KAA4C;CAC5C,KAAsC;CAEtC,KAA8C,CAAC;CAC/C,KAAkB;CAElB,KAAU;CACV,KAAa;CAEb;CACA;CAEA,UAAiB;CAIjB,YACI,GACA,GACA,GACA,GAEJ;EASI,AARA,KAAK,KAAgB,GACrB,KAAK,KAAO,GACZ,KAAK,KAAgB,GACrB,KAAK,KAAO,GAAQ,UAAU,aAAa,KAAK,IAAI,EAAW,aAAa,GAE5E,KAAK,WAAyB;GAAE,KAAK,GAAa;EAAG,GACrD,KAAK,WAA4B;GAAE,KAAK,GAAc;EAAG,GAEtD,EAAU,KAET,OAAO,iBAAiB,UAAU,KAAK,EAAmB;CAElE;CAMA,IAAI,eACJ;EACI,OAAO,KAAK,GAAc,cAAc,SAAS;CACrD;CAEA,IAAI,WACJ;EACI,OAAO,KAAK;CAChB;CAGA,IAAI,YACJ;EACI,OAAO,KAAK;CAChB;CAEA,IAAI,UAAU,GACd;EACI,IAAG,CAAC,OAAO,SAAS,CAAK,KAAK,IAAQ,GAElC,MAAU,WAAW,+CAAgD,GAAQ;EAIjF,AADA,KAAK,KAAa,GAClB,KAAK,GAAK,MAAM,qBAAsB,GAAQ;CAClD;CAQA,eAAe,GACf;EACI,KAAK,KAAc;CACvB;CAKA,gBAAgB,GAChB;EACI,KAAK,KAAgB;CACzB;CAUA,sBAAsB,GAA0B,GAChD;EACI,IAAM,IAAK,KAAK;EAMhB,OALA,KAAK,MAAmB,GAExB,KAAK,GAAgB,KAAK;GAAE;GAAI;GAAU,kBAAkB,GAAS,oBAAoB;EAAK,CAAC,GAC/F,KAAK,GAAK,MAAM,kCAAmC,EAAI,EAAE,SAGzD;GACI,IAAM,IAAQ,KAAK,GAAgB,WAAW,MAAe,EAAW,OAAO,CAAE;GAEjF,AAAG,MAAU,OAET,KAAK,GAAgB,OAAO,GAAO,CAAC,GACpC,KAAK,GAAK,MAAM,oCAAqC,EAAI,EAAE;EAEnE;CACJ;CAMA,KACA;EACI,IAAM,IAAU,KAAK,GAAc,aAAa,IAAI,KAC9C,IAAgB,IAAU,KAAK,IAEjC,IAAmB;EAEvB,IAAG,CAAC,KAAK,IACT;GACI,IAAG,KAAK,OAAgB,MAEpB,MAAU,MAAM,+DAA+D;GAInF,IAAM,IAAa,KAAK,GAAY;GAEpC,IAAG,MAAe,MAClB;IACI,AAAG,KAAK,OAAkB,QAEtB,KAAK,GAAc,mBAAmB,CAAU;IAKpD,IAAM,IAAa,EAAW;IAE9B,AADA,EAAW,uBAAuB,CAAa,GAC/C,IAAmB,EAAW,SAAS;GAC3C;EACJ;EAYA,AARI,KAEA,KAAK,GAAK,MAAM,GAGpB,KAAK,GAAmB,GAAS,CAAa,GAG3C,KAAK,iBAAiB,QAErB,KAAK,aAAa,OAAO;CAEjC;CAIA,GAAmB,GAAkB,GACrC;EACI,KAAI,IAAM,EAAE,aAAU,yBAAsB,KAAK,IAI7C,IAAG,EAFe,KAAK,MAAW,IAGlC;GACI,IAAI,IAAS;GAEb,AAAG,MAEC,IAAS;GAGb,IACA;IACI,EAAS,CAAM;GACnB,SACO,GACP;IACI,KAAK,GAAK,MAAM,4BAA4B,CAAG;GACnD;EACJ;CAER;CAEA,KACA;EACI,AAAG,KAAK,WAEJ,KAAK,GAAc,OAAO;CAElC;CAMA,MAAM,QACN;EAII,AAHA,KAAK,GAAc,cAAc,KAAK,EAAgB,GACtD,KAAK,UAAU,IAEf,KAAK,GAAK,KAAK,+CAA+C;CAClE;CAEA,MAAM,OACN;EAII,AAHA,KAAK,UAAU,IACf,KAAK,GAAc,eAAe,GAElC,KAAK,GAAK,KAAK,+CAA+C;CAClE;CAGA,MAAM,GACN;EACI,IAAG,KAAK,IACR;GACI,KAAK,GAAK,MAAM,wBAAwB;GACxC;EACJ;EAEA,KAAK,KAAU;EAEf,IAAI,IAAe;EAQnB,AAPG,MAEC,IAAe,YAAa,EAAQ,KAGxC,KAAK,GAAK,KAAK,eAAe,CAAY,GAE1C,KAAK,GAAK,KAAK;GACX,MAAM;GACN,UAAU;GACV,SAAS,EAAE,UAAO;EACtB,CAAC;CACL;CAGA,OAAO,GACP;EACI,IAAG,CAAC,KAAK,IACT;GACI,KAAK,GAAK,MAAM,oBAAoB;GACpC;EACJ;EAEA,KAAK,KAAU;EAEf,IAAI,IAAe;EAQnB,AAPG,MAEC,IAAe,YAAa,EAAQ,KAGxC,KAAK,GAAK,KAAK,gBAAgB,CAAY,GAE3C,KAAK,GAAK,KAAK;GACX,MAAM;GACN,UAAU;GACV,SAAS,EAAE,UAAO;EACtB,CAAC;CACL;CAEA,MAAM,UACN;EAeI,AAdA,KAAK,GAAK,KAAK,0BAA0B,GAEtC,KAAK,WAEJ,MAAM,KAAK,KAAK,GAGjB,EAAU,KAET,OAAO,oBAAoB,UAAU,KAAK,EAAmB,GAGjE,KAAK,KAAkB,CAAC,GAExB,KAAK,GAAK,KAAK,oCAAoC;CACvD;AACJ,GCtUa,KAAb,MACA;CACI,qBAAqB,IAAI,IAA0B;CACnD,KAA0C,CAAC;CAC3C;CAEA,YAAY,GACZ;EACI,KAAK,KAAO,GAAQ,UAAU,uBAAuB,KAAK,IAAI,EAAW,uBAAuB;CACpG;CAIA,gBAAgB,GAAe,IAAY,IAC3C;EAMI,AALG,KAAK,GAAU,IAAI,CAAI,KAEtB,KAAK,GAAK,KAAK,mBAAoB,EAAM,sCAAsC,GAGnF,KAAK,GAAU,IAAI,GAAM;GAAE;GAAW,QAAQ;EAAM,CAAC;CACzD;CAKA,gBAAgB,GAAe,GAC/B;EACI,IAAM,IAAU,KAAK,GAAgB,CAAI;EAEzC,IAAG,EAAQ,WAEH,KAAA,IAAM,CAAE,GAAW,MAAW,KAAK,IAEnC,AAAG,MAAc,KAAQ,EAAM,cAE3B,EAAM,SAAS;EAK3B,EAAQ,SAAS;EAEjB,KAAI,IAAM,KAAW,KAAK,IAEtB,AAAG,EAAQ,YAAY,KAEnB,EAAQ,MAAM,EAAa,CAAO,CAAC;CAG/C;CAEA,kBAAkB,GAClB;EACI,KAAK,GAAgB,CAAI,CAAC,CAAC,SAAS;CACxC;CAEA,SAAS,GACT;EACI,OAAO,KAAK,GAAU,IAAI,CAAI,CAAC,EAAE,WAAW;CAChD;CAEA,aAAa,GACb;EACI,OAAO,KAAK,GAAU,IAAI,CAAI;CAClC;CAGA,WAAW,GACX;EACI,KAAK,GAAU,KAAK,CAAO;CAC/B;CAKA,cAAc,GACd;EACI,IAAM,IAAQ,KAAK,GAAU,QAAQ,CAAO;EAE5C,AAAG,MAAU,MAET,KAAK,GAAU,OAAO,GAAO,CAAC;CAEtC;CAIA,OACA;EACI,OAAO,CAAE,GAAG,KAAK,EAAU;CAC/B;CAGA,OAAO,GACP;EAMI,OALG,EAAQ,YAAY,KAAA,KAKhB,KAAK,SAAS,EAAQ,OAAO;CACxC;CAEA,GAAgB,GAChB;EACI,IAAM,IAAU,KAAK,GAAU,IAAI,CAAI;EAEvC,IAAG,MAAY,KAAA,GAEX,MAAU,MAAM,4CAA6C,EAAM,GAAG;EAG1E,OAAO;CACX;AACJ,GC5Ha,KAAb,MACA;CACI,qBAAoB,IAAI,IAAoB;CAE5C,SAAS,GACT;EACI,IAAG,KAAK,GAAS,IAAI,EAAO,IAAI,GAE5B,MAAU,MAAM,mDAAoD,EAAO,KAAM,GAAG;EAGxF,IAAG,EAAO,SAAS,YAAY,EAAO,aAAa,KAAA,KAAa,EAAO,aAAa,KAAA,KAE7E,EAAO,WAAW,EAAO,UAExB,MAAU,MACN,WAAY,EAAO,KAAM,eAAgB,EAAO,SAAU,uBAC1C,EAAO,SAAU,GACrC;EAIR,KAAK,GAAS,IAAI,EAAO,MAAM,CAAM;CACzC;CAEA,IAAI,GACJ;EACI,OAAO,KAAK,GAAS,IAAI,CAAI;CACjC;CAEA,OACA;EACI,OAAO,CAAE,GAAG,KAAK,GAAS,OAAO,CAAE;CACvC;AACJ;;;AC9BA,SAAgB,GACZ,GACA,GACA,GACA,GAEJ;CACI,IAAM,IAAO,EAAQ,QAAQ,CAAK;CAElC,IAAG,MAAS,KAAA,GAER;CAKJ,IAAM,IAAkE;EACpE,MAAM,UAAW,EAAQ,OAAO;EAChC;EACA,SAAS,EAAK;CAClB;CAEA,EAAQ,IAAI,KAAK,CAAkB;AACvC;AAKA,SAAgB,GACZ,GACA,GACA,GACA,GAEJ;CACI,IAAM,IAAkE;EACpE,MAAM,UAAW,EAAO;EACxB;EACA,SAAS;CACb;CAEA,EAAQ,IAAI,KAAK,CAAkB;AACvC;;;AC3CA,IAAa,KAAb,MACA;CACI;CACA;CAEA,YAAY,GAAgC,GAC5C;EAEI,AADA,KAAK,aAAa,GAClB,KAAK,YAAY;CACrB;CAEA,SAAS,GACT;EACI,IAAG,KAAK,eAAe,UAEnB,OAAO,EAAM,QAAQ,KAAK;EAG9B,IAAG,KAAK,eAAe,QAEnB,OAAO,EAAM,KAAK,KAAK;CAI/B;AACJ,GC3Ba,KAAb,MACA;CACI,aAAsB;CACtB;CAEA,YAAY,GACZ;EACI,KAAK,YAAY;CACrB;CAEA,SAAS,GACT;EACI,OAAO,EAAM,KAAK,KAAK;CAC3B;AACJ,GCZa,KAAb,MACA;CACI;CACA;CAEA,YAAY,GAA8B,GAC1C;EAEI,AADA,KAAK,aAAa,GAClB,KAAK,YAAY;CACrB;CAEA,SAAS,GACT;EACI,IAAG,KAAK,eAAe,UAEnB,OAAO,EAAM,QAAQ,KAAK;EAG9B,IAAG,KAAK,eAAe,YAYnB,OAVG,KAAK,cAAc,MAEX,EAAM,SAAS,IAGvB,KAAK,cAAc,MAEX,EAAM,SAAS,IAG1B;EAGJ,IAAG,KAAK,eAAe,YAYnB,OAVG,KAAK,cAAc,MAEX,EAAM,SAAS,IAGvB,KAAK,cAAc,MAEX,EAAM,SAAS,IAG1B;EAGJ,IAAG,KAAK,eAAe,SAiBnB,OAfG,KAAK,cAAc,WAEX,EAAM,MAAM,SAGpB,KAAK,cAAc,WAEX,EAAM,MAAM,SAGpB,KAAK,cAAc,WAEX,EAAM,MAAM,SAGvB;CAIR;AACJ;;;ACrEA,SAAS,GAAkB,GAC3B;CACI,OAAO,MAAe,YAAY,MAAe,cAAc,MAAe,WACvE,MAAe;AAC1B;AAEA,SAAS,GAAoB,GAC7B;CACI,OAAO,MAAe,YAAY,MAAe;AACrD;AAMA,SAAgB,GAAU,GAC1B;CACI,IAAG,EAAM,OAAO,SAAS,cAAc,EAAM,eAAe,OAExD,OAAO,IAAI,GAAe,EAAM,SAAS;CAG7C,IAAG,EAAM,OAAO,SAAS,WAAW,GAAkB,EAAM,UAAU,GAElE,OAAO,IAAI,GAAY,EAAM,YAAY,EAAM,SAAS;CAG5D,IAAG,EAAM,OAAO,SAAS,aAAa,GAAoB,EAAM,UAAU,GAEtE,OAAO,IAAI,GAAc,EAAM,YAAY,EAAM,SAAS;CAG9D,MAAU,MAAM,KAAM,EAAM,OAAO,KAAM,2CAA4C,EAAM,WAAY,GAAG;AAC9G;;;ACtBA,IAAa,KAAb,MACA;CACI,OAAgB;CAChB;CACA;CACA;CACA;CAEA;CACA;CACA;CAGA;CAEA,YACI,GACA,GACA,GACA,IAA2B,CAAC,GAC5B,GAEJ;EAQI,AAPA,KAAK,SAAS,GACd,KAAK,SAAS,GACd,KAAK,UAAU,GACf,KAAK,iBAAiB,EAAQ,kBAAkB,IAEhD,KAAK,KAAU,GACf,KAAK,KAAQ,EAAQ,QAAQ,UAC7B,KAAK,KAAa,EAAQ;CAC9B;CAEA,IAAI,QACJ;EACI,OAAO;GAAE,QAAQ,KAAK;GAAQ,YAAY,KAAK,GAAQ;GAAY,WAAW,KAAK,GAAQ;EAAU;CACzG;CAEA,QAAQ,GACR;EACI,IAAM,IAAU,KAAK,GAAW,KAAK,GAAQ,SAAS,CAAK,CAAC,GACtD,IAAW,KAAK;EAEtB,SAAK,KAAW,GAEb,MAAa,KAAA,KAKZ,KAAK,GAAa,GAAU,CAAO,GAUvC,OALG,KAAK,OAAO,SAAS,WAEb,EAAE,OAAO,KAAK,GAAc,KAAK,GAAQ,SAAS,CAAK,CAAC,EAAE,IAG9D,EAAE,OAAO,EAAQ;CAC5B;CAEA,MAAM,GACN;EACI,KAAK,KAAW,KAAK,GAAW,KAAK,GAAQ,SAAS,CAAK,CAAC;CAChE;CAEA,eACA;EACI,IAAM,IAAoC,EAAE,MAAM,KAAK,GAAM;EAY7D,OAVG,KAAK,OAAe,KAAA,MAEnB,EAAQ,YAAY,KAAK,KAG1B,KAAK,mBAEJ,EAAQ,iBAAiB,KAGtB;GACH,MAAM;GACN,QAAQ,KAAK,OAAO;GACpB,OAAO,KAAK;GACZ,SAAS,KAAK;GACd;EACJ;CACJ;CAEA,GAAa,GAAoB,GACjC;EAWI,OAVG,KAAK,OAAU,WAEP,CAAC,KAAY,IAGrB,KAAK,OAAU,YAEP,KAAY,CAAC,IAGjB,MAAa;CACxB;CAEA,GAAW,GACX;EAgBI,OAfG,OAAO,KAAQ,YAEP,IAGR,OAAO,KAAQ,WAEX,KAAK,OAAe,KAAA,IAKhB,MAAQ,IAHJ,KAAO,KAAK,KAMpB;CACX;CAEA,GAAc,GACd;EAWI,OAVG,OAAO,KAAQ,WAEP,IAGX,EAAG,MAAQ;CAMf;AACJ,GC5Ia,KAAb,MACA;CACI,OAAgB;CAChB;CACA;CACA;CACA;CAEA;CACA;CACA;CACA;CACA;CAIA;CAIA;CAGA;CAEA,YACI,GACA,GACA,GACA,IAA0B,CAAC,GAC3B,GAEJ;EAYI,AAXA,KAAK,SAAS,GACd,KAAK,SAAS,GACd,KAAK,UAAU,GACf,KAAK,iBAAiB,EAAQ,kBAAkB,IAEhD,KAAK,KAAU,GACf,KAAK,KAAW,EAAQ,WAAW,IACnC,KAAK,KAAY,EAAQ,YAAY,IACrC,KAAK,KAAa,EAAQ,WAC1B,KAAK,KAAU,EAAQ,UAAU,IACjC,KAAK,KAAQ,EAAQ,cACrB,KAAK,KAAgB,EAAQ;CACjC;CAEA,IAAI,QACJ;EACI,OAAO;GAAE,QAAQ,KAAK;GAAQ,YAAY,KAAK,GAAQ;GAAY,WAAW,KAAK,GAAQ;EAAU;CACzG;CAEA,QAAQ,GACR;EACI,IAAM,IAAY,KAAK,GAAW,KAAK,GAAQ,SAAS,CAAK,CAAC,GACxD,IAAW,KAAK;EAEtB,SAAK,KAAqB,GAEvB,MAAa,KAAA,KAKb,OAAY,CAAC,IAYhB,OAPA,KAAK,KAAQ,CAAC,KAAK,IAEhB,KAAK,KAEG,EAAE,OAAO,KAAK,GAAS,IAG3B,EAAE,OAAO,KAAK,GAAU;CACnC;CAEA,MAAM,GACN;EACI,KAAK,KAAqB,KAAK,GAAW,KAAK,GAAQ,SAAS,CAAK,CAAC;CAC1E;CAEA,eACA;EACI,IAAM,IAAoC;GACtC,SAAS,KAAK;GACd,UAAU,KAAK;GACf,QAAQ,KAAK;EACjB;EAiBA,OAfG,KAAK,OAAe,KAAA,MAEnB,EAAQ,YAAY,KAAK,KAG1B,KAAK,OAAkB,KAAA,MAEtB,EAAQ,eAAe,KAAK,KAG7B,KAAK,mBAEJ,EAAQ,iBAAiB,KAGtB;GACH,MAAM;GACN,QAAQ,KAAK,OAAO;GACpB,OAAO,KAAK;GACZ,SAAS,KAAK;GACd;EACJ;CACJ;CAGA,GAAW,GACX;EACI,IAAM,IAAU,KAAK,GAAW,CAAG;EAOnC,OALG,KAAK,KAEG,CAAC,IAGL;CACX;CAEA,GAAW,GACX;EAgBI,OAfG,OAAO,KAAQ,YAEP,IAGR,OAAO,KAAQ,WAEX,KAAK,OAAe,KAAA,IAKhB,MAAQ,IAHJ,KAAO,KAAK,KAMpB;CACX;AACJ,GCjJa,KAAb,MACA;CACI,OAAgB;CAChB;CACA;CACA;CACA;CAEA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA,YACI,GACA,GACA,GACA,IAAyB,CAAC,GAC1B,GAEJ;EAYI,AAXA,KAAK,SAAS,GACd,KAAK,SAAS,GACd,KAAK,UAAU,GACf,KAAK,iBAAiB,EAAQ,kBAAkB,IAEhD,KAAK,KAAU,GACf,KAAK,KAAS,EAAQ,SAAS,GAC/B,KAAK,KAAU,EAAQ,UAAU,GACjC,KAAK,KAAU,EAAQ,UAAU,IACjC,KAAK,KAAY,EAAQ,YAAY,GACrC,KAAK,KAAO,EAAQ,KACpB,KAAK,KAAO,EAAQ;CACxB;CAEA,IAAI,QACJ;EACI,OAAO;GAAE,QAAQ,KAAK;GAAQ,YAAY,KAAK,GAAQ;GAAY,WAAW,KAAK,GAAQ;EAAU;CACzG;CAEA,QAAQ,GACR;EACI,OAAO,EAAE,OAAO,KAAK,GAAS,CAAK,EAAE;CACzC;CAIA,MAAM,GACN,CAEA;CAEA,eACA;EACI,IAAM,IAAoC;GACtC,OAAO,KAAK;GACZ,QAAQ,KAAK;GACb,QAAQ,KAAK;GACb,UAAU,KAAK;EACnB;EAiBA,OAfG,KAAK,OAAS,KAAA,MAEb,EAAQ,MAAM,KAAK,KAGpB,KAAK,OAAS,KAAA,MAEb,EAAQ,MAAM,KAAK,KAGpB,KAAK,mBAEJ,EAAQ,iBAAiB,KAGtB;GACH,MAAM;GACN,QAAQ,KAAK,OAAO;GACpB,OAAO,KAAK;GACZ,SAAS,KAAK;GACd;EACJ;CACJ;CAEA,GAAS,GACT;EACI,IAAI,IAAQ,KAAK,GAAU,KAAK,GAAQ,SAAS,CAAK,CAAC;EAyBvD,OAvBA,KAAgB,KAAK,IACrB,KAAgB,KAAK,IAElB,KAAK,OAAS,KAAA,KAAa,IAAQ,KAAK,OAEvC,IAAQ,KAAK,KAGd,KAAK,OAAS,KAAA,KAAa,IAAQ,KAAK,OAEvC,IAAQ,KAAK,KAGd,KAAK,OAEJ,IAAQ,CAAC,IAGV,KAAK,IAAI,CAAK,IAAI,KAAK,OAEtB,IAAQ,IAGL;CACX;CAEA,GAAU,GACV;EAWI,OAVG,OAAO,KAAQ,WAEP,IAGX,EAAG,MAAQ;CAMf;AACJ,GCvIa,KAAb,MACA;CACI,OAAgB;CAChB;CACA;CACA;CACA;CAEA;CACA;CACA;CACA;CAEA,YACI,GACA,GACA,GACA,IAAyB,CAAC,GAC1B,GAEJ;EASI,AARA,KAAK,SAAS,GACd,KAAK,SAAS,GACd,KAAK,UAAU,GACf,KAAK,iBAAiB,EAAQ,kBAAkB,IAEhD,KAAK,KAAU,GACf,KAAK,KAAS,EAAQ,SAAS,GAC/B,KAAK,KAAU,EAAQ,UAAU,IACjC,KAAK,KAAY,EAAQ,YAAY;CACzC;CAEA,IAAI,QACJ;EACI,OAAO;GAAE,QAAQ,KAAK;GAAQ,YAAY,KAAK,GAAQ;GAAY,WAAW,KAAK,GAAQ;EAAU;CACzG;CAEA,QAAQ,GACR;EACI,IAAI,IAAQ,KAAK,GAAU,KAAK,GAAQ,SAAS,CAAK,CAAC;EAEvD,SAAgB,KAAK,IAElB,KAAK,OAEJ,IAAQ,CAAC,IAGV,KAAK,IAAI,CAAK,IAAI,KAAK,OAEtB,IAAQ,IAKT,MAAU,GAKb,OAAO,EAAE,SAAM;CACnB;CAIA,MAAM,GACN,CAEA;CAEA,eACA;EACI,IAAM,IAAoC;GACtC,OAAO,KAAK;GAAQ,QAAQ,KAAK;GAAS,UAAU,KAAK;EAC7D;EAOA,OALG,KAAK,mBAEJ,EAAQ,iBAAiB,KAGtB;GACH,MAAM;GACN,QAAQ,KAAK,OAAO;GACpB,OAAO,KAAK;GACZ,SAAS,KAAK;GACd;EACJ;CACJ;CAEA,GAAU,GACV;EAWI,OAVG,OAAO,KAAQ,WAEP,IAGX,EAAG,MAAQ;CAMf;AACJ;;;AC3GA,SAAgB,GACZ,GACA,GACA,GAEJ;CACI,IAAM,IAAS,EAAW,MAAM,QAC1B,IAAU,EAAW,WAAW,CAAC,GACjC,IAAW,KAAU,GAAU,EAAW,KAAK;CAiBrD,OAfG,EAAW,SAAS,YAEZ,IAAI,GAAe,GAAQ,GAAQ,GAAU,GAA2B,EAAW,OAAO,IAGlG,EAAW,SAAS,WAEZ,IAAI,GAAc,GAAQ,GAAQ,GAAU,GAA0B,EAAW,OAAO,IAGhG,EAAW,SAAS,UAEZ,IAAI,GAAa,GAAQ,GAAQ,GAAU,GAAyB,EAAW,OAAO,IAG1F,IAAI,GAAa,GAAQ,GAAQ,GAAU,GAAyB,EAAW,OAAO;AACjG;;;ACjCA,SAAgB,EAAS,GACzB;CAMI,OALG,MAAe,YAEP,YAGJ;AACX;;;ACoCA,IAAa,IAAyB,cAIhC,KAAuB,IAKvB,IAAa,GAIb,KAAqB;AAG3B,SAAS,GAAa,GACtB;CACI,OAAO,KAAK,IAAI,CAAK,IAAI;AAC7B;AAEA,SAAS,GAAqB,GAAY,GAC1C;CACI,OAAO,KAAK,IAAI,CAAC,IAAI,MAAsB,KAAK,IAAI,CAAC,IAAI;AAC7D;AAGA,IAAM,qBAA2B,IAAI,IAAI;CACrC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACJ,CAAC;AAED,SAAS,GAAkB,GAC3B;CACI,IAAG,EAAE,aAAkB,UAEnB,OAAO;CAGX,IAAG,aAAkB,qBAEjB,OAAO;CAGX,IAAG,aAAkB,kBAEjB,OAAO,CAAC,GAAyB,IAAI,EAAO,IAAI;CAKpD,IAAM,IAAc,EAAO,QAAQ,mBAAmB;CAOtD,OALG,MAAgB,QAKZ,EAAY,aAAa,iBAAiB,MAAM;AAC3D;AAoBA,IAAa,KAAb,MACA;CACI,UAAmB,IAAI,GAAe;CACtC;CAEA,KAA2C,CAAC;CAC5C,KAAmD,CAAC;CACpD,KAA0B;EAAE,GAAG;EAAG,GAAG;CAAE;CACvC,KAAuB;EAAE,QAAQ;EAAG,QAAQ;EAAG,QAAQ;CAAE;CACzD,KAA0B;EAAE,GAAG;EAAG,GAAG;CAAE;CACvC,qBAA0B,IAAI,IAAiC;CAE/D,qBAAqB,IAAI,IAAkB;CAI3C,qBAAqC,IAAI,IAAoB;CAE7D;CAEA;CAGA;CAGA;CAEA;CACA;CACA;CACA;CACA;CACA;CAEA,YAAY,GACZ;EAyFI,AAxFA,KAAK,WAAW,IAAI,GAAsB,CAAM,GAEhD,KAAK,MAAkB,MACvB;GAEO,GAAkB,EAAM,aAAa,CAAC,CAAC,EAAE,MAKzC,KAAK,GAAQ,YAAY,OAAO,EAAM,IAAI,KAEzC,EAAM,eAAe,GAGzB,KAAK,GAAM,EAAM,QAAQ,IACzB,KAAK,GAAuB,UAAU,GACtC,KAAK,GAAe;IAChB,QAAQ;KAAE,MAAM;KAAY,OAAO;IAAE;IAAG,YAAY;IAAO,WAAW,EAAM;IAAM,OAAO;GAC7F,CAAC;EACL,GAEA,KAAK,MAAgB,MACrB;GACI,KAAK,GAAM,EAAM,QAAQ;EAC7B,GAEA,KAAK,MAAsB,MAC3B;GACI,IAAG,EAAM,gBAAgB,SAAW;GAEpC,IAAM,IAAY,OAAO,EAAM,MAAM;GASrC,AAPG,KAAK,GAAQ,SAAS,UAAU,CAAS,KAExC,EAAM,eAAe,GAGzB,KAAK,GAAc,KAAa,IAChC,KAAK,GAAuB,OAAO,GACnC,KAAK,GAAe;IAAE,QAAQ;KAAE,MAAM;KAAS,OAAO;IAAE;IAAG,YAAY;IAAU;IAAW,OAAO;GAAK,CAAC;EAC7G,GAEA,KAAK,MAAoB,MACzB;GACO,EAAM,gBAAgB,YAEzB,KAAK,GAAc,OAAO,EAAM,MAAM,KAAK;EAC/C,GAEA,KAAK,MAAsB,MAC3B;GACO,EAAM,gBAAgB,YAEzB,KAAK,GAAe,IAAI,EAAM,SAC9B,KAAK,GAAe,IAAI,EAAM,SAE9B,KAAK,GAAe,KAAK,EAAM,WAC/B,KAAK,GAAe,KAAK,EAAM,WAI5B,GAAqB,EAAM,WAAW,EAAM,SAAS,MAEpD,KAAK,KAAwB;EAErC,GAEA,KAAK,MAAgB,MACrB;GAYI,AAXG,KAAK,GAAQ,SAAS,OAAO,KAE5B,EAAM,eAAe,GAKzB,KAAK,GAAY,UAAU,EAAM,QACjC,KAAK,GAAY,UAAU,EAAM,QACjC,KAAK,GAAY,UAAU,EAAM,SAE9B,EAAM,WAAW,KAAc,EAAM,WAAW,KAAc,EAAM,WAAW,OAE9E,KAAK,GAAuB,OAAO,GACnC,KAAK,GAAqB,CAAK;EAEvC,GAEG,EAAU,MAET,OAAO,iBAAiB,WAAW,KAAK,EAAc,GACtD,OAAO,iBAAiB,SAAS,KAAK,EAAY,GAClD,OAAO,iBAAiB,eAAe,KAAK,EAAkB,GAC9D,OAAO,iBAAiB,aAAa,KAAK,EAAgB,GAC1D,OAAO,iBAAiB,eAAe,KAAK,EAAkB,GAC9D,OAAO,iBAAiB,SAAS,KAAK,IAAc,EAAE,SAAS,GAAM,CAAC;CAE9E;CAIA,IAAI,uBACJ;EACI,OAAO,KAAK;CAChB;CAIA,IAAI,eACJ;EACI,OAAO,KAAK;CAChB;CAQA,YAAY,GAAmB,GAC/B;EACI,OAAO,KAAK,GAAY,GAAM,CAAK;CACvC;CAKA,KAAK,GACL;EACI,IAAM,IAAS,KAAK,QAAQ,IAAI,EAAW,MAAM;EAEjD,IAAG,MAAW,KAAA,GAEV,MAAU,MAAM,2CAA4C,EAAW,OAAQ,GAAG;EAGtF,IAAM,IAAU,GAAe,GAAY,CAAM;EAIjD,OAFA,KAAK,SAAS,WAAW,CAAO,GAEzB;CACX;CAIA,gBAAgB,GAChB;EAGI,OAFA,KAAK,GAAU,IAAI,CAAO,SAG1B;GACI,KAAK,GAAU,OAAO,CAAO;EACjC;CACJ;CAKA,qBACI,GACA,GACA,GAEJ;EACI,IAAM,IAA+B,CAAC;EAEtC,KAAI,IAAM,KAAW,KAAK,SAAS,KAAK,GACxC;GACI,IAAM,IAAgB,EAAQ,OAAO,SAAS,GACxC,IAAgB,MAAuB,KAAA,KACtC,EAAQ,OAAO,SAAS,KACxB,EAAS,EAAQ,OAAO,IAAI,MAAM,GACnC,IAAiB,MAAY,KAAA,KAAa,EAAQ,YAAY;GAEpE,AAAG,KAAiB,KAAiB,KAEjC,EAAQ,KAAK,CAAO;EAE5B;EAEA,OAAO;CACX;CAMA,mBAAmB,GACnB;EACI,KAAK,GAAc;EAEnB,IAAM,oBAAO,IAAI,IAA4B;EAE7C,KAAI,IAAM,KAAW,KAAK,SAAS,KAAK,GAEpC,IAAG,KAAK,SAAS,OAAO,CAAO,GAC/B;GACI,IAAM,IAAQ,KAAK,GAAY,EAAQ,OAAO,MAAM,EAAQ,OAAO,KAAK;GAExE,AAAG,EAAQ,SAAS,UAEhB,KAAK,GAAiB,GAAM,GAAS,CAAK,IAI1C,GAAc,GAAS,GAAO,GAAS,CAAsB;EAErE;EAcJ,AAXA,KAAK,GAAqB,GAAM,CAAO,GACvC,KAAK,GAAoB,CAAO,GAChC,KAAK,GAAoB,CAAO,GAIhC,KAAK,GAAe,IAAI,GACxB,KAAK,GAAe,IAAI,GAExB,KAAK,GAAY,SAAS,GAC1B,KAAK,GAAY,SAAS,GAC1B,KAAK,GAAY,SAAS;CAC9B;CAKA,GAAQ,GAAyB,GAAqB,GACtD;EACI,KAAI,IAAM,KAAW,KAAK,SAAS,KAAK,GAEpC,IAAG,EAAQ,kBAAkB,KAAK,SAAS,OAAO,CAAO,GACzD;GACI,IAAM,EAAE,WAAQ,YAAY,GAAW,WAAW,MAAa,EAAQ;GAEvE,IAAG,EAAO,SAAS,KAAc,MAAc,MACvC,MAAc,KAAA,KAAa,MAAa,IAE5C,OAAO;EAEf;EAGJ,OAAO;CACX;CAEA,MAAM,UACN;EACI,AAAG,EAAU,MAET,OAAO,oBAAoB,WAAW,KAAK,EAAc,GACzD,OAAO,oBAAoB,SAAS,KAAK,EAAY,GACrD,OAAO,oBAAoB,eAAe,KAAK,EAAkB,GACjE,OAAO,oBAAoB,aAAa,KAAK,EAAgB,GAC7D,OAAO,oBAAoB,eAAe,KAAK,EAAkB,GACjE,OAAO,oBAAoB,SAAS,KAAK,EAAY;CAE7D;CAIA,GAAiB,GAAoC,GAA4B,GACjF;EACI,IAAM,IAAO,EAAQ,QAAQ,CAAK;EAElC,IAAG,MAAS,KAAA,GAER;EAIJ,IAAM,IAAe,OAAO,EAAK,KAAK,GAChC,IAAU,EAAK,IAAI,EAAQ,OAAO,IAAI;EAE5C,AAAG,MAAY,KAAA,IAEX,EAAK,IAAI,EAAQ,OAAO,MAAM;GAAE,QAAQ,EAAQ;GAAQ,OAAO;EAAa,CAAC,IAI7E,EAAQ,SAAwB;CAExC;CAKA,GAAqB,GAAoC,GACzD;EACI,KAAI,IAAM,CAAE,GAAM,MAAY,KAAK,IAE/B,AAAI,EAAK,IAAI,CAAI,KAEb,EAAK,IAAI,GAAM;GAAE;GAAQ,OAAO;EAAE,CAAC;EAI3C,KAAI,IAAM,CAAE,GAAM,MAAS,GAC3B;GACI,IAAM,IAAQ,KAAK,GAAe,EAAI,QAAQ,EAAI,KAAK,GACjD,IAAe,MAAU,KAAK,GAAe,EAAI,QAAQ,CAAU,GACnE,IAAkB,KAAK,GAA0B,IAAI,CAAI;GAW/D,AATG,IAEC,KAAK,GAA0B,IAAI,GAAM,EAAI,MAAM,IAInD,KAAK,GAA0B,OAAO,CAAI,IAG3C,KAAgB,MAEf,GAAmB,EAAI,QAAQ,GAAO,GAAS,CAAsB;EAE7E;CACJ;CAIA,GAAe,GAAiB,GAChC;EAgBI,OAfG,EAAO,SAAS,WAKhB,EAAO,aAAa,KAAA,KAAa,IAAQ,EAAO,WAExC,EAAO,WAGf,EAAO,aAAa,KAAA,KAAa,IAAQ,EAAO,WAExC,EAAO,WAGX,IAbI;CAcf;CAEA,GAAY,GAAmB,GAC/B;EACI,IAAG,MAAS,YAER,OAAO,EAAE,MAAM,KAAK,GAAM;EAG9B,IAAG,MAAS,SAER,OAAO;GACH,SAAS,KAAK;GACd,UAAU,KAAK;GACf,OAAO,KAAK;GACZ,UAAU,KAAK;EACnB;EAGJ,IAAM,IAAU,KAAK,GAAe,IAAI,CAAK;EAO7C,OALG,MAAY,KAAA,IAEJ;GAAE,SAAS,CAAC;GAAG,MAAM,CAAC;EAAE,IAG5B;CACX;CAIA,KACA;EACI,IAAG,OAAO,YAAc,OAAe,OAAO,UAAU,eAAgB,YAEpE;EAGJ,IAAM,IAAW,UAAU,YAAY;EAEvC,KAAI,IAAI,IAAQ,GAAG,IAAQ,EAAS,QAAQ,KAC5C;GACI,IAAM,IAAU,EAAS;GAEzB,AAAG,MAAY,QAEX,KAAK,GAAgB,GAAO,CAAO;EAE3C;CACJ;CAEA,GAAgB,GAAgB,GAChC;EACI,IAAM,IAAmC,CAAC,GACtC,IAAa;EAEjB,KAAI,IAAI,IAAc,GAAG,IAAc,EAAQ,QAAQ,QAAQ,KAC/D;GACI,IAAM,IAAQ,EAAQ,QAAQ,EAAY,CAAC,OACrC,IAAY,OAAO,CAAW;GAIpC,AAFA,EAAQ,KAAa,GAElB,GAAa,CAAK,MAEjB,IAAa,IACb,KAAK,GAAe;IAAE,QAAQ;KAAE,MAAM;KAAW;IAAM;IAAG,YAAY;IAAU;IAAW;GAAM,CAAC;EAE1G;EAEA,IAAM,IAAgC,CAAC;EAEvC,KAAI,IAAI,IAAY,GAAG,IAAY,EAAQ,KAAK,QAAQ,KACxD;GACI,IAAM,IAAQ,EAAQ,KAAK,IACrB,IAAY,OAAO,CAAS;GAIlC,AAFA,EAAK,KAAa,GAEf,GAAa,CAAK,MAEjB,IAAa,IACb,KAAK,GAAe;IAAE,QAAQ;KAAE,MAAM;KAAW;IAAM;IAAG,YAAY;IAAQ;IAAW;GAAM,CAAC;EAExG;EAIA,AAFA,KAAK,GAAe,IAAI,GAAO;GAAE;GAAS;EAAK,CAAC,GAE7C,KAEC,KAAK,GAAuB,SAAS;CAE7C;CAIA,GAAoB,GACpB;EACI,IAAG,KAAK,OAA0B,KAAK,IAEnC;EAGJ,IAAM,IAAqB,KAAK;EAChC,KAAK,KAAsB,KAAK,IAE7B,KAAK,OAA0B,KAAA,KAKlC,EAAQ,IAAI,KAAK;GACb,MAAM;GACN,UAAU;GACV,SAAS;IAAE,YAAY,KAAK;IAAuB;GAAmB;EAC1E,CAAC;CACL;CAIA,GAAuB,GACvB;EAEI,AADA,KAAK,KAAwB,GAC7B,KAAK,KAAgB,EAAS,CAAU;CAC5C;CAIA,GAAoB,GACpB;EACI,IAAG,KAAK,OAAkB,KAAK,IAE3B;EAGJ,IAAM,IAAiB,KAAK;EAC5B,KAAK,KAAkB,KAAK,IAEzB,KAAK,OAAkB,KAAA,KAK1B,EAAQ,IAAI,KAAK;GACb,MAAM;GACN,UAAU;GACV,SAAS;IAAE,QAAQ,KAAK;IAAe;GAAe;EAC1D,CAAC;CACL;CAIA,GAAqB,GACrB;EACI,IAAM,IAAO;GACT;IAAE,WAAW;IAAU,OAAO,EAAM;GAAO;GAC3C;IAAE,WAAW;IAAU,OAAO,EAAM;GAAO;GAC3C;IAAE,WAAW;IAAU,OAAO,EAAM;GAAO;EAC/C;EAEA,KAAI,IAAM,KAAQ,GAEd,AAAG,EAAK,UAAU,KAEd,KAAK,GAAe;GAChB,QAAQ;IAAE,MAAM;IAAS,OAAO;GAAE;GAClC,YAAY;GACZ,WAAW,EAAK;GAChB,OAAO,EAAK;EAChB,CAAC;CAGb;CAEA,GAAe,GACf;EACI,KAAI,IAAM,KAAW,KAAK,IAEtB,EAAQ,KAAK,CAAS;CAE9B;AACJ,GCvqBM,KAA6C;CAC/C,OAAO,EAA6B;CACpC,UAAU,EAA6B;CACvC,YAAY,EAA6B;CACzC,cAAc,EAA6B;CAC3C,MAAM,EAA6B;AACvC;AAUA,SAAgB,GACZ,GACA,GACA,GAEJ;CAEI,IAAG,CAAC,EAAM,cAEN;CAIJ,AAAG,EAAO,cAEN,EAAO,KAAK,4EAA4E;CAI5F,IAAM,IAAW,IAAI,EAAyB,gBAAgB,IAAM,GAAO,EAAM,OAAO;CAyGxF,IAtGG,EAAO,UAEN,EAAS,eAAe,IAErB,EAAO,MAAM,WAAW,KAAA,MAEvB,EAAS,cAAc,EAAO,MAAM,SAErC,EAAO,MAAM,cAAc,KAAA,MAE1B,EAAS,iBAAiB,EAAO,MAAM,YAExC,EAAO,MAAM,UAAU,KAAA,MAEtB,EAAS,aAAa,EAAO,MAAM,QAEpC,EAAO,MAAM,WAAW,KAAA,MAEvB,EAAS,cAAc,EAAO,MAAM,UAKzC,EAAO,YAEN,EAAS,yBAAyB,IAClC,EAAS,gBAAgB,qBAAqB,IAE3C,EAAO,QAAQ,aAEd,EAAS,gBAAgB,kBACnB,GAAkB,EAAO,QAAQ,aAAa,EAA6B,wBAKtF,EAAO,UAEN,EAAS,eAAe,IAErB,EAAO,MAAM,cAAc,KAAA,MAE1B,EAAS,MAAM,YAAY,EAAO,MAAM,YAEzC,EAAO,MAAM,aAAa,KAAA,MAEzB,EAAS,MAAM,WAAW,EAAO,MAAM,YAK5C,EAAO,aAEN,EAAS,yBAAyB,IAClC,EAAS,gBAAgB,kBAAkB,IAExC,EAAO,SAAS,WAAW,KAAA,MAE1B,EAAS,gBAAgB,iBAAiB,EAAO,SAAS,SAE3D,EAAO,SAAS,YAAY,KAAA,MAE3B,EAAS,gBAAgB,kBAAkB,EAAO,SAAS,UAE5D,EAAO,SAAS,UAEf,EAAS,gBAAgB,gBAAgB,IAAI,EACzC,EAAO,SAAS,MAAM,GACtB,EAAO,SAAS,MAAM,GACtB,EAAO,SAAS,MAAM,GACtB,CACJ,KAKL,EAAO,YAEN,EAAS,iBAAiB,IAEvB,EAAO,QAAQ,SAAS,KAAA,MAEvB,EAAS,QAAQ,aAAa,EAAO,QAAQ,OAE9C,EAAO,QAAQ,UAAU,KAAA,MAExB,EAAS,QAAQ,cAAc,EAAO,QAAQ,SAKnD,EAAO,wBAEN,EAAS,6BAA6B,IAEnC,EAAO,oBAAoB,WAAW,KAAA,MAErC,EAAS,oBAAoB,mBAAmB,EAAO,oBAAoB,UAKhF,EAAO,MACV;EACI,IAAM,IAAO,IAAI,GAAuB,aAAa,GAAO;GAAE,WAAW;GAAK,WAAW;EAAI,CAAC;EAU9F,AARG,EAAO,KAAK,WAAW,KAAA,MAEtB,EAAK,SAAS,EAAO,KAAK,SAE3B,EAAO,KAAK,YAAY,KAAA,MAEvB,EAAK,UAAU,EAAO,KAAK,UAE5B,EAAO,KAAK,kBAAkB,KAAA,MAE7B,EAAK,gBAAgB,EAAO,KAAK;CAEzC;AACJ;;;AChKA,eAAsB,GAClB,GACA,GACA,GAEJ;CACI,IAAG,EAAO,aAAa,cACvB;EACI,IAAM,EAAE,qCAAkC,MAAM,OAAO;EACvD,MAAM,EAA8B,GAAO,GAAQ,CAAM;CAC7D,OAGI,GAA4B,GAAO,GAAQ,CAAM;AAEzD;;;ACRA,SAAgB,EAAuB,GACvC;CACI,IAAM,EAAE,QAAK,WAAQ,EAAK,4BAA4B,EAAI,GACpD,IAAU,EAAI,SAAS,CAAG,GAK1B,IAJc,EAAI,IAAI,CAAG,CAAC,CAAC,MAAM,EAInB,CAAA,CAAY,SAAS,EAAK,gBAAgB,GACxD,IAAkB,EAAW,QAAQ,EAAK,0BAA0B,GACpE,IAAS,IAAI,EAAQ;CAI3B,OAFA,EAAY,wBAAwB,GAAiB,CAAM,GAEpD;EAAE;EAAS;CAAO;AAC7B;;;ACtBA,IAAM,IAAwB,GAIxB,KAAkB;CAAE;CAAO;CAAU;CAAQ;AAAO;AAqB1D,SAAS,GAAU,GAAsB,GAAiB,GAC1D;CACI,KAAI,IAAM,KAAS,IAEf,IAAG,MAAU,GAET,OAAO;CAIf,EAAO,KACH,yBAA0B,EAAK,KAAM,KAAM,KAAK,UAAU,CAAK,EAAG,gCAChC,GAAgB,KAAK,IAAI,EAAG,oBAClE;AAGJ;AAMA,SAAS,GAAiB,GAAsB,GAChD;CACI,IAAM,IAAW,EAAK,UAAU;CAiBhC,OAfG,KAAuC,OAE/B,IAGR,OAAO,KAAa,YAAY,CAAC,OAAO,SAAS,CAAQ,KAExD,EAAO,KACH,6BAA8B,EAAK,KAAM,IAAK,KAAK,UAAU,CAAQ,EAAG,yBAC7C,EAAuB,EACtD,GAEO,KAGJ;AACX;AAOA,SAAS,GAAmB,GAAsB,GAClD;CACI,IAAM,IAAO,GAAiB,GAAM,CAAM,GACpC,IAAW,EAAK,UAAU;CAiB5B,OAfD,MAAa,YAEL;EAAE,MAAM,EAAkB;EAAS;CAAK,IAE3C,MAAa,cAEV;EAAE,MAAM,EAAkB;EAAU;CAAK,KAE5C,MAAa,YAAY,MAAa,KAAA,KAM1C,EAAO,KAAK,8BAA+B,EAAK,KAAM,KAAM,EAAU,2BAA2B,GAJ1F;EAAE,MAAM,EAAkB;EAAQ;CAAK;AAOtD;AAMA,SAAS,GAAe,GACxB;CACI,OAAO,aAAgB,KAAQ,EAAK,iBAAiB,IAAI;AAC7D;AAEA,SAAS,GAAmB,GAC5B;CACI,KAAI,IAAM,KAAS,EAAK,eAAe,EAAK,GAExC,IAAG,EAAM,iBAAiB,IAAI,GAE1B,OAAO;CAIf,OAAO;AACX;AAEA,SAAS,EACL,GACA,GACA,GACA,GAEJ;CACI,IAAI,IAAO,EAAO;CAElB,AAAG,EAAO,SAAS,EAAkB,WAEjC,IAAO;CAGX,IAAM,IAAuC,EAAE,QAAK;CAEpD,IAAG,MAAgB,KAAA,GACnB;EAGI,IAFA,EAAQ,SAAS,EAAY,QAE1B,MAAc,EAAiB,QAClC;GAGI,IAAM,EAAE,MAAG,MAAG,SAAM,EAAY;GAChC,EAAQ,SAAS,KAAK,IAAI,GAAG,GAAG,CAAC,IAAI;EACzC,OAGI,EAAQ,UAAU,EAAY;CAEtC;CAEA,IAAM,IAAY,IAAI,EAAiB,GAAM,GAAW,GAAS,EAAK,SAAS,CAAC;CAQhF,AANA,EAAU,KAAK,cAAc,EAAO,IAAI,GAMrC,EAAO,SAAS,EAAkB,YAEjC,EAAU,KAAK,eAAe,EAAmB,QAAQ;AAEjE;AAMA,SAAS,GAAkB,GAAsB,GAA8B,GAC/E;CACI,IAAG,GAAe,CAAI,GACtB;EACI,EAAgB,GAAM,GAAW,GAAQ,KAAA,CAAS;EAClD;CACJ;CAEA,IAAG,CAAC,GAAmB,CAAI,GAEvB,MAAU,MACN,+BAAgC,EAAK,KAAM,oEAE/C;CAGJ,EAAgB,GAAM,GAAW,GAAQ,EAAuB,CAAI,CAAC;AACzE;AAMA,SAAS,GAAsB,GAAsB,GACrD;CACI,IAAI,IAAS;CAEb,KAAI,IAAM,KAAS,EAAK,eAAe,EAAK,GAExC,IAAG,EAAM,UAAU,kBAAkB,MAAQ,aAAiB,GAC9D;EAEI,AADA,EAAM,YAAY,IAClB,IAAS;EACT;CACJ;CAGJ,IAAG,CAAC,GAAe,CAAM,GAErB,MAAU,MACN,sCAAuC,EAAO,KAAM,sEAExD;CAGJ,EAAgB,GAAQ,EAAiB,MAAM,GAAQ,KAAA,CAAS;AACpE;AAcA,SAAgB,GAAwB,GACxC;CACI,EAAa,wBAAwB,aAAa,GAAM,GAAO,GAAO,MACtE;EACI,IAAG,CAAC,EAAM,OAEN,MAAU,MAAM,0CAA0C;EAG9D,IAAM,IAAQ,GAAU,GAAM,GAAO,EAAQ,MAAM;EAUnD,IARG,MAAU,KAAA,KAQV,MAAU,QAET;EAGJ,IAAM,IAAS,GAAmB,GAAM,EAAQ,MAAM;EAEtD,AAAG,MAAU,SAET,GAAsB,GAAM,CAAM,IAE9B,MAAU,WAEd,GAAkB,GAAM,EAAiB,QAAQ,CAAM,IAIvD,GAAkB,GAAM,EAAiB,KAAK,CAAM;CAE5D,CAAC;AACL;;;ACvRA,SAAS,GAAe,GACxB;CACI,IAAM,IAAuB,CAAC;CAE9B,KAAI,IAAM,KAAQ,EAAM,MAAM,GAAG,GACjC;EACI,IAAM,IAAW,WAAW,EAAK,KAAK,CAAC;EAEvC,AAAI,OAAO,MAAM,CAAQ,KAErB,EAAU,KAAK,CAAQ;CAE/B;CAEA,OAAO;AACX;AAmBA,SAAgB,GAAmB,GACnC;CACI,EAAa,wBAAwB,kBAAkB,GAAM,GAAO,GAAQ,MAC5E;EACI,IAAG,EAAE,aAAgB,IAEjB;EAGJ,IAAM,IAAY,GAAe,OAAO,CAAK,CAAC;EAE9C,IAAG,EAAU,WAAW,GACxB;GACI,EAAQ,OAAO,KACX,8BAA+B,EAAK,KAAM,KAAM,KAAK,UAAU,CAAK,EAAG,kBAC3E;GACA;EACJ;EAEA,IAAM,IAAW,EAAK,eAAe,EAAI,GACnC,IAAa,KAAK,IAAI,EAAU,QAAQ,EAAS,MAAM;EAE7D,KAAI,IAAI,IAAQ,GAAG,IAAQ,GAAY,KAEnC,EAAK,YAAY,EAAU,IAAQ,EAAS,EAAM;EAOtD,IAAG,EAAU,SAAS,EAAS,QAC/B;GACI,IAAM,IAAe,EAAU,EAAU,SAAS;GAClD,EAAK,YAAY,GAAc,IAAI;EACvC;CACJ,CAAC;AACL;;;AClEA,SAAgB,GAAyB,GACzC;CACI,EAAa,wBAAwB,cAAc,GAAM,GAAO,GAAQ,MACxE;EACS,aAAgB,MAKlB,MAAU,eAET,EAAK,gBAAgB,EAAK,4BAEtB,MAAU,WAEd,EAAK,gBAAgB,EAAK,wBAI1B,EAAQ,OAAO,KACX,yBAA0B,EAAK,KAAM,KAAM,KAAK,UAAU,CAAK,EAAG,yDAEtE;CAER,CAAC;AACL;;;ACrBA,SAAgB,GACZ,GACA,GAEJ;CACI,IAAM,IAAmC,CAAC,GACpC,IAAuB,GAAI,EAAQ;CAEzC,KAAI,IAAM,CAAE,GAAK,MAAW,OAAO,QAAQ,CAAQ,GAE/C,IAAG,EAAI,WAAW,CAAoB,GACtC;EACI,IAAM,IAAS,EAAI,MAAM,EAAqB,MAAM;EACpD,EAAO,KAAU;CACrB;CAGJ,OAAO;AACX;;;ACjBA,SAAgB,GAAqB,GACrC;CAII,EAAa,wBAAwB,SAAS,OAAO,GAAM,GAAO,GAAO,MACzE;EACI,IAAM,IAAY;EAGlB,IAAG,CAFW,EAAM,OAIhB,MAAU,MAAM,uCAAuC;EAG3D,IAAM,IAAS,GACX,EAAK,UACL,OACJ,GAEM,IAAU,EAAO,UAAqB,GACtC,IAAQ,EAAO,QAAoB,IACnC,IAAW,EAAO,WAAuB,IACzC,IAAe,EAAO,YAAuB,KAC7C,IAAY,EAAO,YAAwB,IAC3C,IAAW,EAAO,WAAsB,WAExC,IAAe,EAAQ;EAE7B,IAAG,CAAC,GACJ;GACI,EAAQ,OAAO,KAAK,yCAA0C,EAAK,KAAM,WAAW;GACpF;EACJ;EAEA,IAAM,IAAQ,MAAM,EAAa,YAC7B,GAAI,EAAK,KAAM,SACf,GACA,GACA;GACI;GACA;GACA;GACA,gBAAgB;GAChB,oBAAoB;GACpB,sBAAsB;EAC1B,CACJ;EAEA,AAAG,KAEC,EAAM,QAAQ,OAAO,CAAI;CAEjC,CAAC;AACL;;;ACrDA,SAAS,GACL,GACA,GACA,GACA,GACA,GAEJ;CACI,IAAI,IAAc,EAAc;CAEhC,AAAG,MAAe,YAEd,IAAc,EAAc;CAGhC,IAAM,IAAY,WAAY;CAE9B,OAAO,EAAc,eACjB,IAAI,EACA;EACI,SAAS;EACT,WAAW,EAAE,MAAM,EAAW;CAClC,IACC,MACD;EAGI,EAAI,KAAK;GACL,MAAM;GACN,UAAU;GACV,SAAS;IACL,SAAS;IACT,OAAO,EAAI;GACf;EACJ,CAAC;CACL,CACJ,CACJ;AACJ;AAkBA,SAAgB,GAAuB,GACvC;CACI,EAAa,wBAAwB,YAAY,GAAM,GAAO,GAAO,MACrE;EACI,IAAM,IAAc,GACd,IAAQ,EAAM;EAEpB,IAAG,CAAC,GAEA,MAAU,MAAM,yCAAyC;EAG7D,IAAG,EAAE,aAAgB,IAEjB;EAGJ,IAAM,IAAM,EAAQ;EAGpB,AADA,EAAK,YAAY,IACjB,EAAK,kBAAkB;EAEvB,IAAM,IAAW,EAAK,eAClB;EAEJ,AAAG,IAEC,IAAgB,KAIhB,IAAgB,IAAI,EAAc,CAAK,GACvC,EAAK,gBAAgB;EAMzB,IAAM,oBAAU,IAAI,IAAkC;EAEtD,SAAS,EAAM,GACf;GACI,IAAG,MAAW,KAAQ,EAAO,UAAU,SAEnC;GAGJ,IAAM,IAAsB,CAAC;GAE7B,KAAI,IAAM,KAAc,CAAE,SAAS,MAAO,GAC1C;IACI,IAAM,IAAS,GAA2B,GAAe,GAAa,GAAK,GAAQ,CAAU;IAE7F,AAAG,KAEC,EAAQ,KAAK,CAAM;GAE3B;GAEA,IAAM,IAAW,EAAO,oBAAoB,UAC5C;IACI,KAAI,IAAM,KAAU,GAEhB,EAAc,iBAAiB,CAAM;IAGzC,EAAQ,OAAO,CAAM;GACzB,CAAC;GAED,AAAG,KAEC,EAAQ,IAAI,GAAQ,CAAQ;EAEpC;EAEA,KAAI,IAAM,KAAQ,EAAM,QAEpB,EAAM,CAAI;EAGd,IAAM,IAAkB,EAAM,yBAAyB,IAAI,CAAK;EAGhE,EAAK,oBAAoB,UACzB;GACI,EAAM,yBAAyB,OAAO,CAAe;GAErD,KAAI,IAAM,CAAE,GAAQ,MAAc,GAE9B,EAAO,oBAAoB,OAAO,CAAQ;GAG9C,EAAQ,MAAM;EAClB,CAAC;CACL,CAAC;AACL;;;AC5JA,SAAgB,GAAuB,GACvC;CACI,EAAa,wBAAwB,YAAY,GAAsB,MACvE;EAEI,IAAG,EAAE,aAAgB,IAEjB;EAGJ,IAAI,IAAU;EAsBd,AApBG,OAAO,KAAU,YAEhB,IAAU,IAEN,OAAO,KAAU,WAElB,EAAM,YAAY,MAAM,UAAU,MAAU,MAE3C,IAAU,KAEN,EAAM,YAAY,MAAM,YAE5B,IAAU,MAGV,OAAO,KAAU,aAErB,IAAU,MAAU,IAGxB,EAAK,YAAY;CACrB,CAAC;AACL;;;ACpBA,IAAa,KAAsB;CAC/B;CACA;CACA;CACA;CACA;CACA;AACJ;AAMA,SAAgB,GAA4B,GAC5C;CAMI,AALA,GAAwB,CAAY,GACpC,GAAmB,CAAY,GAC/B,GAAyB,CAAY,GACrC,GAAqB,CAAY,GACjC,GAAuB,CAAY,GACnC,GAAuB,CAAY;AACvC;;;ACZA,IAAsB,KAAtB,MACA;CACI;CACA;CACA;CACA,SAAkC;CAClC,cAAwB;CAOxB,cAA0D;CAC1D,kBAA0D;CAC1D,WAA0C;CAM1C,YAAY,GAAsB,GAClC;EAGI,AAFA,KAAK,OAAO,EAAO,MACnB,KAAK,WAAW,GAChB,KAAK,OAAO,EAAQ,OAAO,UAAU,SAAU,EAAO,MAAO;CACjE;CAMA,IAAI,QACJ;EACI,OAAO,KAAK;CAChB;CAEA,IAAI,WACJ;EACI,OAAO,KAAK,WAAW;CAC3B;CAIA,IAAc,UACd;EACI,OAAO,KAAK;CAChB;CAEA,IAAI,mBACJ;EACI,OAAO,KAAK,SAAS;CACzB;CAUA,IAAI,cACJ;EACI,OAAO,KAAK;CAChB;CAMA,iBAAiB,GACjB;EACI,KAAK,cAAc;CACvB;CAOA,kBAA4B,GAAe,GAC3C;EAGI,OAFA,KAAK,cAAc,KAAK,SAAS,kBAAkB,GAAO,CAAU,GAE7D,KAAK;CAChB;CAOA,cAAwB,GAAmB,GAC3C;EACI,KAAK,SAAS,IAAI,KAAK;GACnB,MAAM;GACN,UAAU,KAAK;GACf,SAAS;IACL,WAAW,KAAK;IAChB;IACA;GACJ;EACJ,CAAC;CACL;CAIA,UAAoB,GACpB;EACI,OAAO,IAAI,EAAO,EAAM,GAAG,EAAM,GAAG,EAAM,CAAC;CAC/C;CAQA,uBAAiC,GACjC;EACI,IAAG,CAAC,EAAK,UAEL,OAAO;EAGX,IAAM,IAAgC,EAAE,GAAI,EAAK,SAAqC,GAChF,IAAO,EAAI;EAGjB,OAFA,OAAO,EAAI,MAEJ;GAAE,GAAG;GAAK,GAAG,GAAM;EAAO;CACrC;CAKA,MAAgB,qBAAqB,GACrC;EAGI,IAAM,IAAW,EAAK;EAEnB,SAAa,MAKhB;QAAI,IAAM,CAAE,GAAU,MAAa,KAAK,kBAEpC,IAAG,KAAY,GAEX,IACA;IAEI,MAAM,EAAQ,GAAM,EAAS,IAAW,MAAM,KAAK,GAAgB,CAAC;GACxE,SACO,GACP;IACI,KAAK,KAAK,MACN,8BAA+B,EAAU,cAAe,EAAK,KAAM,KACnE,CACJ;GACJ;;CAGZ;CAIA,KACA;EACI,OAAO;GACH,KAAK,KAAK,SAAS;GACnB,cAAc,KAAK,SAAS;GAC5B,QAAQ,KAAK,SAAS,OAAO,UAAU,kBAAkB;EAC7D;CACJ;CAWA,MAAM,KAAK,GACX;EACI,IAAG,KAAK,QAGJ,OADA,KAAK,KAAK,KAAK,SAAU,KAAK,KAAM,mBAAmB,GAChD,KAAK;EAMhB,AAHA,KAAK,cAAc,GAAS,cAAc,IAE1C,KAAK,KAAK,KAAK,kBAAmB,KAAK,MAAO,GAC9C,KAAK,KAAK,KAAK,SAAU,KAAK,KAAM,MAAM;EAE1C,IACA;GAmBI,OAlBA,KAAK,cAAc,GAAG,wBAAwB,GAE9C,KAAK,SAAS,MAAM,KAAK,WAAW,GAEpC,KAAK,cAAc,KAAK,2BAA2B,GACnD,KAAK,SAAS,IAAI,KAAK;IACnB,MAAM;IACN,UAAU,KAAK;IACf,SAAS;KACL,WAAW,KAAK;KAChB,SAAS;KACT,OAAO;IACX;GACJ,CAAC,GAED,KAAK,KAAK,QAAQ,SAAU,KAAK,KAAM,MAAM,GAC7C,KAAK,KAAK,KAAK,SAAU,KAAK,KAAM,qBAAqB,GAElD,KAAK;EAChB,SACO,GACP;GAWI,MAVA,KAAK,KAAK,MAAM,wBAAyB,KAAK,KAAM,IAAI,CAAK,GAC7D,KAAK,SAAS,IAAI,KAAK;IACnB,MAAM;IACN,UAAU,KAAK;IACf,SAAS;KACL,WAAW,KAAK;KAChB,SAAS;KACT;IACJ;GACJ,CAAC,GACK;EACV;CACJ;CA4BA,MAAM,UACN;EACI,AAiBI,KAAK,YAfL,KAAK,KAAK,KAAK,oBAAqB,KAAK,MAAO,GAEhD,AAGI,KAAK,cADL,KAAK,SAAS,QAAQ,GACN,OAGpB,AAGI,KAAK,qBADL,KAAK,gBAAgB,QAAQ,GACN,OAG3B,KAAK,OAAO,QAAQ,GACN;CAEtB;AACJ,GChTa,KAAb,MACA;CACI;CACA,0BAAkB,IAAI,IAA+B;CAErD,YAAY,GAAe,GAC3B;EAGI,IAFA,KAAK,SAAS,GAEX,GAEC,KAAI,IAAM,CAAE,GAAM,MAAY,OAAO,QAAQ,CAAO,GAEhD,KAAK,OAAO,GAAM,CAAM;CAGpC;CAMA,IAAI,GACJ;EACI,OAAO,KAAK,QAAQ,IAAI,CAAI,CAAC,EAAE;CACnC;CAEA,OAAO,GAAe,GACtB;EACI,IAAM,IAAW,KAAK,QAAQ,IAAI,CAAI;EACtC,IAAG,GAEC,OAAO,EAAS;EAGpB,IAAM,IAAQ,IAAI,GAAsB,gBAAiB,KAAS,KAAK,MAAM;EAW7E,OATG,GAAQ,UAEP,EAAM,eAAe,IAAI,EAAO,EAAO,MAAM,GAAG,EAAO,MAAM,GAAG,EAAO,MAAM,CAAC,IAG/E,GAAQ,cAAc,KAAA,MAAa,EAAM,mBAAmB,EAAO,YACnE,GAAQ,sBAAsB,KAAA,MAAa,EAAM,oBAAoB,EAAO,oBAE/E,KAAK,QAAQ,IAAI,GAAM;GAAE;GAAO,0BAAU,IAAI,IAAI;EAAE,CAAC,GAC9C;CACX;CAEA,gBAAgB,GAAoB,GACpC;EACI,IAAM,IAAQ,KAAK,QAAQ,IAAI,CAAS;EACpC,MAKJ,EAAM,SAAS,IAAI,CAAM,GACzB,KAAK,kBAAkB,CAAK;CAChC;CAEA,kBAAkB,GAAoB,GACtC;EACI,IAAM,IAAQ,KAAK,QAAQ,IAAI,CAAS;EACpC,MAKJ,EAAM,SAAS,OAAO,CAAM,GAC5B,KAAK,kBAAkB,CAAK;CAChC;CAEA,qBAAqB,GACrB;EACI,KAAI,IAAM,KAAS,KAAK,QAAQ,OAAO,GAEnC,AAAG,EAAM,SAAS,OAAO,CAAM,KAE3B,KAAK,kBAAkB,CAAK;CAGxC;CAEA,MAAM,GACN;EACI,IAAM,IAAQ,KAAK,QAAQ,IAAI,CAAS;EACpC,MAKJ,EAAM,SAAS,MAAM,GACrB,EAAM,MAAM,eAAe;CAC/B;CAEA,UACA;EACI,KAAI,IAAM,KAAS,KAAK,QAAQ,OAAO,GAGnC,AADA,EAAM,SAAS,MAAM,GACrB,EAAM,MAAM,QAAQ;EAGxB,KAAK,QAAQ,MAAM;CACvB;CAMA,kBAA0B,GAC1B;EACI,EAAM,MAAM,eAAe;EAE3B,KAAI,IAAM,KAAU,EAAM,UAC1B;GACI,IAAM,IAAS,EAAO,KAAK,eAAe;GAE1C,AAAG,EAAO,SAAS,KAEf,EAAM,MAAM,aAAa,CAAM;EAEvC;CACJ;AACJ,GCjEa,KAAb,cAAyF,GACzF;CAEI;CAGA,eAA4C,CAAC;CAG7C,eAA4C,CAAC;CAG7C,mBAA4C,CAAC;CAG7C,+BAAyB,IAAI,IAAyB;CAGtD,iDAAyC,IAAI,IAAY;CAMzD,YAAY,GAAsB,GAClC;EAEI,AADA,MAAM,GAAQ,CAAO,GACrB,KAAK,UAAU;CACnB;CAMA,IAAI,SACJ;EACI,OAAO,KAAK;CAChB;CAEA,IAAI,kBACJ;EACI,OAAO,KAAK;CAChB;CAMA,IAAc,aACd;EACI,IAAG,KAAK,gBAAgB,MAEpB,MAAU,MAAM,SAAU,KAAK,KAAM,gEAAgE;EAGzG,OAAO,KAAK;CAChB;CAKA,GAAiB,GAAe,GAChC;EACI,IAAM,IAAS,KAAK,WAAW,YAAY,GAAM,CAAK;EAEtD,IAAG,EAAE,aAAkB,IAInB,MAFA,KAAK,WAAW,QAAQ,EAAO,EAAE,GAEvB,MACN,iCAAkC,EAAM,qEAE5C;EAGJ,OAAO;CACX;CASA,MAAgB,aAChB;EACI,IAAM,EAAE,gBAAa,oBAAiB,KAAK;EAE3C,AAAG,KAAK,QAAQ,WAAW,KAAK,QAAQ,QAAQ,SAAS,MAErD,KAAK,cAAc,GAAG,sBAAsB,GAC5C,MAAM,EAAa,QAAQ,KAAK,QAAQ,OAAO;EAInD,IAAM,IAAQ,EAAY,YAAY;EAmCtC,IAlCA,KAAK,SAAS,GAId,KAAK,kBAAkB,GAAO,KAAK,QAAQ,UAAU,GAGlD,KAAK,QAAQ,YAEZ,KAAK,cAAc,GAAG,yBAAyB,GAC/C,MAAM,KAAK,eAAe,CAAK,IAGhC,KAAK,QAAQ,UAEZ,KAAK,cAAc,IAAI,uBAAuB,GAC9C,MAAM,KAAK,eAAe,CAAK,IAIhC,KAAK,QAAQ,gBAEZ,KAAK,cAAc,IAAI,2BAA2B,GAClD,KAAK,oBAAoB,CAAK,IAIlC,KAAK,gBAAgB,CAAK,GAE1B,KAAK,eAAe,CAAK,GAGzB,KAAK,iBAAiB,CAAK,GAExB,KAAK,QAAQ,gBAChB;GACI,IAAM,IAAW,KAAK,QAAQ,eAAe,YAAY;GAEzD,AADA,KAAK,KAAK,KAAK,iCAAkC,EAAU,UAAU,GACrE,MAAM,GAAoB,GAAO,KAAK,QAAQ,gBAAgB,KAAK,IAAI;EAC3E;EAyBA,OAvBA,KAAK,WAAW,IAAI,GAAe,GAAO,KAAK,QAAQ,QAAQ,GAE/D,KAAK,cAAc,IAAI,gCAAgC,GACvD,MAAM,KAAK,uBAAuB,CAAK,GAKnC,KAAK,gBAEL,KAAK,cAAc,IAAI,sBAAsB,GAC7C,MAAM,KAAK,oBAAoB,GAE/B,KAAK,cAAc,IAAI,yBAAyB,GAChD,KAAK,oBAAoB,IAG1B,KAAK,QAAQ,WAEZ,KAAK,cAAc,IAAI,mBAAmB,GAC1C,MAAM,KAAK,oBAAoB,IAG5B;CACX;CAKA,MAAc,eAAe,GAC7B;EACI,IAAM,IAAY,KAAK,cAAc;EACjC,OAQJ;GAHA,KAAK,KAAK,MAAM,uBAAwB,GAAY,GAGjD,KAAK,eAAe,MAEnB,EAAM,uBAAuB,IAC7B,KAAK,KAAK,MAAM,6CAA6C;GAGjE,IACA;IAEI,AADA,MAAM,KAAK,QAAQ,YAAY,aAAa,CAAC,GAAG,GAAW,CAAK,GAChE,KAAK,KAAK,MAAM,gCAAgC;GACpD,SACO,GACP;IAEI,MADA,KAAK,KAAK,MAAM,8BAA+B,KAAc,CAAK,GAC5D;GACV;EAXA;CAYJ;CAKA,gBACA;EACQ,SAAK,QAAQ,OAUjB,OALG,OAAO,KAAK,QAAQ,SAAU,WAEtB,KAAK,QAAQ,QAGjB,KAAK,QAAQ,MAAM;CAC9B;CAKA,iBACA;EAMI,OALG,CAAC,KAAK,QAAQ,SAAS,OAAO,KAAK,QAAQ,SAAU,WAE7C,KAGJ,KAAK,QAAQ,MAAM,gBAAgB;CAC9C;CAKA,oBAA4B,GAC5B;EACI,IAAM,IAAM,KAAK,QAAQ;EACrB,OAUJ;OAPG,EAAI,QAEH,EAAM,qBAAqB,KAAK,0BAA0B,EAAI,KAAK,EAAI,iBAAiB,KAAK,CAAK,GAClG,KAAK,KAAK,MAAM,iBAAkB,EAAI,KAAM,IAI7C,EAAI,QACP;IACI,IAAM,IAAM,KAAK,kBAAkB,EAAI,MAAM;IAE7C,IAAG,MAAQ,UAAU,MAAQ,QAC7B;KAEI,IAAG,CAAC,EAAI,KACR;MACI,IAAM,IAAM,EAAI,iBAAiB;MACjC,EAAM,qBAAqB,KAAK,0BAA0B,EAAI,QAAQ,GAAK,CAAK;KACpF;KAEA,AAAG,EAAM,sBAEL,EAAM,oBAAoB,EAAM,oBAAoB,IAAM,EAAI,cAAc,KAAM,GAAG,EAAK;IAElG,OAEA;KAEI,IAAM,IAAO,EAAI,cAAc,KACzB,IAAS,EAAY,aAAa,UAAU;MAAE,UAAU;MAAM,UAAU;KAAG,GAAG,CAAK,GAEnF,IAAM,IAAI,EAAiB,cAAc,CAAK;KAKpD,AAJA,EAAI,kBAAkB,IACtB,EAAI,kBAAkB,IACtB,EAAI,kBAAkB,IAAI,GAAQ,EAAI,QAAQ,CAAK,GACnD,EAAO,WAAW,GAClB,EAAO,mBAAmB;IAC9B;IAEA,KAAK,KAAK,MAAM,oBAAqB,EAAI,QAAS;GACtD;GAGA,AAAG,EAAI,aAAa,KAAA,KAAa,EAAM,uBAEnC,EAAO,mBAAsC,YAAY,EAAI;EALjE;CAOJ;CAKA,0BAAkC,GAAe,GAAqB,GACtE;EAQI,OAPY,KAAK,kBAAkB,CAEhC,MAAQ,SAEA,EAAY,0BAA0B,GAAM,CAAK,IAGrD,IAAI,EAAe,GAAM,GAAO,CAAU;CACrD;CAEA,kBAA0B,GAC1B;EACI,IAAM,IAAW,EAAK,YAAY,GAAG;EAOrC,OALG,KAAY,IAEJ,EAAK,MAAM,CAAQ,CAAC,CAAC,YAAY,IAGrC;CACX;CAMA,oBACA;EACI,IAAM,IAAS,KAAK,QAAQ;EAY5B,OAVG,MAAW,QAKX,OAAO,kBAAoB,OAAe,aAAkB,kBAEpD,OAGJ;CACX;CAMA,gBAAwB,GACxB;EACI,IAAM,IAAgB,KAAK,QAAQ,SAC7B,IAAS,KAAK,kBAAkB;EAEtC,IAAG,CAAC,GACJ;GAEI,AAAG,EAAM,QAAQ,SAAS,MAEtB,EAAM,eAAe,EAAM,QAAQ,IAEhC,KAEC,EAAM,aAAa,cAAc,GAAQ,EAAI,GAGjD,KAAK,KAAK,MAAM,+BAAgC,EAAM,aAAa,KAAM,EAAE;GAG/E;EACJ;EAEA,IAAI,IAA+B;EAEnC,KAAI,IAAM,CAAE,GAAM,MAAY,OAAO,QAAQ,CAAa,GAC1D;GACI,IAAM,IAAW,EAAM,gBAAgB,CAAI;GAE3C,IAAG,GAKC,AAHA,KAAK,mBAAmB,GAAU,CAAM,GACxC,KAAK,KAAK,MAAM,iCAAkC,EAAM,EAAE,IAEvD,CAAC,KAAgB,EAAO,YAEvB,IAAe;QAGlB,IAAG,EAAO,MACf;IACI,IAAM,IAAS,KAAK,cAAc,GAAM,GAAQ,CAAK;IAErD,AAAG,MAAW,SAEV,KAAK,KAAK,MAAM,WAAY,EAAO,KAAM,YAAa,EAAM,EAAE,IAE3D,CAAC,KAAgB,EAAO,YAEvB,IAAe;GAG3B,OAGI,KAAK,KAAK,KACN,WAAY,EAAM,sDACtB;EAER;EAEA,IAAG,GACH;GACI,EAAM,eAAe;GAErB,IAAM,IAAY,EAAc,EAAa;GAC7C,AAAG,KAAU,GAAW,kBAAkB,MAEtC,EAAa,cAAc,GAAQ,EAAI;EAE/C;CACJ;CAMA,cAAsB,GAAe,GAA2B,GAChE;EACI,IAAI,IAAM,EAAQ,KAAK;EACvB,AAAG,EAAO,aAEN,IAAM,EAAU,EAAO,QAAQ;EAGnC,IAAI;EAEJ,QAAQ,EAAO,MAAf;GAEI,KAAK,aACL;IACI,IAAI,IAAS,EAAQ,KAAK;IAM1B,AALG,EAAO,WAEN,IAAS,EAAU,EAAO,MAAM,IAGpC,IAAS,IAAI,EACT,GACA,EAAO,SAAS,KAAK,KAAK,GAC1B,EAAO,QAAQ,KAAK,KAAK,GACzB,EAAO,UAAU,IACjB,GACA,CACJ;IACA;GACJ;GAEA,KAAK;IAED,IAAS,IAAI,GAAgB,GAAM,GAAK,CAAK;IAC7C;GAGJ,KAAK;IAED,IAAG,CAAC,EAAO,cAGP,OADA,KAAK,KAAK,KAAK,sBAAuB,EAAM,kCAAkC,GACvE;IAKX,IAAS,IAAI,GAAiB,GAAM,GAAO,EACvC,cAAc,EAAO,aACzB,CAAC;IACD;GAIJ,SAEI,IAAS,IAAI,EAAW,GAAM,GAAK,CAAK;EAGhD;EAGA,OADA,KAAK,mBAAmB,GAAQ,CAAM,GAC/B;CACX;CAKA,mBAA2B,GAAiB,GAC5C;EA2BI,AA1BG,EAAO,QAAQ,KAAA,MAAa,EAAO,MAAM,EAAO,MAChD,EAAO,aAAY,EAAO,WAAW,EAAU,EAAO,QAAQ,IAC9D,EAAO,SAAS,KAAA,MAAa,EAAO,OAAO,EAAO,OAClD,EAAO,SAAS,KAAA,MAAa,EAAO,OAAO,EAAO,OAElD,aAAkB,MAEd,EAAO,UAAU,KAAA,MAAa,EAAO,QAAQ,EAAO,QACpD,EAAO,aAAY,EAAO,WAAW,EAAU,EAAO,QAAQ,KAGlE,aAAkB,MAId,EAAO,UAAU,EAAO,UAAU,EAAU,EAAO,MAAM,CAAC,GAC1D,EAAO,UAAU,KAAA,MAAa,EAAO,QAAQ,EAAO,QACpD,EAAO,SAAS,KAAA,MAAa,EAAO,OAAO,EAAO,OAClD,EAAO,WAAW,KAAA,MAAa,EAAO,SAAS,EAAO,SACtD,EAAO,qBAAqB,KAAA,MAAa,EAAO,mBAAmB,EAAO,mBAC1E,EAAO,qBAAqB,KAAA,MAAa,EAAO,mBAAmB,EAAO,mBAC1E,EAAO,mBAAmB,KAAA,MAAa,EAAO,iBAAiB,EAAO,iBACtE,EAAO,mBAAmB,KAAA,MAAa,EAAO,iBAAiB,EAAO,iBACtE,EAAO,mBAAmB,KAAA,MAAa,EAAO,iBAAiB,EAAO,kBAG1E,aAAkB,OAEd,EAAO,iBAAiB,KAAA,MAAa,EAAO,OAAO,eAAe,EAAO,eACzE,EAAO,QAAQ,KAAA,MAAa,EAAO,MAAM,EAAO,MAChD,EAAO,UAAU,KAAA,MAAa,EAAO,QAAQ,EAAO,QACpD,EAAO,WAAW,KAAA,MAAa,EAAO,SAAS,EAAO,SACtD,EAAO,WAAU,EAAO,SAAS,EAAU,EAAO,MAAM,IACxD,EAAO,oBAAoB,KAAA,MAAa,EAAO,kBAAkB,EAAO,kBACxE,EAAO,WAAW,KAAA,MAAa,EAAO,OAAO,SAAS,EAAO,SAC7D,EAAO,WAAW,KAAA,MAAa,EAAO,OAAO,SAAS,EAAO,SAC7D,EAAO,cAAc,KAAA,MAAa,EAAO,OAAO,YAAY,EAAO,YACnE,EAAO,cAAc,KAAA,MAAa,EAAO,OAAO,YAAY,EAAO,YACnE,EAAO,aAAa,KAAA,MAAa,EAAO,OAAO,WAAW,EAAO,WACjE,EAAO,aAAa,KAAA,MAAa,EAAO,OAAO,WAAW,EAAO;CAE5E;CAQA,eAAuB,GACvB;EACI,IAAM,IAAe,KAAK,QAAQ;EAC9B,OAEJ,KAAI,IAAM,CAAE,GAAM,MAAY,OAAO,QAAQ,CAAY,GACzD;GACI,IAAM,IAAW,EAAM,eAAe,CAAI;GAE1C,AAAG,KAEC,KAAK,kBAAkB,GAAU,CAAM,GACvC,KAAK,KAAK,MAAM,gCAAiC,EAAM,EAAE,KAErD,EAAO,QAEX,KAAK,aAAa,GAAM,GAAQ,CAAK,GACrC,KAAK,KAAK,MAAM,WAAY,EAAO,KAAM,WAAY,EAAM,EAAE,KAI7D,KAAK,KAAK,KACN,UAAW,EAAM,sDACrB;EAER;CACJ;CAKA,aAAqB,GAAe,GAA0B,GAC9D;EACI,IAAI,IAAM,IAAI,EAAQ,GAAG,IAAI,CAAC;EAC9B,AAAG,EAAO,cAEN,IAAM,EAAU,EAAO,SAAS;EAGpC,IAAI,IAAM,EAAQ,KAAK;EAMvB,QALG,EAAO,aAEN,IAAM,EAAU,EAAO,QAAQ,IAG3B,EAAO,MAAf;GAEI,KAAK,eACL;IACI,IAAM,IAAQ,IAAI,EAAiB,GAAM,GAAK,CAAK;IAOnD,AALG,EAAO,gBAEN,EAAM,cAAc,KAAK,UAAU,EAAO,WAAW,IAGzD,KAAK,kBAAkB,GAAO,CAAM;IACpC;GACJ;GAEA,KAAK,eACL;IACI,IAAM,IAAQ,IAAI,EAAiB,GAAM,GAAK,CAAK;IACnD,KAAK,kBAAkB,GAAO,CAAM;IACpC;GACJ;GAEA,KAAK,SACL;IACI,IAAM,IAAQ,IAAI,GAAW,GAAM,GAAK,CAAK;IAC7C,KAAK,kBAAkB,GAAO,CAAM;IACpC;GACJ;GAEA,KAAK,QACL;IACI,IAAM,IAAQ,IAAI,GACd,GACA,GACA,GACA,EAAO,SAAS,KAAK,KAAK,GAC1B,EAAO,YAAY,GACnB,CACJ;IACA,KAAK,kBAAkB,GAAO,CAAM;IACpC;GACJ;GAEA,KAAK,YACL;IACI,IAAM,IAAQ,IAAI,GACd,GACA,GACA,EAAO,SAAS,GAChB,EAAO,UAAU,GACjB,CACJ;IAKA,IAJA,KAAK,kBAAkB,GAAO,CAAM,GAIjC,EAAO,WACV;KACI,IAAM,IAAQ,IAAI,EAAc,GAAI,EAAM,SAAS,CAAK;KAMxD,AALA,EAAM,WAAW,EAAM,SAAS,MAAM,GACtC,EAAM,WAAW,EAAQ,KAAK,GAC9B,EAAM,SAAS,GAGf,EAAM,OAAO,EAAM,SAAS,SAAS,CAAG,CAAC;IAC7C;IAEA;GACJ;EACJ;CACJ;CAKA,kBACI,GACA,GAEJ;EAGI,AAFG,EAAO,cAAc,KAAA,MAAa,EAAM,YAAY,EAAO,YAC3D,EAAO,YAAW,EAAM,UAAU,KAAK,UAAU,EAAO,OAAO,IAC/D,EAAO,aAAY,EAAM,WAAW,KAAK,UAAU,EAAO,QAAQ;CACzE;CAMA,iBAAyB,GACzB;EACI,IAAM,IAAgB,KAAK,QAAQ;EACnC,IAAG,CAAC,GAAe,SAEf;EAIJ,IAAM,IAAiB,EAAM,OAAO,QAC/B,MAAU,GAAwB,iBAAiB,CAAK,CAC7D;EAEA,IAAG,EAAe,WAAW,GAC7B;GACI,KAAK,KAAK,MAAM,iDAAiD;GACjE;EACJ;EAGA,KAAI,IAAM,KAAS,GAEf,EAAM,YAAY,CAAK;EAI3B,IAAM,IAAY,IAAI,GAAwB,kBAAkB,GAAgB,CAAK;EAUrF,AAPG,EAAc,oBAAoB,KAAA,MAAa,EAAU,kBAAkB,EAAc,kBACzF,EAAc,kBAAkB,KAAA,MAAa,EAAU,gBAAgB,EAAc,gBACrF,EAAc,gBAAgB,KAAA,MAAa,EAAU,cAAc,EAAc,cACjF,EAAc,aAAa,KAAA,MAAa,EAAU,WAAW,EAAc,WAE9E,KAAK,kBAAkB,GAEvB,KAAK,KAAK,MACN,+BAAgC,EAAe,OAAQ,WACjD,EAAU,gBAAiB,GAAI,EAAU,cAAe,UACxD,EAAU,YAAa,cACjC;EAGA,KAAI,IAAM,KAAY,EAAM,WAExB,AAAG,aAAoB,OAEnB,EAAS,sBAAsB;CAG3C;CAOA,MAAc,eAAe,GAC7B;EACI,IAAM,IAAgB,KAAK,QAAQ,SAE/B,IAAU,EAAe;EAE7B,AAAG,OAAO,KAAkB,YAAY,EAAc,YAElD,IAAU,IAAI,EACV,EAAc,QAAQ,GACtB,EAAc,QAAQ,GACtB,EAAc,QAAQ,CAC1B;EAIJ,IAAI;EAOJ,AANG,KAAK,QAAQ,wBAEZ,IAAuB,MAG3B,KAAK,KAAK,MAAM,kCAAmC,EAAQ,SAAS,GAAI,GACxE,MAAM,KAAK,QAAQ,YAAY,cAAc,GAAO,GAAS,CAAoB;CACrF;CAKA,MAAc,uBAAuB,GACrC;EACI,IAAM,IAAQ,EAAM,eAAe,OAAO,EAAM,MAAyB,GACnE,IAA6B,CAAC;EAMpC,KAAI,IAAM,KAAQ,GAClB;GACI,IAAM,IAAW,KAAK,uBAAuB,CAAI;GAEjD,AAAG,KAAY,OAAO,KAAK,CAAQ,CAAC,CAAC,SAAS,MAE1C,EAAK,WAAW;IAAE,GAAG,EAAK;IAAU,GAAG;GAAS,GAChD,EAAS,KAAK,CAAI;EAE1B;EAEA,KAAI,IAAM,KAAQ,GAKd,AAHA,KAAK,yBAAyB,CAAI,GAGlC,MAAM,KAAK,qBAAqB,CAAI;EAGxC,KAAK,KAAK,MACN,aAAc,EAAM,OAAQ,gBAAiB,KAAK,aAAa,OAAQ,oBAC7D,KAAK,aAAa,OAAQ,cACxC;CACJ;CAKA,yBAAiC,GACjC;EACI,IAAM,IAAW,EAAK;EAEtB,KAAI,IAAM,KAAY,IAElB,AAAG,KAAY,KAAY,CAAC,KAAK,iBAAiB,IAAI,CAAQ,KAE1D,KAAK,KAAK,KACN,SAAU,EAAK,KAAM,aAAc,EAAU,+HAEjD;CAGZ;CAKA,MAAc,qBAAqB,GACnC;EAMI,IAAG,WAAW,EAAK,UACnB;GACI,IAAM,EAAE,aAAU,aAAU,eAAY,GAAsB,CAAI;GAElE,KAAK,aAAa,KAAK;IACnB,MAAM,EAAK,SAAS;IACpB;IACA;IACA;IACA;GACJ,CAAC;EACL;EAYA,AATG,YAAY,EAAK,YAEhB,KAAK,aAAa,KAAK;GACnB,MAAM,EAAK,SAAS;GACpB;EACJ,CAAC,GAIL,MAAM,KAAK,qBAAqB,CAAI;CACxC;CAKA,MAAc,sBACd;EACI,KAAI,IAAM,KAAc,KAAK,cAC7B;GACI,IAAM,IAAW,KAAK,QAAQ,SAAS,EAAW;GAElD,AAAI,IAOA,MAAM,KAAK,mBAAmB,GAAY,CAAQ,IALlD,KAAK,KAAK,KAAK,kCAAmC,EAAW,KAAM,YAAY;EAOvF;CACJ;CAKA,MAAc,mBAAmB,GAA6B,GAC9D;EACI,IACA;GACI,IAAM,IAAS,MAAM,KAAK,aAAa,GAAU,CAAU;GAM3D,AALA,KAAK,iBAAiB,KAAK,CAAM,GAGjC,EAAW,KAAK,QAAQ,GAExB,KAAK,KAAK,MAAM,mBAAoB,EAAS,OAAQ,oBAAqB,EAAW,KAAM,EAAE;EACjG,SACO,GACP;GACI,KAAK,KAAK,MAAM,0CAA2C,EAAW,KAAM,KAAK,CAAK;EAC1F;CACJ;CAUA,MAAc,aAAa,GAA4B,GACvD;EACI,IAAM,IAAyC;GAC3C,GAAG,EAAS;GACZ,MAAM,EAAS,QAAQ,CAAC;GACxB,UAAU,EAAa,EAAW,QAAQ;GAC1C,UAAU,EAAa,EAAW,QAAQ;EAC9C;EAOA,AALG,EAAS,gBAAgB,KAAA,MAExB,EAAa,cAAc,EAAS,cAGrC,EAAS,kBAAkB,KAAA,MAE1B,EAAa,gBAAgB,EAAS;EAG1C,IAAM,IAAQ;EAEd,OAAO,KAAK,GAAiB,EAAS,QAAQ,CAAK;CACvD;CAMA,sBACA;EACI,KAAI,IAAM,KAAc,KAAK,cAC7B;GACI,IAAM,IAAY,KAAK,QAAQ,WAAW,EAAW;GAErD,AAAI,IAMA,KAAK,iBAAiB,GAAW,CAAU,IAJ3C,KAAK,KAAK,KAAK,mCAAoC,EAAW,KAAM,YAAY;EAMxF;CACJ;CAOA,iBAAyB,GAA8B,GACvD;EACI,IACA;GACI,IAAM,IAAyC;IAC3C,GAAG,EAAU;IACb,MAAM,EAAU,QAAQ,CAAC;IACzB,UAAU,EAAW,KAAK;GAC9B;GAOA,AALG,EAAU,gBAAgB,KAAA,MAEzB,EAAa,cAAc,EAAU,cAGtC,EAAU,kBAAkB,KAAA,MAE3B,EAAa,gBAAgB,EAAU;GAG3C,IAAM,IAAQ,GACR,IAAS,KAAK,GAAiB,EAAU,QAAQ,CAAK;GAG5D,AAFA,KAAK,iBAAiB,KAAK,CAAM,GAEjC,KAAK,KAAK,MAAM,mBAAoB,EAAU,OAAQ,mBAAoB,EAAW,KAAK,KAAM,EAAE;EACtG,SACO,GACP;GACI,KAAK,KAAK,MAAM,yCAA0C,EAAW,KAAK,KAAM,KAAK,CAAK;EAC9F;CACJ;CAUA,MAAc,sBACd;EACI,IAAM,IAAe,KAAK,QAAQ;EAClC,IAAG,CAAC,GAAgB;EAEpB,IAAM,EAAE,oBAAiB,KAAK;EAC9B,IAAG,CAAC,GACJ;GACI,KAAK,KAAK,KAAK,+DAA+D;GAC9E;EACJ;EAEA,KAAI,IAAM,CAAE,GAAM,MAAY,OAAO,QAAQ,CAAY,GAErD,IACA;GAEI,IAAM,IAAQ,MAAM,EAAa,YAAY,GAAM,EAAO,KAAK,EAAO,SAAS;IAC3E,MAAM,EAAO,QAAQ;IACrB,QAAQ,EAAO,UAAU;GAC7B,CAAC;GASD,AAPA,KAAK,aAAa,IAAI,GAAM,CAAK,GAE9B,EAAO,YAEN,EAAM,KAAK,GAGf,KAAK,KAAK,MAAM,yBAA0B,EAAM,EAAE;EACtD,SACO,GACP;GACI,KAAK,KAAK,MAAM,iCAAkC,EAAM,KAAK,CAAK;EACtE;CAER;CAUA,MAAM,eACN;EACI,KAAI,IAAM,CAAE,GAAM,MAAW,KAAK,cAE9B,AAAG,EAAM,UAAU,GAAW,YAE1B,KAAK,+BAA+B,IAAI,CAAI,GAC5C,EAAM,MAAM;CAGxB;CAMA,MAAM,aACN;EACI,KAAI,IAAM,KAAQ,KAAK,gCACvB;GACI,IAAM,IAAQ,KAAK,aAAa,IAAI,CAAI;GACxC,AAAG,KAEC,EAAM,KAAK;EAEnB;EAEA,KAAK,+BAA+B,MAAM;CAC9C;CASA,MAAe,UACf;EAGI,IAAG,KAAK,gBAAgB,MAEpB,KAAI,IAAM,KAAU,KAAK,kBAErB,KAAK,YAAY,QAAQ,EAAO,EAAE;EAO1C,AAHA,KAAK,cAAc,MACnB,KAAK,mBAAmB,CAAC,GACzB,KAAK,eAAe,CAAC,GACrB,KAAK,eAAe,CAAC;EAGrB,KAAI,IAAM,KAAS,KAAK,aAAa,OAAO,GAExC,EAAM,QAAQ;EAKlB,AAHA,KAAK,aAAa,MAAM,GACxB,KAAK,+BAA+B,MAAM,GAE1C,MAAM,MAAM,QAAQ;CACxB;AACJ,GC1lCa,KAAb,MACA;CACI;CACA,cAAoD;CACpD;CACA;CAGA,gCAAwB,IAAI,IAAyB;CAGrD,gCAAwB,IAAI,IAAwC;CAGpE,gCAAwB,IAAI,IAA6B;CAGzD,oCAA4B,IAAI,IAA6B;CAG7D,gBAAiD;CAGjD,gBAAiD;CAGjD,sBAA+C,CAAC;CAKhD,IAAW,eACX;EACI,OAAO,KAAK;CAChB;CAOA,IAAW,eACX;EACI,OAAO,KAAK;CAChB;CAGA,IAAW,mBACX;EACI,OAAO,KAAK;CAChB;CAIA,YACI,GACA,GAEJ;EA0BI,AAzBA,KAAK,OAAO,GACZ,KAAK,UAAU,KAAU,IAAI,EAAe,QAAQ,IAAI,EAAY,CAAC,GACrE,KAAK,OAAO,GAAQ,UAAU,cAAc,KAAK,IAAI,EAAW,cAAc,GAE9E,KAAK,oBAAoB,KACrB,KAAK,KAAK,GAAG,mBAAmB,MAChC;GACI,KAAK,gBAAgB,EAAM,OAAO;EACtC,CAAC,CACL,GAEA,KAAK,oBAAoB,KACrB,KAAK,KAAK,GAAG,mBAAmB,MAChC;GACI,KAAK,gBAAgB,EAAM,OAAO;EACtC,CAAC,CACL,GAEA,KAAK,oBAAoB,KACrB,KAAK,KAAK,GAAG,gBAAgB,MAC7B;GACI,KAAK,aAAa,EAAM,OAAO;EACnC,CAAC,CACL,GAEA,KAAK,KAAK,KAAK,0BAA0B;CAC7C;CAWA,eAAe,GACf;EACI,KAAK,cAAc;CACvB;CAMA,gBAAwB,GACxB;EACI,KAAK,KAAK,MAAM,SAAU,EAAQ,UAAW,aAAc,EAAQ,SAAU,EAAE;CACnF;CAEA,gBAAwB,GACxB;EACI,KAAK,KAAK,KAAK,SAAU,EAAQ,UAAW,qBAAqB;CACrE;CAEA,aAAqB,GACrB;EACI,KAAK,KAAK,MAAM,SAAU,EAAQ,UAAW,UAAW,EAAQ,SAAU;CAC9E;CAGA,eAAuB,GACvB;EACI,IAAM,IAAS,KAAK;EAEpB,IAAG,CAAC,GAEA,MAAU,MAAM,gEAAgE;EAGpF,OAAO;GACH,KAAK,KAAK;GACV,aAAa,EAAO,QAAQ;GAC5B,cAAc,EAAO,SAAS;GAC9B,cAAc,EAAO,SAAS;GAC9B,oBAAoB,GAAO,MAAe,EAAO,kBAAkB,GAAO,EAAO,MAAM;IACnF;IACA,mBAAmB,EAAO;GAC9B,CAAC;GACD,QAAQ,EAAO;GACf,qBAAqB,EAAO;GAC5B,kBAAkB,KAAK;GACvB,QAAQ,KAAK;EACjB;CACJ;CAIA,qBAA6B,GAC7B;EACI,IAAM,IAAU,KAAK,eAAe,CAAM;EAE1C,IAAG,EAAO,OACV;GACI,IAAM,IAAa,KAAK,cAAc,IAAI,EAAO,KAAK;GACtD,IAAG,CAAC,GAEA,MAAU,MAAM,gBAAiB,EAAO,MAAO,qBAAqB;GAIxE,OADA,KAAK,KAAK,MAAM,mBAAoB,EAAO,KAAM,iBAAkB,EAAO,MAAO,EAAE,GAC5E,IAAI,EAAW,GAAQ,CAAO;EACzC;EAGA,OADA,KAAK,KAAK,MAAM,mBAAoB,EAAO,KAAM,kBAAkB,GAC5D,IAAI,GAAoB,GAAQ,CAAO;CAClD;CAUA,oBAA2B,GAC3B;EAOI,AANG,KAAK,cAAc,IAAI,EAAO,IAAI,KAEjC,KAAK,KAAK,KAAK,iBAAkB,EAAO,KAAM,sCAAsC,GAGxF,KAAK,cAAc,IAAI,EAAO,MAAM,CAAM,GAC1C,KAAK,KAAK,KAAK,4BAA6B,EAAO,MAAO;CAC9D;CAOA,mBAA0B,GAAe,GACzC;EAOI,AANG,KAAK,cAAc,IAAI,CAAI,KAE1B,KAAK,KAAK,KAAK,gBAAiB,EAAM,sCAAsC,GAGhF,KAAK,cAAc,IAAI,GAAM,CAAU,GACvC,KAAK,KAAK,KAAK,2BAA4B,GAAO;CACtD;CAWA,wBAA+B,GAAmB,GAClD;EAOI,AANG,KAAK,kBAAkB,IAAI,CAAQ,KAElC,KAAK,KAAK,KAAK,qBAAsB,EAAU,sCAAsC,GAGzF,KAAK,kBAAkB,IAAI,GAAU,CAAO,GAC5C,KAAK,KAAK,MAAM,gCAAiC,GAAW;CAChE;CAGA,0BAAiC,GACjC;EACI,KAAK,kBAAkB,OAAO,CAAQ;CAC1C;CAGA,2BACA;EACI,KAAK,kBAAkB,MAAM;CACjC;CAOA,SAAgB,GAChB;EACI,OAAO,KAAK,cAAc,IAAI,CAAI,KAAK;CAC3C;CAGA,eAAsB,GACtB;EACI,OAAO,KAAK,cAAc,IAAI,CAAI,KAAK;CAC3C;CAOA,MAAa,UACT,GACA,GAEJ;EACI,IAAI;EAEJ,IAAG,OAAO,KAAiB,UAC3B;GACI,IAAM,IAAa,KAAK,cAAc,IAAI,CAAY;GACtD,IAAG,CAAC,GAEA,MAAU,MAAM,iBAAkB,EAAc,qBAAqB;GAGzE,IAAS;EACb,OAII,AADA,IAAS,GACT,KAAK,oBAAoB,CAAM;EAGnC,IAAM,IAAW,KAAK,cAAc,IAAI,EAAO,IAAI;EACnD,IAAG,GAGC,OADA,KAAK,KAAK,KAAK,UAAW,EAAO,KAAM,oBAAoB,GACpD;EAGX,KAAK,KAAK,MAAM,kBAAmB,EAAO,MAAO;EACjD,IAAM,IAAQ,KAAK,qBAAqB,CAAM;EAI9C,KAAK,gBAAgB;EACrB,IACA;GACI,MAAM,EAAM,KAAK,CAAO;EAC5B,SACO,GACP;GAII,MADA,MAAM,EAAM,QAAQ,GACd;EACV,UAEA;GACI,KAAK,gBAAgB;EACzB;EAIA,OAFA,KAAK,cAAc,IAAI,EAAO,MAAM,CAAK,GAElC;CACX;CAGA,gBAAwB,GACxB;EACO,KAAK,gBAAgB,QAAQ,EAAM,gBAAgB,QAKtD,KAAK,YAAY,eAAe,EAAM,WAAW;CACrD;CAMA,gBAAgB,GAChB;EAMI,AALG,KAAK,kBAAkB,QAEtB,KAAK,cAAc,iBAAiB,CAAU,GAG/C,KAAK,gBAAgB,QAEpB,KAAK,YAAY,eAAe,CAAU;CAElD;CAOA,MAAa,cAAc,GAC3B;EACI,IAAM,IAAQ,MAAM,KAAK,UAAU,CAAY;EAqB/C,OAjBG,KAAK,kBAAkB,KAEtB,KAAK,gBAAgB,CAAK,GACnB,MAGX,MAAM,KAAK,YAAY,KAAK,aAAa,GAEzC,KAAK,gBAAgB,GACrB,KAAK,gBAAgB,CAAK,GAEvB,EAAM,cAEL,MAAM,EAAM,WAAW,GAG3B,KAAK,KAAK,KAAK,oBAAqB,EAAM,MAAO,GAC1C;CACX;CAGA,MAAc,YAAY,GAC1B;EACI,AAAG,GAAO,gBAEN,MAAM,EAAM,aAAa;CAEjC;CAMA,MAAa,YAAY,GACzB;EACI,IAAM,IAAQ,KAAK,cAAc,IAAI,CAAI;EACzC,AAAG,MAII,KAAK,kBAAkB,MAEtB,KAAK,gBAAgB,MAElB,KAAK,gBAAgB,QAEpB,KAAK,YAAY,eAAe,IAAI,IAI5C,MAAM,EAAM,QAAQ,GACpB,KAAK,cAAc,OAAO,CAAI,GAE9B,KAAK,KAAK,KAAK,mBAAoB,GAAO;CAElD;CAGA,MAAa,qBACb;EACI,AAAG,KAAK,gBAEJ,MAAM,KAAK,YAAY,KAAK,cAAc,IAAI,IAI9C,KAAK,KAAK,KAAK,4BAA4B;CAEnD;CAWA,MAAa,WAAW,GAAoB,GAC5C;EACI,IAAM,IAAW,KAAK,eAIhB,IAAqB,KAAK,aAAa,cAAc,MAIvD,IAAsB;EAE1B,IACA;GAUI,AATA,KAAK,KAAK,KAAK;IACX,MAAM;IACN,UAAU;IACV,SAAS;KACL,MAAM,GAAU;KAChB,IAAI;IACR;GACJ,CAAC,GAED,KAAK,KAAK,KAAK;IACX,MAAM;IACN,UAAU;IACV,SAAS;KACL,OAAO;KACP;IACJ;GACJ,CAAC;GAKD,IAAM,IAAe,KAAK,cAAc,IAAI,CAAS,KAAK,MACpD,IAAW,MAAiB,SAAS,GAAS,eAAe,MAAQ,MAAiB,IACtF,IAAmB,KAAY,MAAiB;GAStD,AAPG,KAIC,MAAM,KAAK,YAAY,CAAQ,GAGhC,KAEC,MAAM,KAAK,YAAY,CAAS;GAGpC,IAAM,IAAW,MAAM,KAAK,UAAU,GAAW,CAAO;GAExD,IAAG,GAAS,gBAAgB,IAExB;GA2BJ,AAxBI,MAKA,MAAM,KAAK,YAAY,CAAQ,GAC/B,IAAsB,KAKvB,MAAa,QAAQ,CAAC,KAAoB,GAAS,cAAc,MAEhE,MAAM,KAAK,YAAY,EAAS,IAAI,GAGxC,KAAK,gBAAgB,GACrB,KAAK,gBAAgB,CAAQ,GAE1B,EAAS,cAER,MAAM,EAAS,WAAW,GAG9B,KAAK,KAAK,KAAK;IACX,MAAM;IACN,UAAU;IACV,SAAS,EAAE,aAAU;GACzB,CAAC;EACL,SACO,GACP;GAyCI,MApCG,MAAa,QAAQ,EAAS,YAE7B,KAAK,gBAAgB,GAElB,KAAK,gBAAgB,QAEpB,KAAK,YAAY,eAAe,CAAkB,GAKnD,KAAuB,EAAS,cAE/B,MAAM,EAAS,WAAW,MAK9B,KAAK,gBAAgB,MAElB,KAAK,gBAAgB,QAEpB,KAAK,YAAY,eAAe,IAAI,IAI5C,KAAK,KAAK,KAAK;IACX,MAAM;IACN,UAAU;IACV,SAAS;KACL,MAAM,GAAU;KAChB,IAAI;KACJ;IACJ;GACJ,CAAC,GAEK;EACV;CACJ;CAUA,MAAM,UACN;EACI,KAAI,IAAM,KAAe,KAAK,qBAE1B,EAAY;EAGhB,KAAK,sBAAsB,CAAC;EAE5B,KAAI,IAAM,KAAS,KAAK,cAAc,OAAO,GAGzC,MAAM,EAAM,QAAQ;EAKxB,AAFA,KAAK,cAAc,MAAM,GACzB,KAAK,cAAc,MAAM,GACzB,KAAK,gBAAgB;CACzB;AACJ,GCjnBa,KAAb,MACA;CACI;CACA,qBAAY,IAAI,IAA0B;CAC1C,KAAe;CACf,KAA0B;CAC1B;CAEA,YAAY,GAA2B,GACvC;EAEI,AADA,KAAK,KAAe,GACpB,KAAK,KAAO,EAAO,UAAU,cAAc;CAC/C;CAMA,MAAM,WAAW,GACjB;EACI,KAAK,GAAK,KAAK,gCAAiC,EAAS,KAAK,IAAI,GAAI;EAEtE,KAAI,IAAM,KAAQ,GAClB;GAEI,IAAM,IAAM,MAAM,KAAK,GAAa,UAAU,CAAI;GAClD,KAAK,GAAU,IAAI,GAAM;IAAE;IAAK,QAAQ;IAAG,OAAO;GAAM,CAAC;EAC7D;EAEA,KAAK,GAAK,KAAK,6BAA6B;CAChD;CAEA,MAAM,UACN;EACI,KAAK,GAAK,KAAK,+BAA+B;EAE9C,KAAI,IAAM,KAAS,KAAK,GAAU,OAAO,GAErC,EAAM,IAAI,QAAQ;EAItB,AADA,KAAK,GAAU,MAAM,GACrB,KAAK,GAAK,KAAK,0BAA0B;CAC7C;CAMA,MAAM,YACF,GACA,GACA,GACA,GAEJ;EACI,IAAI;EAEJ,IAAG,GACH;GACI,IAAM,IAAQ,KAAK,GAAU,IAAI,CAAO;GACxC,IAAG,CAAC,GAEA,MAAU,MACN,mCAAoC,EAAS,wBAAyB,EAAM,qBAChF;GAGJ,IAAM,EAAM;EAChB;EAEA,OAAO,KAAK,GAAa,YAAY,GAAM,GAAK,GAAK,CAAO;CAChE;CAMA,IAAI,eACJ;EAMI,OALG,KAAK,KAEG,KAAK,KAGT,KAAK,GAAa,gBAAgB;CAC7C;CAEA,IAAI,aAAa,GACjB;EACI,AAAI,KAAK,KAOL,KAAK,KAA0B,IAL/B,KAAK,GAAa,gBAAgB,CAAM;CAOhD;CAEA,IAAI,cACJ;EACI,OAAO,KAAK;CAChB;CAEA,IAAI,YAAY,GAChB;EACO,MAAU,KAAK,OAElB,KAAK,KAAe,GAEjB,KAEC,KAAK,KAA0B,KAAK,GAAa,gBAAgB,GACjE,KAAK,GAAa,gBAAgB,CAAC,KAInC,KAAK,GAAa,gBAAgB,KAAK,EAAuB;CAEtE;CAMA,iBAAiB,GAAkB,GACnC;EACI,IAAM,IAAQ,KAAK,GAAgB,CAAO;EAI1C,AAFA,EAAM,SAAS,GAEX,EAAM,SAEN,KAAK,GAAa,aAAa,EAAM,KAAK,CAAM;CAExD;CAEA,iBAAiB,GACjB;EACI,OAAO,KAAK,GAAgB,CAAO,CAAC,CAAC;CACzC;CAEA,gBAAgB,GAAkB,GAClC;EACI,IAAM,IAAQ,KAAK,GAAgB,CAAO;EAEvC,MAAU,EAAM,UAEnB,EAAM,QAAQ,GAEX,IAEC,KAAK,GAAa,aAAa,EAAM,KAAK,CAAC,IAI3C,KAAK,GAAa,aAAa,EAAM,KAAK,EAAM,MAAM;CAE9D;CAEA,eAAe,GACf;EACI,OAAO,KAAK,GAAgB,CAAO,CAAC,CAAC;CACzC;CAEA,IAAI,WACJ;EACI,OAAO,MAAM,KAAK,KAAK,GAAU,KAAK,CAAC;CAC3C;CAEA,GAAgB,GAChB;EACI,IAAM,IAAQ,KAAK,GAAU,IAAI,CAAO;EACxC,IAAG,CAAC,GAEA,MAAU,MAAM,mCAAoC,EAAS,eAAe;EAGhF,OAAO;CACX;AACJ,GClMM,KAAoB,GA+Bb,KAAb,MACA;CACI;CACA;CACA;CAEA,KAA+C,CAAC;CAEhD,KAA4C;CAE5C,YAAY,GAAqC,GAAwB,IAAqB,KAAK,KACnG;EAGI,AAFA,KAAK,KAAe,GACpB,KAAK,KAAiB,GACtB,KAAK,KAAO;CAChB;CAMA,eAAe,GACf;EACI,KAAK,KAAc;CACvB;CAIA,aAAa,GACb;EAGI,OAFA,KAAK,GAAiB,KAAK,CAAI,SAG/B;GACI,IAAM,IAAQ,KAAK,GAAiB,QAAQ,CAAI;GAEhD,AAAG,KAAS,KAER,KAAK,GAAiB,OAAO,GAAO,CAAC;EAE7C;CACJ;CAGA,OACA;EACI,IAAM,IAAa,KAAK,GAAkB,GACpC,IAAmC,CAAC;EAE1C,KAAI,IAAM,KAAQ,KAAK,IAEnB,KAAI,IAAM,CAAE,GAAO,MAAW,OAAO,QAAQ,EAAK,CAAC,GACnD;GACI,IAAG,OAAO,OAAO,GAAQ,CAAK,GAE1B,MAAU,MACN,0EAA2E,EAAO,GACtF;GAGJ,EAAO,KAAS;EACpB;EAGJ,OAAO;GACH;GACA,eAAe,KAAK;GACpB,OAAO,KAAK,GAAa,cAAc,QAAQ;GAC/C,WAAW,KAAK,GAAK;GACrB;GACA,UAAU,EAAW,SAAS;EAClC;CACJ;CAEA,MAAM,KAAK,GACX;EACI,IAAG,EAAS,sBAAsB,IAE9B,MAAU,MACN,4BAA6B,EAAS,kBAAmB,4CAC7D;EAKJ,AAFA,KAAK,GAAe,GAEpB,MAAM,KAAK,GAAa,WAAW,EAAS,OAAO,EAAE,YAAY,GAAK,CAAC;EAOvE,IAAM,IAFkB,KAAK,GAEZ,CAAA,CAAgB,kBAAkB,EAAS,QAAQ;EAOpE,OAFA,KAAK,GAAa,gBAAgB,CAAQ,GAEnC;GAAE,YAAY;GAAU;EAAS;CAC5C;CAEA,MAAM,UACN;EACI,KAAK,GAAiB,SAAS;CACnC;CAEA,KACA;EACI,IAAG,KAAK,OAAgB,MAEpB,MAAU,MAAM,yEAAyE;EAG7F,OAAO,KAAK;CAChB;CAEA,KACA;EACI,IAAM,IAAa,KAAK,GAAe,CAAC,CAAC;EAEzC,IAAG,MAAe,MAEd,MAAU,MAAM,wDAAwD;EAG5E,OAAO;CACX;AACJ,GCrJa,KAAb,MACA;CACI,UAAyC;CACzC,yBAAiB,IAAI,IAA+B;CACpD,6BAAqB,IAAI,IAA8B;CAMvD,oBAAoB,GAAsB,GAC1C;EACI,IAAG,CAAC,EAAK,eAAe,KAAK,OAAO,IAAI,CAAI,GAExC;EAIJ,IAAM,IADS,KAAK,WAAW,CACb,CAAA,CAAO,SAAS,EAAK,WAAW;EAElD,IAAG,CAAC,GAEA;EAGJ,IAAM,IAAqB,EAAE,aAAU;EAEvC,IAAG,GACH;GACI,IAAM,IAAW,KAAK,kBAAkB,GAAO,CAAI;GAEnD,AADA,EAAU,WAAW,GACrB,EAAM,gBAAgB;EAC1B;EAEA,KAAK,OAAO,IAAI,GAAM,CAAK;CAC/B;CAEA,oBAAoB,GACpB;EAEO,CADW,KAAK,OAAO,IAAI,CAC1B,KAAS,CAAC,KAAK,YAKhB,EAAK,eAEJ,KAAK,QAAQ,SAAS,EAAK,WAAW,GAG1C,KAAK,OAAO,OAAO,CAAI;CAC3B;CAEA,UACA;EACI,KAAI,IAAM,CAAE,MAAU,KAAK,QAEvB,KAAK,oBAAoB,CAAI;CAErC;CAEA,QAAQ,GACR;EACI,OAAO,KAAK,OAAO,IAAI,CAAI;CAC/B;CAEA,MAAM,UACN;EAQI,AAPA,AAII,KAAK,aAFL,KAAK,QAAQ,GACb,KAAK,QAAQ,QAAQ,GACN,OAGnB,KAAK,OAAO,MAAM;EAElB,KAAI,IAAM,KAAO,KAAK,WAAW,OAAO,GAEpC,EAAI,QAAQ;EAGhB,KAAK,WAAW,MAAM;CAC1B;CAMA,WAAmB,GACnB;EAMI,OALA,AAEI,KAAK,YAAU,IAAI,GAAc,EAAK,SAAS,CAAC,GAG7C,KAAK;CAChB;CAEA,kBAA0B,GAAgB,GAC1C;EACI,IAAI,IAAW,KAAK,WAAW,IAAI,CAAK;EAYxC,OAXG,MAKH,IAAW,IAAI,EAAiB,uBAAwB,KAAU,EAAK,SAAS,CAAC,GACjF,EAAS,gBAAgB,EAAO,cAAc,CAAK,GACnD,EAAS,kBAAkB,IAC3B,EAAS,YAAY,IACrB,KAAK,WAAW,IAAI,GAAO,CAAQ,GAE5B;CACX;AACJ;;;AC3GA,eAAe,KACf;CAMI,OALI,GAAU,IAKP,GAAa,mBAHT;AAIf;AAKA,eAAe,GACX,GACA,GAEJ;CACI,IAAM,IAAS,IAAI,GAAa,GAAQ,CAAO;CAE/C,OADA,MAAM,EAAO,UAAU,GAChB;AACX;AAKA,SAAS,GAAkB,GAAqB,GAChD;CACI,OAAO,IAAI,EAAO,GAAQ,EAAQ,WAAW,EAAQ,SAAS,EAAQ,kBAAkB;AAC5F;AAKA,SAAS,GAAiB,GAC1B;CACI,OAAO,IAAI,GAAW,CAA4B;AACtD;AAWA,eAAsB,GAClB,GACA,GACA,IAAsB,IACtB,IAAwB,IAAI,EAAW,MAAM,GAEjD;CAEI,IAAG,MAAW,MAGV,OADA,EAAI,MAAM,mBAAmB,GACtB,GAAiB,CAA4B;CAKxD,IAAG,GACH;EACI,IAAG,aAAa,GAChB;GACI,IAAM,IAAc;GACpB,IAAU;IACN,GAAG;IACH,SAAS;KAAE,GAAG,EAAY;KAAS,wBAAwB;IAAK;GACpE;EACJ,OAII,IAAU;GAAE,GAAG;GAAS,wBAAwB;EAAK;CAE7D;CAGA,IAAM,IAAc,EAAQ,UAAU;CAGtC,IAAG,MAAgB,UACnB;EACI,IAAG,MAAM,GAAsB,GAE3B,IACA;GAEI,OADA,EAAI,MAAM,4BAA4B,GAC/B,MAAM,GAAmB,GAAQ,CAAO;EACnD,SACO,GACP;GAEI,MADA,EAAI,MAAM,wCAAwC,CAAK,GAC7C,MACN,2FACA,EAAE,OAAO,EAAM,CACnB;EACJ;EAIA,MAAU,MAAM,yDAAyD;CAEjF;CAGA,IAAG,MAAgB,SAGf,OADA,EAAI,MAAM,2BAA2B,GAC9B,GAAkB,GAAQ,CAA+B;CAIpE,IAAG,MAAM,GAAsB,GAE3B,IACA;EAGI,OAFA,EAAI,MAAM,cAAc,GAEjB,MAAM,GAAmB,GAAQ,CAAO;CACnD,SACO,GACP;EACI,EAAI,KAAK,wDAAwD,CAAK;CAC1E;MAIA,EAAI,KAAK,8CAA8C;CAK3D,OAFA,EAAI,MAAM,aAAa,GAEhB,GAAkB,GAAQ,CAAwB;AAC7D;;;AC9HA,IAAa,KAAb,MACA;CACI,UAAkB;CAClB,UAAiC,CAAC;CAClC,aAAuC,CAAC;CACxC,aAAuC,CAAC;CAWxC,MAAM,GAAa,GACnB;EACI,IAAG,IAAK,GAEJ,MAAU,WAAW,iCAAkC,GAAK;EAGhE,IAAM,IAAK,KAAK;EAGhB,OAFA,KAAK,QAAQ,KAAK;GAAE;GAAI,WAAW;GAAI;EAAS,CAAC,SAGjD;GACI,KAAK,UAAU,KAAK,QAAQ,QAAQ,MAAU,EAAM,OAAO,CAAE;EACjE;CACJ;CAOA,SAAS,GAAa,GACtB;EACI,IAAG,KAAM,GAEL,MAAU,WAAW,mCAAoC,GAAK;EAGlE,IAAM,IAAK,KAAK;EAGhB,OAFA,KAAK,WAAW,KAAK;GAAE;GAAI,UAAU;GAAI,SAAS;GAAG;EAAS,CAAC,SAG/D;GACI,KAAK,aAAa,KAAK,WAAW,QAAQ,MAAU,EAAM,OAAO,CAAE;EACvE;CACJ;CAKA,SAAS,GACT;EAEI,IAAM,IAAwB;GAAE,IADrB,KAAK;GACoB,UAAU;GAAI,WAAW;EAAE;EAG/D,OAFA,KAAK,WAAW,KAAK,CAAK,GAEnB;GACH,IAAI,QACJ;IACI,OAAO,EAAM,aAAa;GAC9B;GACA,QACA;IACI,EAAM,YAAY,EAAM;GAC5B;EACJ;CACJ;CAKA,YACA;EAEI,AADA,KAAK,UAAU,CAAC,GAChB,KAAK,aAAa,CAAC;EAInB,KAAI,IAAM,KAAS,KAAK,YAEpB,EAAM,YAAY;CAE1B;CAKA,KAAK,GACL;EAEI,IAAM,IAAyB,CAAC;EAChC,KAAI,IAAM,KAAS,KAAK,SAGpB,AADA,EAAM,aAAa,GAChB,EAAM,aAAa,MAElB,EAAM,SAAS,GACf,EAAY,KAAK,EAAM,EAAE;EAIjC,AAAG,EAAY,SAAS,MAEpB,KAAK,UAAU,KAAK,QAAQ,QAAQ,MAAU,CAAC,EAAY,SAAS,EAAM,EAAE,CAAC;EAIjF,KAAI,IAAM,KAAS,KAAK,YAGpB,KADA,EAAM,WAAW,GACX,EAAM,WAAW,EAAM,WAGzB,AADA,EAAM,SAAS,GACf,EAAM,WAAW,EAAM;EAK/B,KAAI,IAAM,KAAS,KAAK,YAEpB,AAAG,EAAM,YAAY,MAEjB,EAAM,YAAY,KAAK,IAAI,GAAG,EAAM,YAAY,CAAI;CAGhE;AACJ;;;AC5JA,SAAS,EACL,GAEJ;CACI,IAAG,EAAG,eAAe,MACrB;EACI,QAAQ,KAAK,wFAAwF;EACrG;CACJ;CAEA,OAAO,EAAG;AACd;AAEA,SAAS,GACL,GACA,GAEJ;CAMI,OALG,OAAO,KAAe,WAEd,EAAkB,CAAE,CAAC,EAAE,UAAU,CAAU,IAG/C;AACX;AAIA,SAAgB,GACZ,GACA,GAEJ;CACI,IAAM,IAAK,EAAG,SAAS,aACjB,IAAK,EAAG,SAAS,cAEjB,IAAgC,CAAC;CAmMvC,AA7LA,EAAG,OAAO,UAAU,CAAE,GACtB,EAAG,OAAO,UAAU,EAAG,GAAG,GAG1B,EAAG,OAAO,WAAW,EAAE,WAAW,EAAG,cAAc,OAAO,iBAAiB,KAAK,KAAK,CAAC,GACtF,EAAG,OAAO,aAAa,EAAG,MAAM,SAAS,GAEzC,EAAG,OAAO,YAAY,IAAI,MAAM,CAAC,GAA8B,EAC3D,IAAI,GAAmC,GACvC;EACI,IAAG,OAAO,KAAS,UAEf;EAGJ,IAAM,IAAS,EAAkB,CAAE,CAAC,EAAE,UAAU,CAAI;EAMpD,OALI,KAEA,QAAQ,KAAK,4BAA6B,GAAO,GAG9C;CACX,EACJ,CAAC,CAAC,GAEF,EAAG,OAAO,SAAS,EAAE,WAAW,EAAG,aAAa,CAAC,GACjD,EAAG,OAAO,SAAS,EAAE,WAAW,EAAG,cAAc,SAAS,KAAK,CAAC,GAMhE,EAAG,OAAO,OAAO,GAAkC,GAAY,GAAY,MAC3E;EACI,IAAM,IAAS,GAAc,GAAY,CAAE;EAC3C,IAAG,CAAC,GACJ;GACI,QAAQ,KAAK,4BAA6B,GAAa;GACvD;EACJ;EAEA,IAAG,EAAE,aAAkB,IACvB;GACI,QAAQ,KAAK,kBAAmB,EAAO,GAAI,cAAc;GACzD;EACJ;EAIA,IAAM,IAAU,EAAO;EAIvB,AAFA,EAAQ,WAAW;GAAE;GAAG;GAAG;EAAE,GAE7B,QAAQ,IAAI,sBAAuB,EAAO,GAAI,QAAS,EAAG,IAAK,EAAG,IAAK,EAAG,EAAE;CAChF,CAAC,GAED,EAAG,OAAO,UAAU,GAAe,GAAY,GAAY,MAC3D;EACI,IAAM,IAAa,EAAkB,CAAE;EACvC,IAAG,CAAC,GAEA;EAIJ,IAAM,IAAS,EAAW,YAAY,GAAM,EAAE,UAAU;GAAE;GAAG;GAAG;EAAE,EAAE,CAAC;EAErE,OADA,QAAQ,IAAI,mBAAoB,EAAM,KAAM,EAAO,GAAI,QAAS,EAAG,IAAK,EAAG,IAAK,EAAG,EAAE,GAC9E;CACX,CAAC,GAED,EAAG,OAAO,SAAS,GAAgB,MACnC;EACI,IAAM,IAAa,EAAkB,CAAE;EACvC,IAAG,CAAC,GAEA,OAAO,CAAC;EAGZ,IAAI,IAAmC,EAAW;EAOlD,AALG,MAAS,KAAA,MAER,IAAW,EAAS,QAAQ,MAAQ,EAAI,SAAS,CAAI,IAGtD,MAAQ,KAAA,MAEP,IAAW,EAAS,QAAQ,MAAS,EAAI,MAA8B,KAAK,SAAS,CAAG,CAAC;EAG7F,IAAM,IAAO,EAAS,KAAK,MAC3B;GACI,IAAI,IAAW;GAEf,AAAG,aAAe,MAEd,IAAW,EAAI,KAAK;GAGxB,IAAM,IAAgC;IAClC,IAAI,EAAI;IACR,MAAM,EAAI;IACV,MAAM;GACV;GAOA,OALG,EAAI,YAAY,EAAa,MAE5B,EAAI,cAAc,EAAI,MAAM,cAGzB;EACX,CAAC;EAGD,OADA,QAAQ,MAAM,CAAI,GACX,CAAE,GAAG,CAAS;CACzB,CAAC,GAED,EAAG,OAAO,YAAY,MACtB;EACI,IAAM,IAAS,GAAc,GAAY,CAAE;EAC3C,IAAG,CAAC,GACJ;GACI,QAAQ,KAAK,4BAA6B,GAAa;GACvD;EACJ;EAEA,IAAI,IAA2B,MAC3B,IAA4B;EAEhC,AAAG,aAAkB,MAEjB,IAAW,EAAO,KAAK,MACvB,IAAW,EAAO,KAAK;EAG3B,IAAM,IAAO;GACT,IAAI,EAAO;GACX,OAAO,EAAO;GACd,MAAM;GACN;EACJ;EAGA,OADA,QAAQ,IAAI,uBAAuB,CAAI,GAChC;CACX,CAAC,GAED,EAAG,OAAO,cAAc,MACxB;EACI,IAAG,MAAY,KAAA,GAEX,MAAU,MAAM,kEAAgE;EAIpF,IAAM,IAAkB,GAElB,IAAQ,EAAG,IAAI,GAAG,IAAkB,MAC1C;GACI,QAAQ,IAAI,iBAAkB,EAAM,QAAS,EAAM,OAAO;EAC9D,CAAC;EAID,OAFA,EAAc,KAAK,CAAK,GACxB,QAAQ,IAAI,mCAAoC,EAAiB,EAAE,GAC5D;CACX,CAAC,GAED,EAAG,OAAO,qBACV;EACI,KAAI,IAAM,KAAS,GAEf,EAAM;EAIV,AADA,QAAQ,IAAI,kBAAmB,EAAc,OAAQ,iBAAiB,GACtE,EAAc,SAAS;CAC3B,CAAC,GAED,EAAG,OAAO,eACV;EAEI,AADA,EAAG,MAAM,eAAe,GACxB,QAAQ,IAAI,oBAAoB;CACpC,CAAC,GAED,EAAG,OAAO,gBACV;EAEI,AADA,EAAG,OAAO,eAAe,GACzB,QAAQ,IAAI,qBAAqB;CACrC,CAAC,GAED,EAAG,OAAO,SAAS,MACnB;EAEI,AADA,EAAG,YAAY,GACf,QAAQ,IAAI,4BAA6B,GAAS;CACtD,CAAC;AACL;;;AC1PA,IAAa,KAAY;CACrB;CACA;CACA;CACA;CACA;CACA;AACJ;;;ACkCA,SAAS,GACL,GACA,GAEJ;CACI,IAAM,IAAiB,KAAY,CAAC,GAC9B,IAAiB,EAAW,QAAQ,CAAC,GACrC,IAAgB,EAAc,QAAoC,CAAC;CAEzE,OAAO;EACH,GAAG,EAAW;EACd,GAAG;EACH,MAAM,CAAE,mBAAG,IAAI,IAAI,CAAE,GAAG,GAAgB,GAAG,CAAa,CAAC,CAAE;CAC/D;AACJ;AAIA,SAAgB,GACZ,GAEJ;CACI,MAAM,UAAsB,EAC5B;EACI,OAAyB,YAAY,EAAW;EAChD,OAAyB,aAAa,EAAW;EACjD,OAAyB,UAAU,EAAW;EAE9C,OAAgB,OAEZ,GACA,GACA,GACA,GAEJ;GACI,IAAM,IAAS,GAAgB,GAAY,CAAiD;GAE5F,OAAO,MAAM,OAAO,GAAI,GAAS,GAAgD,CAAI;EACzF;CACJ;CAMA,OAFA,OAAO,eAAe,GAAe,QAAQ,EAAE,OAAO,EAAW,KAAK,CAAC,GAEhE;AACX;;;ACrEA,SAAS,EAAS,GAClB;CACI,OAAO,IAAI,EAAO,EAAM,GAAG,EAAM,GAAG,EAAM,CAAC;AAC/C;AAEA,SAAS,GAAmB,GAAe,GAAiB,GAC5D;CACI,IAAM,IAAc,SAAU,EAAO,OAAO,CAAC,CAAC,CAAC,YAAY,IAAM,EAAO,MAAM,CAAC,KAEzE,IAAU,EAAS;CAEzB,IAAG,OAAO,KAAY,YAElB,MAAU,MAAM,yDAA0D,EAAQ,GAAG;CAGzF,OAAO,EAAQ,GAAQ,KAAU,CAAC,GAAG,CAAK;AAC9C;AAEA,SAAS,GAAc,GAAe,GAAa,GACnD;CACI,IAAG,EAAO,SAAS,OACnB;EACI,IAAM,IAAW,IAAI,GAAY,GAAI,EAAK,KAAM,YAAY,CAAK;EAsBjE,AApBG,EAAO,UAAU,KAAA,MAEhB,EAAS,cAAc,EAAS,EAAO,KAAK,IAG7C,EAAO,aAAa,KAAA,MAEnB,EAAS,gBAAgB,EAAS,EAAO,QAAQ,IAGlD,EAAO,aAAa,KAAA,MAEnB,EAAS,WAAW,EAAO,WAG5B,EAAO,cAAc,KAAA,MAEpB,EAAS,YAAY,EAAO,YAGhC,EAAK,WAAW;CACpB,OAEA;EACI,IAAM,IAAW,IAAI,EAAiB,GAAI,EAAK,KAAM,YAAY,CAAK;EAYtE,AAVG,EAAO,UAAU,KAAA,MAEhB,EAAS,eAAe,EAAS,EAAO,KAAK,IAG9C,EAAO,aAAa,KAAA,MAEnB,EAAS,gBAAgB,EAAS,EAAO,QAAQ,IAGrD,EAAK,WAAW;CACpB;AACJ;AAEA,eAAe,GAAU,GAAe,GAAqB,GAC7D;CACI,IAAI;CAmBJ,OAjBG,EAAO,OAAO,SAAS,GAAG,KAEzB,MAAM,EAAO,KAAK,EAAO,MAAM,GAC/B,IAAO,EAAO,MAAM,EAAO,QAAQ,CAAK,KAIxC,IAAO,GAAmB,GAAO,EAAO,QAAQ,EAAO,MAAM,GAGjE,EAAK,YAAY,EAAO,WAAW,IAEhC,EAAO,aAAa,KAAA,KAEnB,GAAc,GAAO,GAAM,EAAO,QAAQ,GAGvC;AACX;AAIA,SAAgB,GACZ,GACA,GAEJ;CACI,OAAO,OAAO,GAAO,MACrB;EAEI,IAAM,IADc,EAAc,EACnB,EAAa;EAEzB,UAAW,KAAA,GAKd,OAAO,GAAU,GAAO,GAAQ,CAAM;CAC1C;AACJ;;;ACzGA,IAAa,KAAb,cAAmC,EACnC;CACI,OAAgB,MAAM,CAAE,QAAQ,MAAO;CAEvC,WAAiC,EAAE,QAAQ,CAAC,EAAE;CAE9C,qBAAmB,IAAI,IAAyB;CAChD,KAAa;CAEb,eACA;EACI,IAAM,IAAS,KAAK,QACd,IAAQ,EAAO;EAElB,UAAU,KAAA,GAKb,KAAI,IAAM,CAAE,GAAM,MAAY,OAAO,QAAQ,KAAK,MAAM,MAAM,GAE1D,EAAM,YAAY,GAAM,EAAO,KAAK,EAAO,SAAS;GAChD,QAAQ,EAAO,UAAU;GACzB,MAAM,EAAO,QAAQ;EACzB,CAAC,CAAC,CAAC,MAAM,MACT;GACI,KAAK,GAAc,GAAM,GAAQ,GAAQ,CAAK;EAClD,CAAC;CAET;CAEA,YACA;EACI,KAAK,KAAa;EAElB,KAAI,IAAM,KAAS,KAAK,GAAQ,OAAO,GAGnC,AADA,EAAM,KAAK,GACX,EAAM,QAAQ;EAGlB,KAAK,GAAQ,MAAM;CACvB;CAEA,KAAK,GACL;EACI,KAAK,GAAM,CAAI,CAAC,EAAE,KAAK;CAC3B;CAEA,KAAK,GACL;EACI,KAAK,GAAM,CAAI,CAAC,EAAE,KAAK;CAC3B;CAGA,KACA;EACI,IAAG,KAAK,OAAO,UAAU,KAAA,GAErB,MAAU,MACN,0BAA2B,KAAK,OAAO,GAAI,gEAE/C;CAER;CAKA,GAAc,GAAe,GAAsB,GAAqB,GACxE;EACI,IAAG,KAAK,IACR;GACI,EAAM,QAAQ;GACd;EACJ;EAUA,CARG,EAAO,WAAW,QAEjB,EAAM,QAAQ,OAAO,EAAO,IAAI,GAChC,EAAM,QAAQ,cAAc,EAAO,eAAe,MAGtD,KAAK,GAAQ,IAAI,GAAM,CAAK,IAEzB,EAAO,QAAQ,OAEd,EAAM,KAAK;CAEnB;CAEA,GAAM,GACN;EAGI,IAFA,KAAK,GAAc,GAEhB,CAAC,OAAO,OAAO,KAAK,MAAM,QAAQ,CAAI,GAErC,MAAU,MAAM,kCAAmC,EAAM,eAAe;EAG5E,OAAO,KAAK,GAAQ,IAAI,CAAI;CAChC;AACJ;;;ACjDA,SAAS,GAAa,GACtB;CAqBI,OApBG,MAAU,WAEF,EAAiB,SAGzB,MAAU,aAEF,EAAiB,WAGzB,MAAU,WAEF,EAAiB,cAGzB,MAAU,SAEF,EAAiB,OAGrB,EAAiB;AAC5B;AAEA,SAAS,GAAmB,GAAmB,GAC/C;CACI,IAAM,EAAE,YAAS,cAAW;CAkB5B,OAhBG,MAAU,WAIF;EAAE,QAAQ,KAAK,IAAI,EAAQ,GAAG,EAAQ,GAAG,EAAQ,CAAC,IAAI;EAAG;CAAO,IAGxE,MAAU,aAEF;EACH,QAAQ,EAAQ,IAAI;EACpB,QAAQ,IAAI,EAAQ,EAAO,GAAG,EAAO,IAAK,EAAQ,IAAI,GAAI,EAAO,CAAC;EAClE,QAAQ,IAAI,EAAQ,EAAO,GAAG,EAAO,IAAK,EAAQ,IAAI,GAAI,EAAO,CAAC;CACtE,IAGG;EAAE;EAAS;CAAO;AAC7B;AAEA,SAAS,GAAe,GAAqB,GAC7C;CACI,IAAM,EAAE,eAAY;CAEpB,IAAG,EAAK,UAAU,UAEd,OAAO,IAAI,GAAmB,EAAQ,KAAK,GAAG,EAAQ,IAAI,GAAG,CAAK;CAGtE,IAAG,EAAK,UAAU,YAClB;EACI,IAAM,IAAS,IAAI,EAAQ,GAAG,CAAC,EAAQ,IAAI,GAAG,CAAC,GACzC,IAAS,IAAI,EAAQ,GAAG,EAAQ,IAAI,GAAG,CAAC;EAE9C,OAAO,IAAI,GAAqB,GAAQ,GAAQ,EAAQ,IAAI,GAAG,CAAK;CACxE;CAEA,OAAO,IAAI,GAAgB,EAAQ,KAAK,GAAG,EAAW,SAAS,GAAG,EAAU,CAAO,GAAG,CAAK;AAC/F;AAIA,SAAS,EAAc,GACvB;CAWI,OAVG,MAAW,WAEH,EAAkB,SAG1B,MAAW,cAEH,EAAkB,WAGtB,EAAkB;AAC7B;AAEA,SAAS,GAAU,GACnB;CAWI,OAVG,MAAe,EAAkB,SAEzB,WAGR,MAAe,EAAkB,WAEzB,cAGJ;AACX;AAIA,SAAS,GAAW,GACpB;CAMI,OALG,MAAW,cAEH,EAAmB,SAGvB,EAAmB;AAC9B;AAEA,SAAS,GAAY,GACrB;CACI,KAAI,IAAM,KAAS,EAAK,eAAe,EAAK,GAExC,IAAG,EAAM,iBAAiB,IAAI,GAE1B,OAAO;CAIf,OAAO;AACX;AAIA,IAAa,KAAb,cACY,EAEZ;CACI,OAAgB,MAAM,CAAE,cAAe;CAIvC,WAAuC;EACnC,OAAO;EACP,OAAO,CAAC;EACR,QAAQ;EACR,MAAM;EACN,UAAU;EACV,aAAa;EACb,UAAU;GAAE,GAAG;GAAG,GAAG;GAAG,GAAG;EAAE;EAC7B,UAAU;GAAE,GAAG;GAAG,GAAG;GAAG,GAAG;GAAG,GAAG;EAAE;EACnC,UAAU;GAAE,GAAG;GAAG,GAAG;GAAG,GAAG;EAAE;EAC7B,iBAAiB;GAAE,GAAG;GAAG,GAAG;GAAG,GAAG;EAAE;EACpC,WAAW;CACf;CAEA;CACA;CACA,KAAa;CAGb,KAAoC,EAAkB;CACtD,qBAAc,IAAI,IAA6D;CAI/E,KAAgC,CAAC;CAMjC,eACA;EACI,IAAM,IAAS,KAAK,QACd,IAAQ,EAAO,KAAK,SAAS,GAC7B,IAAS,EAAM,iBAAiB;EAEtC,IAAG,MAAW,MAEV,MAAU,MACN,qCAAsC,EAAO,GAAI,iFAErD;EAKJ,IAAG,EAAO,KAAK,aAEX,MAAU,MACN,qCAAsC,EAAO,GAAI,mJAErD;EAKJ,IAAI,EAAO,MAA0B,aAAa,KAAA,GAE9C,MAAU,MACN,qCAAsC,EAAO,GAAI,oFAErD;EAeJ,AAZA,KAAK,KAAU,EAAO,iBAAiB,GACvC,KAAK,KAAa,KAAK,GAAgB,GAAQ,CAAK,GAEpD,KAAK,GAAU,GAIZ,KAAK,MAAM,cAAc,MAExB,KAAK,GAAS,GAGlB,KAAK,GAAkB;CAC3B;CAEA,YACA;EAKI,AAFA,KAAK,IAAY,QAAQ,GACzB,KAAK,KAAa,KAAA,GAClB,KAAK,KAAU,KAAA;EAEf,KAAI,IAAM,KAAS,KAAK,IAEpB,EAAM,QAAQ;EAGlB,KAAK,KAAe,CAAC;CACzB;CASA,aAAa,GAAwB,GACrC;EACI,IAAG,KAAK,OAAe,KAAA,GAEnB;EAGJ,IAAM,IAAO,KAAK,GAAW,MAIzB,IAAQ,EAAK,qBAAqB;EAMtC,AALG,MAAa,KAAA,MAEZ,IAAQ,EAAU,CAAQ,IAG9B,EAAK,aAAa,EAAU,CAAO,GAAG,CAAK;CAC/C;CAOA,GAAgB,GAAqB,GACrC;EACI,IAAM,IAAO,EAAO,MACd,IAAQ,KAAK,MAAM,OAEnB,IAAO,KAAK,MAAM;EAExB,IAAG,MAAU,YAET,OAAO,IAAI,EAAiB,GAAM,KAAK,GAAe,CAAK,GAAG,EAAE,QAAK,GAAG,CAAK;EAGjF,IAAG,MAAU,YAAY,MAAU,QACnC;GACI,IAAM,IAAO,EAAO;GAEpB,IAAG,EAAE,aAAgB,MAAS,EAAK,iBAAiB,MAAM,GAEtD,MAAU,MACN,qCAAsC,EAAO,GAAI,iBAAkB,EAAO,mEAE9E;GAGJ,OAAO,IAAI,EAAiB,GAAM,GAAa,CAAK,GAAG;IAAE;IAAM;GAAK,GAAG,CAAK;EAChF;EAEA,IAAM,IAAa,GAAmB,GAAO,KAAK,GAAS,CAAM,CAAC;EAElE,OAAO,IAAI,EAAiB,GAAM,GAAa,CAAK,GAAG;GAAE;GAAM,GAAG;EAAW,GAAG,CAAK;CACzF;CAEA,GAAS,GACT;EACI,IAAM,IAAO,EAAO;EAIpB,IAAG,MAAS,KAAA,KAAa,EAAK,iBAAiB,IAAI,GACnD;GACI,IAAM,IAAM,EAAK,gBAAgB,CAAC,CAAC;GAEnC,OAAO;IAAE,SAAS,EAAI,WAAW,MAAM,CAAC;IAAG,QAAQ,EAAI,OAAO,MAAM;GAAE;EAC1E;EAKA,IAAG,CAAC,GAAY,EAAO,IAAI,GAEvB,MAAU,MACN,qCAAsC,EAAO,GAAI,sEAErD;EAGJ,OAAO,EAAuB,EAAO,IAAI;CAC7C;CAKA,GAAe,GACf;EACI,IAAM,IAAY,IAAI,GAAsB,CAAK;EAEjD,KAAI,IAAM,KAAQ,KAAK,MAAM,OAC7B;GACI,IAAM,IAAQ,GAAe,GAAM,CAAK;GAGxC,AADA,EAAU,SAAS,GAAO,EAAU,EAAK,QAAQ,GAAG,EAAa,EAAK,QAAQ,CAAC,GAC/E,KAAK,GAAa,KAAK,CAAK;EAChC;EAIA,OAFA,KAAK,GAAa,KAAK,CAAS,GAEzB;CACX;CAKA,KACA;EACI,IAAM,IAAO,KAAK,GAAM;EAMxB,AAJA,EAAK,cAAc,EAAc,KAAK,MAAM,MAAM,CAAC,GACnD,EAAK,kBAAkB,EAAU,KAAK,MAAM,QAAQ,CAAC,GACrD,EAAK,mBAAmB,EAAU,KAAK,MAAM,eAAe,CAAC,GAE7D,KAAK,GAAe;GAAE,UAAU,KAAK,MAAM;GAAU,aAAa,KAAK,MAAM;EAAY,CAAC;CAC9F;CAQA,KACA;EAII,IAAM,KAAiB,MAAgC;GAAE,KAAK,GAAgB,GAAO,KAAK,MAAM,QAAQ;EAAG,GACrG,KAAiB,MAAmC;GAAE,KAAK,GAAgB,KAAK,MAAM,UAAU,CAAK;EAAG,GACxG,KAAiB,MAAgC;GAAE,KAAK,GAAM,CAAC,CAAC,kBAAkB,EAAU,CAAK,CAAC;EAAG,GAErG,KAAwB,MAC9B;GACI,KAAK,GAAM,CAAC,CAAC,mBAAmB,EAAU,CAAK,CAAC;EACpD,GAEM,IAAW,QAAiB,KAAK,GAAM,CAAC,CAAC,cAAc,UAAU,CAAa,GAC9E,IAAW,SAAqB,KAAK,GAAU,GAAG,CAAa,GAC/D,IAAW,QAAiB,KAAK,GAAM,CAAC,CAAC,kBAAkB,GAAG,CAAa,GAC3E,IAAkB,QAAiB,KAAK,GAAM,CAAC,CAAC,mBAAmB,GAAG,CAAoB;EAiDhG,AA/CA,KAAK,SAAS,YAAY;GAAE,WAAW;GAAU,KAAK;EAAc,CAAC,GAErE,KAAK,SAAS,YAAY;GAAE,WAAW;GAAU,KAAK;EAAc,CAAC,GAErE,KAAK,SAAS,YAAY;GAAE,WAAW;GAAU,KAAK;EAAc,CAAC,GAErE,KAAK,SAAS,mBAAmB;GAAE,WAAW;GAAiB,KAAK;EAAqB,CAAC,GAI1F,KAAK,SAAS,UAAU;GACpB,WAEO,KAAK,KAEG,GAAU,KAAK,EAAa,IAGhC,GAAU,KAAK,GAAM,CAAC,CAAC,cAAc,CAAC;GAEjD,MAAM,MACN;IACI,IAAG,KAAK,IACR;KACI,KAAK,KAAgB,EAAc,CAAK;KACxC;IACJ;IAEA,KAAK,GAAM,CAAC,CAAC,cAAc,EAAc,CAAK,CAAC;GACnD;EACJ,CAAC,GAED,KAAK,SAAS,aAAa;GACvB,WAAW,KAAK;GAChB,MAAM,MAAU;IAAE,KAAK,GAAgB,CAAK;GAAG;EACnD,CAAC,GAED,KAAK,SAAS,QAAQ;GAClB,WAAW,KAAK,GAAM,CAAC,CAAC,kBAAkB,CAAC,CAAC,QAAQ;GACpD,MAAM,MAAU;IAAE,KAAK,GAAM,CAAC,CAAC,kBAAkB,EAAE,MAAM,EAAM,CAAC;GAAG;EACvE,CAAC,GAED,KAAK,SAAS,YAAY;GACtB,WAAW,KAAK,GAAU,CAAC,CAAC,YAAY;GACxC,MAAM,MAAU;IAAE,KAAK,GAAe;KAAE,UAAU;KAAO,aAAa,KAAK,MAAM;IAAY,CAAC;GAAG;EACrG,CAAC,GAED,KAAK,SAAS,eAAe;GACzB,WAAW,KAAK,GAAU,CAAC,CAAC,eAAe;GAC3C,MAAM,MAAU;IAAE,KAAK,GAAe;KAAE,UAAU,KAAK,MAAM;KAAU,aAAa;IAAM,CAAC;GAAG;EAClG,CAAC;CACL;CAEA,GAAgB,GAAyB,GACzC;EACI,IAAM,IAAO,KAAK,GAAM,GAClB,IAAO,EAAK;EAElB,EAAK,WAAW,EAAU,CAAQ,GAClC,EAAK,qBAAqB,EAAa,CAAQ,GAI5C,MAAK,OAOR,EAAK,eAAe,GAAW,GAAU,EAAK,cAAc,CAAC,CAAC,CAAC,GAC/D,KAAK,GAAO,CAAC,CAAC,6BAA6B,GAAM,CAAI,GACrD,EAAK,eAAe,EAAmB,QAAQ;CACnD;CAEA,GAAgB,GAChB;EACO,UAAU,KAAK,IAKlB;OAAG,GACH;IACI,KAAK,GAAS;IACd;GACJ;GAEA,KAAK,GAAQ;EAFb;CAGJ;CAIA,KACA;EACI,IAAM,IAAO,KAAK,GAAM;EAGxB,AADA,KAAK,KAAgB,EAAK,cAAc,GACxC,EAAK,cAAc,EAAkB,MAAM;EAE3C,KAAI,IAAM,KAAS,KAAK,GAAQ,GAI5B,AAFA,KAAK,GAAY,IAAI,GAAO;GAAE,YAAY,EAAM;GAAsB,SAAS,EAAM;EAAkB,CAAC,GACxG,EAAM,uBAAuB,GAC7B,EAAM,oBAAoB;EAG9B,KAAK,KAAa;CACtB;CAIA,KACA;EACI,IAAM,IAAO,KAAK,GAAM;EAExB,KAAI,IAAM,KAAS,KAAK,GAAQ,GAChC;GACI,IAAM,IAAQ,KAAK,GAAY,IAAI,CAAK;GAExC,AAAG,MAAU,KAAA,MAET,EAAM,uBAAuB,EAAM,YACnC,EAAM,oBAAoB,EAAM;EAExC;EAUA,AARA,KAAK,GAAY,MAAM,GACvB,KAAK,KAAa,IAElB,EAAK,cAAc,KAAK,EAAa,GACrC,EAAK,eAAe,EAAmB,QAAQ,GAC/C,KAAK,GAAO,CAAC,CAAC,6BAA6B,GAAM,EAAK,aAAa,GACnE,EAAK,eAAe,EAAmB,QAAQ,GAC/C,EAAK,kBAAkB,EAAQ,KAAK,CAAC,GACrC,EAAK,mBAAmB,EAAQ,KAAK,CAAC;CAC1C;CAGA,KACA;EACI,IAAM,IAA0B,CAAE,GAAG,KAAK,EAAa,GACjD,IAAM,KAAK,GAAM,CAAC,CAAC;EAOzB,OALG,MAAQ,QAAQ,CAAC,EAAO,SAAS,CAAG,KAEnC,EAAO,KAAK,CAAG,GAGZ;CACX;CAGA,GAAe,GACf;EACI,IAAM,IAAQ,KAAK,GAAM,CAAC,CAAC;EAE3B,AAAG,MAAU,SAET,EAAM,WAAW;EAGrB,KAAI,IAAM,KAAS,KAAK,IAEpB,EAAM,WAAW;CAEzB;CAEA,KACA;EACI,IAAM,IAAW,KAAK,GAAM,CAAC,CAAC,cAAc;EAO5C,OALG,MAAa,OAEL,EAAW,SAAS,IAGxB;CACX;CAEA,KACA;EACI,IAAM,IAAQ,KAAK,GAAM,CAAC,CAAC;EAO3B,OALG,MAAU,OAEF,CAAC,IAGL,EAAM;CACjB;CAEA,KACA;EACI,IAAG,KAAK,OAAe,KAAA,GAEnB,MAAU,MACN,qCAAsC,KAAK,OAAO,GAAI,qCAC1D;EAGJ,OAAO,KAAK,GAAW;CAC3B;CAEA,KACA;EACI,IAAG,KAAK,OAAY,KAAA,GAEhB,MAAU,MACN,qCAAsC,KAAK,OAAO,GAAI,uCAC1D;EAGJ,OAAO,KAAK;CAChB;AACJ,GCxlBM,KAAO,IAAI,EAAQ,GAAG,IAAI,CAAC,GAI3B,KAAe;AAIrB,SAAS,GAAoB,GAC7B;CACI,IAAM,IAAQ,OAAO,KAAK,CAAO;CAIjC,OAFA,EAAM,MAAM,GAAM,MAAU,EAAQ,EAAM,CAAC,SAAS,EAAQ,EAAK,CAAC,MAAM,GAEjE;AACX;AAIA,IAAa,KAAb,cACY,EAEZ;CACI,WAA+C;EAC3C,OAAO;EACP,OAAO;EACP,WAAW;EACX,aAAa;EACb,aAAa;EACb,SAAS;GACL,UAAU;IAAE,QAAQ;IAAK,QAAQ;IAAK,OAAO;GAAE;GAC/C,WAAW;IAAE,QAAQ;IAAK,QAAQ;IAAK,OAAO;GAAI;EACtD;EACA,gBAAgB;EAChB,aAAa;EACb,WAAW;EACX,iBAAiB;EACjB,aAAa;EACb,UAAU;GAAE,GAAG;GAAG,GAAG;GAAG,GAAG;EAAE;EAC7B,UAAU;GAAE,GAAG;GAAG,GAAG;GAAG,GAAG;EAAE;EAC7B,UAAU;EACV,QAAQ;CACZ;CAEA;CACA;CAMA;CAEA;CACA,KAAe;CAEf,qBAAgB,IAAI,IAA+E;CAEnG,KAAW,EAAe;CAI1B,KAAW;CAEX,KAAU;CACV,KAAY;CAIZ;CAIA,KAAW,EAAQ,KAAK;CAIxB,KAAe;CACf,KAAe;CAEf,KAAc;CACd;CAIA,KAAU;CAMV,WACA;EACI,KAAK,KAAW;CACpB;CAEA,eACA;EACI,IAAM,IAAS,KAAK,QACd,IAAQ,EAAO,KAAK,SAAS,GAC7B,IAAS,EAAM,iBAAiB;EAEtC,IAAG,MAAW,MAEV,MAAU,MACN,wCAAyC,EAAO,GAAI,yFAExD;EAeJ,AAZG,KAAK,MAAY,KAAK,MAAM,kBAE3B,KAAK,GAAgB,CAAM,GAG/B,KAAK,KAAU,EAAO,iBAAiB,GACvC,KAAK,KAAW,EAAO,QAAQ,MAAM,GACrC,KAAK,KAAU,KAAK,GAAS,GAE7B,KAAK,GAAa,KAAK,EAAO,GAE9B,KAAK,GAAiB,GAAQ,CAAK,GACnC,KAAK,GAAgB;CACzB;CAEA,SAAS,GACT;EACI,IAAM,IAAa,KAAK;EAExB,IAAG,MAAe,KAAA,GAEd;EAGJ,IAAM,IAAU,EAAW,aAAa,GAAU,EAAI;EAkBtD,AAhBA,KAAK,KAAY,EAAQ,mBAAmB,EAAwB,WAEjE,KAAK,KAEJ,KAAK,KAAuB,IAI5B,KAAK,MAAwB,GAKjC,KAAK,GAAe,GACpB,KAAK,KAAU,KAAK,GAAY,GAE7B,KAAK,GAAa,CAAC,CAAC,KAAK,MAExB,KAAK,KAAU;EAGnB,IAAM,IAAW,KAAK,gBAAgB,GAAU,CAAO;EAqBvD,AAhBG,KAAK,GAAY,CAAO,MAEvB,KAAK,KAAU,IACf,EAAS,IAAI,KAAK,MAAM,YAG5B,KAAK,KAAW,KAAK,GAAiB,GACtC,EAAS,WAAW,KAAK,EAAQ,GAEjC,EAAW,YAAY,CAAQ,GAC/B,EAAW,UAAU,GAAU,GAAS,KAAK,EAAQ,GAKrD,KAAK,OAAO,KAAK,oBAAoB,KAAK,GAAQ,EAAW,YAAY,CAAC,CAAC,GAC3E,KAAK,MAAM,WAAW,EAAa,EAAW,YAAY,CAAC;CAC/D;CAEA,YACA;EAOI,AALA,KAAK,IAAa,QAAQ,GAC1B,KAAK,KAAc,KAAA,GACnB,KAAK,KAAe,KAAA,GAEpB,KAAK,IAAQ,QAAQ,GACrB,KAAK,KAAS,KAAA;EAEd,KAAI,IAAM,KAAQ,KAAK,GAAc,OAAO,GAExC,EAAK,MAAM,QAAQ;EAMvB,AAHA,KAAK,GAAc,MAAM,GAEzB,KAAK,KAAU,KAAA,GACf,KAAK,KAAU,KAAA;CACnB;CAWA,gBAA0B,GAAmB,GAC7C;EACI,IAAM,IAAY,EAAQ,mBAAmB,EAAwB,WAC/D,IAAW,KAAK,GAAsB;EAW5C,OATG,KAAa,CAAC,KAAK,MAElB,EAAS,IAAI,GAEN,MAGX,EAAS,IAAI,KAAK,GAAa,CAAC,CAAC,IAAK,KAAK,GAAS,IAAI,GAEjD;CACX;CAMA,WAAqB,GACrB;EACI,IAAM,IAAS,KAAK,MAAM,QAAQ;EASlC,OAPG,MAAW,KAAA,KAOP,CAAC,KAAK,GAAiB,GAAQ,CAAM;CAChD;CAMA,GAAY,GACZ;EAMI,OALG,MAAW,KAAA,KAKP,EAAO,UAAU,EAAO,SAAS;CAC5C;CAKA,GAAa,GACb;EACI,IAAM,IAAS,KAAK,MAAM,QAAQ;EAE/B,YAAW,KAAA,KAAa,KAAK,GAAY,CAAM,IAKlD,MAAU,MACN,wCAAyC,KAAK,OAAO,GAAI,yBAA0B,EAAQ,mBAC3E,EAAO,OAAQ,mBAAoB,EAAO,OAAQ,+DAEtE;CACJ;CAEA,GAAiB,GAAqB,GACtC;EACI,IAAM,IAAS,KAAK,GAAe,GAC7B,IAAO,KAAK,GAAW,CAAM,GAC7B,IAAS,IAAI,EAAQ,EAAK,GAAG,EAAK,IAAK,IAAS,GAAI,EAAK,CAAC,GAK1D,IAAS,IAAI,IAAI,EAAM,cAAc;EAS3C,AAPA,KAAK,KAAc,IAAI,GACnB,GACA;GAAE,eAAe;GAAQ,eAAe,KAAK,GAAe;EAAE,GAC9D,CACJ,GAEA,KAAK,KAAe,GACpB,KAAK,KAAe,KAAK,GAAe;EAExC,KAAI,IAAM,KAAQ,EAAM,gBAEpB,AAAG,CAAC,EAAO,IAAI,CAAI,KAAK,EAAK,gBAAgB,SAEzC,KAAK,KAAe,EAAK;CAGrC;CAIA,GAAW,GACX;EACI,OAAO,EAAO,KAAK,oBAAoB,CAAC,CAAC,MAAM;CACnD;CAEA,GAAgB,GAChB;EACI,IAAM,IAAO,EAAO;EAEpB,IAAG,MAAS,KAAA,KAAa,EAAK,iBAAiB,MAAM,GAEjD,MAAU,MACN,wCAAyC,EAAO,GAAI,wEAExD;EAGJ,IAAM,IAAO,EAAK,gBAAgB,CAAC,CAAC,YAAY,YAE1C,IAAU,KAAK,GAAc,KAAK,GAAS,CAAC;EAKlD,AAHA,EAAQ,SAAS,EAAK,IAAI,GAG1B,EAAQ,SAAS,KAAK,IAAI,EAAK,GAAG,EAAK,CAAC;CAC5C;CAMA,KACA;EACI,KAAK,GAAa,KAAK,MAAM,WAAW;EAExC,IAAI,IAAO,KAAK;EAEhB,IAAG,KAAK,WAAW,KAAK,MAAM,WAAW,GAErC,IAAO,KAAK,MAAM;OAIlB,KAAI,IAAM,KAAQ,GAAoB,KAAK,MAAM,OAAO,GAEpD,IAAG,KAAK,GAAY,KAAK,MAAM,QAAQ,EAAK,KAAK,KAAK,WAAW,CAAI,GACrE;GACI,IAAO;GACP;EACJ;EAIR,KAAK,KAAU;EAEf,IAAM,IAAS,KAAK,GAAc,CAAI;EAInC,GAAO,WAAW,KAAK,MAAgB,EAAO,WAAW,KAAK,QAKjE,KAAK,GAAa,CAAI,GAGtB,KAAK,GAAW,CAAC,CAAC,gBAAgB;GAAE,eAAe,EAAO;GAAQ,eAAe,EAAO;EAAO,GAAG,EAAI,GAEtG,KAAK,KAAe,EAAO,QAC3B,KAAK,KAAe,EAAO;CAC/B;CAEA,KACA;EACI,IAAM,IAAQ,IAAI,EAAQ,KAAK,MAAM,OAAO,GAAG,KAAK,MAAM,KAAK;EAE/D,AAAG,EAAM,cAAc,IAAI,KAEvB,EAAM,UAAU;EAGpB,IAAI,IAAQ,KAAK,MAAM,QAAQ,KAAK,GAAQ,CAAC;EAO7C,AALG,KAAK,MAAM,gBAEV,KAAgB,KAAK,MAAM,cAG/B,EAAM,aAAa,CAAK;EAGxB,IAAM,IAAS,KAAK,OAAO,KAAK,sBAAsB,EAAW,SAAS;EAE1E,OAAO,EAAM,wBAAwB,GAAQ,IAAI,EAAQ,CAAC;CAC9D;CAGA,GAAY,GACZ;EA0BI,OAzBG,KAAK,MAAM,cAKV,KAAK,KAAc,KAAK,IAAI,KAAK,MAAM,iBAAiB,CAAC,IAG1D,KAAK,MAAe,IAEZ,KAGP,KAAK,GAAa,CAAO,KAO7B,KAAK,KAAc,GAGnB,KAAK,kBAEE,OAVH,OAAK,IAEE;CASf;CAEA,GAAa,GACb;EAaI,OAVG,EAAQ,mBAAmB,EAAwB,WAKnD,KAAK,OAAY,KAAK,GAAS,IAEvB,KAGJ,KAAK,MAAa,KAAK,MAAwB,KAAK,MAAM;CACrE;CAEA,KACA;EACI,OAAO,KAAK,GAAW,CAAC,CAAC,YAAY,CAAC,CACjC,SAAS,KAAK,EAAQ;CAC/B;CAEA,KACA;EACI,IAAM,IAAS,KAAK;EAOpB,OALG,MAAW,KAAA,IAEH,EAAQ,KAAK,IAGjB,EAAO,kBAAkB;CACpC;CASA,KACA;EACI,IAAG,CAAC,KAAK,IAEL;EAGJ,IAAM,IAAO,KAAK,GAAM,GAClB,IAAK,IAAI,EAAQ,EAAK,GAAG,EAAK,IAAI,KAAK,GAAY,GAAG,EAAK,CAAC,GAC5D,IAAU,KAAK,GAAW,GAAM,CAAE,GAClC,IAAa,KAAK;EAcxB,OAZG,MAAe,KAAA,KAAa,MAAe,KAAW,EAAW,aAEzD,IAKR,MAAY,KAAA,KAAa,KAAK,GAAW,GAAM,GAAI,CAAO,MAAM,IAExD,IAGJ;CACX;CAIA,GAAiB,GAAiB,GAClC;EACI,IAAM,IAAS,KAAK,GAAO,GACrB,IAAO,KAAK,GAAM,GAClB,IAAQ,IAAI,GAAoB,GAChC,IAAM,IAAI,GAAoB;EAepC,OAbA,EAAO,eACH;GACI,OAAO,KAAK,GAAa,GAAQ,CAAM;GACvC,UAAU,IAAI,EAAQ,EAAK,GAAG,EAAK,IAAK,EAAO,SAAS,GAAI,EAAK,CAAC;GAClE,UAAU,EAAW,SAAS;GAC9B,aAAa;GACb,mBAAmB;GACnB,YAAY,KAAK;EACrB,GACA,GACA,CACJ,GAEO,EAAI;CACf;CAEA,GAAa,GAAiB,GAC9B;EACI,IAAM,IAAO,KAAK,GAAc,IAAI,CAAM;EAE1C,IAAG,MAAS,KAAA,KAAa,EAAK,WAAW,EAAO,UAAU,EAAK,WAAW,EAAO,QAE7E,OAAO,EAAK;EAGhB,GAAM,MAAM,QAAQ;EAEpB,IAAM,IAAQ,EAAO,SAAS,IAAK,EAAO,QACpC,IAAQ,IAAI,GACd,IAAI,EAAQ,GAAG,GAAM,CAAC,GACtB,IAAI,EAAQ,GAAG,CAAC,GAAM,CAAC,GACvB,EAAO,QACP,KAAK,OAAO,KAAK,SAAS,CAC9B;EAIA,OAFA,KAAK,GAAc,IAAI,GAAQ;GAAE;GAAO,QAAQ,EAAO;GAAQ,QAAQ,EAAO;EAAO,CAAC,GAE/E;CACX;CAIA,KACA;EACI,IAAM,IAAa,KAAK,GAAW;EAEnC,OAAO,EAAW,eAAe,EAAW;CAChD;CAEA,GAAW,GAAgB,GAAc,GACzC;EACI,IAAM,IAAS,KAAK,GAAO,GAErB,IAAQ,IAAI,GAAgB,GAC5B,IAAM,IAAI,GAAgB;EAEhC,MAAO,UACH;GACI,OAAO,KAAK,GAAY,KAAK,GAAe,CAAC;GAC7C,UAAU,EAAW,SAAS;GAC9B,eAAe;GACf,aAAa;GACb,mBAAmB;GACnB,YAAY;EAChB,GACA,GACA,CACJ,GAIG,GAAC,EAAI,UAAU,EAAI,SAAS,KAAA,KAAa,EAAI,SAAS,KAAK,KAK9D,OAAO,EAAI;CACf;CAEA,GAAY,GACZ;EAYI,QAXG,KAAK,OAAW,KAAA,KAAa,KAAK,OAAiB,OAElD,KAAK,IAAQ,QAAQ,GACrB,KAAK,KAAS,IAAI,GACd,EAAQ,KAAK,GACb,GACA,KAAK,OAAO,KAAK,SAAS,CAC9B,GACA,KAAK,KAAe,IAGjB,KAAK;CAChB;CAaA,KACA;EAMI,AALA,KAAK,SAAS,YAAY;GACtB,WAAW,KAAK;GAChB,WAAW,CAA+B;EAC9C,CAAC,GAED,KAAK,SAAS,UAAU;GACpB,WAAW,KAAK;GAChB,WAAW,CAA+B;EAC9C,CAAC;CACL;CAMA,KACA;EACI,OAAO,KAAK,GAAc,KAAK,EAAO,CAAC,CAAC;CAC5C;CAEA,KACA;EACI,OAAO,KAAK,GAAc,KAAK,EAAO,CAAC,CAAC;CAC5C;CAGA,GAAc,GACd;EACI,IAAM,IAAS,KAAK,MAAM,QAAQ;EAElC,IAAG,MAAW,KAAA,GAEV,MAAU,MACN,wCAAyC,KAAK,OAAO,GAAI,sBAAuB,EAAQ,uDAE5F;EAGJ,OAAO;CACX;CAEA,KACA;EACI,IAAM,IAAQ,GAAoB,KAAK,MAAM,OAAO;EAEpD,IAAG,EAAM,WAAW,GAEhB,MAAU,MACN,wCAAyC,KAAK,OAAO,GAAI,6DAE7D;EAGJ,OAAO,EAAM;CACjB;CAEA,KACA;EACI,OAAO,KAAK,GAAQ,KAAK,GAAW,CAAC,CAAC,YAAY,CAAC;CACvD;CAEA,GAAQ,GACR;EACI,OAAO,IAAI,EAAQ,EAAO,GAAG,EAAO,IAAI,KAAK,GAAW,CAAC,CAAC,YAAY,EAAO,CAAC;CAClF;CAKA,KACA;EACI,IAAG,KAAK,OAAY,KAAA,GAEhB,MAAU,MACN,wCAAyC,KAAK,OAAO,GAAI,gCAC7D;EAGJ,OAAO,KAAK;CAChB;CAEA,KACA;EACI,IAAG,KAAK,OAAgB,KAAA,GAEpB,MAAU,MACN,wCAAyC,KAAK,OAAO,GAAI,gCAC7D;EAGJ,OAAO,KAAK;CAChB;AACJ,GCjwBa,KAAb,MACA;CACI;CACA;CAEA;CACA;CAEA,YAAY,IAA2B,CAAC,GACxC;EAII,AAHA,KAAK,KAAe,EAAQ,aAC5B,KAAK,KAAe,EAAQ,aAE5B,EAAQ,QAAQ,iBAAiB,eACjC;GAGI,AAFA,KAAK,KAAc,EAAQ,QAAQ,UAAU,gBAAI,MAAM,0BAA0B,CAAC,GAClF,KAAK,KAAe,KAAA,GACpB,KAAK,KAAc,KAAA;EACvB,CAAC;CACL;CAGA,KAAK,GACL;EACO,KAAK,OAAiB,KAAA,KAKrB,KAAK,GAAc,EAAU,KAAK,MAKnC,KAAK,OAAiB,KAAA,KAAa,CAAC,KAAK,GAAa,SAAS,EAAU,OAAO,IAAI,KAKpF,KAAK,OAAiB,KAAA,KAAa,CAAC,KAAK,GAAa,SAAS,EAAU,UAAU,MAKtF,KAAK,GAAa;GACd,QAAQ,EAAU;GAClB,YAAY,EAAU;GACtB,WAAW,EAAU;EACzB,CAAC,GAED,KAAK,KAAe,KAAA,GACpB,KAAK,KAAc,KAAA;CACvB;CAEA,OACA;EACI,OAAO,IAAI,SAAS,GAAS,MAC7B;GAEI,AADA,KAAK,KAAe,GACpB,KAAK,KAAc;EACvB,CAAC;CACL;CAEA,GAAc,GACd;EAWI,OAVG,MAAU,MAKV,OAAO,KAAU,YAAY,MAAU;CAM9C;AACJ;;;AC3EA,SAAgB,GACZ,GACA,GACA,GAEJ;CACI,OAAO;EACH,SAAS,CAAE,GAAG,CAAQ;EACtB,UAAU,EAAS,KAAK,MAAY,EAAQ,aAAa,CAAC;EAC1D,UAAU,CAAE,GAAG,CAAS;CAC5B;AACJ;AAIA,SAAgB,GACZ,GACA,GACA,GAEJ;CACI,KAAI,IAAM,KAAU,EAAO,SAEvB,EAAQ,SAAS,CAAM;CAG3B,KAAI,IAAM,KAAW,EAAO,UAExB,EAAS,gBAAgB,EAAQ,MAAM,EAAQ,SAAS;CAG5D,OAAO,CAAE,GAAG,EAAO,QAAS;AAChC;;;ACLA,eAAsB,GAClB,GACA,GACA,IAAwB,CAAC,GAE7B;CAEI,IAAM,IAAS,IAAI,EAAe,EAAQ,YAAY,OAAO,GACvD,IAAe,EAAO,UAAU,MAAM;CAK5C,AAHA,EAAa,KAAK,kCAAmC,EAAS,IAAI,GAGlE,EAAa,MAAM,8BAA8B;CASjD,IAAM,IAAS,MAAM,GAAa,GAAQ;EANtC,WAAW;EACX,oBAAoB;EACpB,mBAAmB;EACnB,GAAG,EAAQ;CAG2B,GAAe,EAAQ,uBAAuB,IAAO,CAAY;CAG3G,EAAa,MAAM,uBAAuB;CAC1C,IAAM,IAAM,IAAI,GAAS;CAGzB,EAAa,MAAM,0BAA0B;CAC7C,IAAM,IAAc,IAAI,GAAY,GAAQ,GAAQ,EAAQ,WAAW;CAGvE,EAAa,MAAM,sBAAsB;CACzC,IAAM,IAAe,IAAI,GAAa,GAAK,GAAa,CAAM,GACxD,IAAe,IAAI,GAAuB,GAAK,CAAM,GACrD,IAAc,IAAI,GAAgC,GAAc,CAAO,GACvE,IAAc,IAAI,GAAsB,GAAQ,GAAK,GAAc,CAAM,GACzE,IAAe,IAAI,GAAa,CAAM;CAC5C,EAAY,gBAAgB,CAAY;CAGxC,IAAI,GACA;CAEJ,AAAG,EAAQ,iBAAiB,EAAQ,cAAc,SAAS,MAEvD,EAAa,MAAM,0BAA0B,GAC7C,IAAc,IAAI,GAAY,CAAM,GACpC,MAAM,EAAY,WAAW,GAE7B,EAAa,MAAM,2BAA2B,GAC9C,IAAe,IAAI,GAAa,GAAa,CAAM,GACnD,MAAM,EAAa,WAAW,EAAQ,aAAa;CAKvD,IAAM,IAAoB,EAAQ,qBAAqB,IAAI,IACrD,IAAsB,YAAY,KAAA;CAExC,EAAa,MAAM,oCAAoC;CACvD,IAAM,IAAuB,IAAI,GAAqB,GAEhD,MACF,GACA,GACA,MACC,IAAI,GAAyB;EAC9B,IAAI;EACJ;EACA,mBAAmB,GAAc,qBAAqB;EACtD;EACA;EACA,MAAM,IAAI,GAAS;EACnB,UAAU,GAAc,cAAc;EAEtC,OAAO;EACP,WAAW;EACX,QAAQ,EAAO,UAAU,gBAAgB;CAC7C,CAAC,GAGG,IAAqC;CAEzC,IAAG,EAAQ,UAAU,IACrB;EACI,EAAa,MAAM,2BAA2B;EAC9C,IAAI,IAAgB;EAOpB,AALG,OAAO,EAAQ,SAAU,YAAY,EAAQ,MAAM,cAAc,KAAA,MAEhE,IAAgB,EAAQ,MAAM,YAGlC,IAAe,IAAI,GAAa,CAAa;CACjD;CAGA,EAAa,MAAM,wBAAwB;CAC3C,IAAM,IAAQ,IAAI,GAAU;CAG5B,AAFA,EAAY,uBAAuB,MAAO,EAAM,KAAK,IAAK,GAAI,CAAC,GAE/D,EAAa,KAAK,qBAAsB,EAAS,2BAA2B;CAE5E,IAAM,IAAK,IAAI,GACX,GACA,GACA,GACA,GACA,GACA,IAGA;EACI;EACA;CACJ,GAGA;EACI;EACA;EACA;EACA;EACA;EACA;CACJ,GAEA,EAAQ,uBAAuB,IAG/B;EACI,WAAW;EACX,SAAS;EACT,SAAS,GAAe,MACxB;GACI,GAAc,OAAO,GAAM,CAAK;EACpC;CACJ,CACJ;CAeA,OAVA,EAAa,eAAe,CAAE,GAC9B,EAAY,eAAe,CAAE,GAC7B,EAAY,eAAe,CAAE,GAG1B,KAEC,GAAiB,GAAc,CAAE,GAG9B;AACX"}