@hology/core 0.0.263 → 0.0.265

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.
Files changed (43) hide show
  1. package/dist/effects/sequence/sequence-player.d.ts +1 -0
  2. package/dist/effects/sequence/sequence-player.js +1 -1
  3. package/dist/effects/vfx/gpu-sprite-runtime.js +1 -1
  4. package/dist/effects/vfx/vfx-actor.js +1 -1
  5. package/dist/gameplay/ai/behavior-tree/behavior-tree.d.ts +26 -0
  6. package/dist/gameplay/ai/behavior-tree/behavior-tree.js +4 -0
  7. package/dist/gameplay/ai/behavior-tree/behavior-tree.test.d.ts +2 -0
  8. package/dist/gameplay/ai/behavior-tree/behavior-tree.test.js +4 -0
  9. package/dist/gameplay/ai/behavior-tree/bt.d.ts +83 -70
  10. package/dist/gameplay/ai/behavior-tree/bt.js +1 -1
  11. package/dist/gameplay/ai/behavior-tree/bt.test.d.ts +2 -0
  12. package/dist/gameplay/ai/behavior-tree/bt.test.js +4 -0
  13. package/dist/gameplay/ai/behavior-tree/move.d.ts +1 -0
  14. package/dist/gameplay/ai/behavior-tree/move.js +1 -1
  15. package/dist/gameplay/ai/index.d.ts +1 -0
  16. package/dist/gameplay/ai/index.js +1 -1
  17. package/dist/gameplay/services/render.d.ts +3 -0
  18. package/dist/gameplay/services/render.js +1 -1
  19. package/dist/rendering/node-material-webgpu-resolver.d.ts +1 -0
  20. package/dist/rendering/node-material-webgpu-resolver.js +1 -1
  21. package/dist/rendering/webgpu-experimental-backend.d.ts +1 -0
  22. package/dist/rendering/webgpu-experimental-backend.js +1 -1
  23. package/dist/secondary-motion/budget.d.ts +26 -0
  24. package/dist/secondary-motion/budget.js +4 -0
  25. package/dist/secondary-motion/cache.d.ts +16 -0
  26. package/dist/secondary-motion/cache.js +4 -0
  27. package/dist/secondary-motion/runtime.d.ts +15 -0
  28. package/dist/secondary-motion/runtime.js +1 -1
  29. package/dist/secondary-motion/skinned-targets.d.ts +35 -0
  30. package/dist/secondary-motion/skinned-targets.js +4 -0
  31. package/dist/test/gpu-sprite-compiler.test.js +1 -1
  32. package/dist/test/node-material-webgpu-resolver.test.js +1 -1
  33. package/dist/test/secondary-motion-budget.test.d.ts +2 -0
  34. package/dist/test/secondary-motion-budget.test.js +4 -0
  35. package/dist/test/secondary-motion-cache.test.d.ts +2 -0
  36. package/dist/test/secondary-motion-cache.test.js +4 -0
  37. package/dist/test/secondary-motion-gpu-check.d.ts +2 -0
  38. package/dist/test/secondary-motion-gpu-check.js +4 -0
  39. package/dist/test/sequence-scene-binding.test.js +1 -1
  40. package/dist/test/vfx-playback-transform.test.d.ts +2 -0
  41. package/dist/test/vfx-playback-transform.test.js +4 -0
  42. package/package.json +4 -1
  43. package/tsconfig.tsbuildinfo +1 -1
@@ -1,4 +1,4 @@
1
- import{__decorate as t,__metadata as e}from"tslib";import*as s from"three";import{Rate as i}from"@hology/nebula";import{Actor as r,BaseActor as a}from"../../gameplay/actors/actor.js";import{inject as n}from"../../gameplay/inject.js";import{AssetLoader as o}from"../../gameplay/services/asset-loader.js";import{ViewController as l}from"../../gameplay/services/render.js";import{World as p}from"../../gameplay/services/world.js";import{DelayRate as h}from"./rates.js";import{materializeVfx as u}from"./vfx-materializer.js";import{PhysicsSystem as m}from"../../gameplay/services/physics/physics-system.js";import{ShaderProvider as c}from"../../gameplay/services/shader-provider.js";import{AssetResourceLoader as d}from"../../scene/asset-resource-loader.js";import{AssetsProvider as f}from"../../scene/assets-provider.js";import{VfxInputRuntime as g}from"./vfx-input-runtime.js";import{Subject as y}from"rxjs";import{EventGraphCallable as v}from"../../gameplay/event-graph/decorators.js";import{EVENT_GRAPH_VOID_TYPE as b}from"../../gameplay/event-graph/types.js";import{VFX_ACTOR_TYPE_ID as w}from"../../gameplay/event-graph/asset-actor-types.js";import{GpuVfxWorldService as P}from"./gpu-vfx-world-service.js";import{compileGpuSpriteEmitters as S,requiresCpuVfxBackend as j}from"./gpu-sprite-compiler.js";import{withSeededRandom as E}from"./seeded-random.js";let x=class extends a{constructor(){super(...arguments),this.onComplete=new y,this.collisionEvent={emitterId:"",emitterName:"",position:new s.Vector3,normal:new s.Vector3},this._timescale=1,this.paused=!0,this.runtimeBackend="cpu",this.backendFallbackReasons=[],this.profilingEnabled=!1,this.simulationCpuMs=0,this.system=null,this.disposeSystem=null,this.assetLoader=n(o),this.assetManagerService=n(d),this.assetService=n(f),this.world=n(p),this.view=n(l),this.gpuWorld=n(P),this.particleSystemContainer=null,this.gpuPrepared=null,this.gpuHandle=null,this.physics=n(m),this.shaderProvider=n(c),this.inputRuntime=new g,this.inputOverrides=new Map,this.finishRequested=!1,this.completionEmitted=!1,this.inputsMaterialized=!1,this._worldPos=new s.Vector3,this._worldRot=new s.Quaternion,this._worldEul=new s.Euler,this._worldScale=new s.Vector3,this.previewStepSeconds=1/60,this.maxPreviewSteps=240,this.max=0}get timescale(){return this._timescale}set timescale(t){this._timescale=Math.max(0,t),this.gpuHandle?.setTimescale(this._timescale)}async fromAsset(t,e,i,r){if("vfx"!==t.type)throw"Asset must be a VFX asset but is "+t.type;null!=this.system&&(this.system.destroy(),this.system.emitters.forEach(t=>t.reset())),this.gpuHandle?.release(),this.gpuPrepared?.release(),this.gpuHandle=null,this.gpuPrepared=null,this.runtimeBackend="cpu",this.backendFallbackReasons=[],this.simulationCpuMs=0,this.sourceAsset=t,this.finishRequested=!1,this.completionEmitted=!1,this.inputsMaterialized=!1,this.disposeSystem&&this.disposeSystem(),null==this.object.parent&&this.world.scene.add(this.object),this.inputRuntime.setDefinitions(this.sourceAsset.vfx.inputs??[]);for(const t of this.inputRuntime.getDefinitions()){if("mesh"!==t.type||"string"!=typeof t.defaultValue||0===t.defaultValue.length)continue;const e=await this.assetService.getAsset(t.defaultValue);if(null==e)continue;const s=await this.assetManagerService.getMesh(e,{applyMaterials:!0,rescale:!0});this.inputRuntime.setInput(t.id,s.scene)}for(const[t,e]of this.inputOverrides)this.inputRuntime.setInput(t,e);null!=e&&this.setInputs(e);const a=i??this.view.vfxBackendPreference,n=j(this.sourceAsset);if("cpu"===a&&(this.backendFallbackReasons=["GPU VFX was not requested; the selected VFX backend preference is CPU."]),"cpu"!==a&&!n){const t=new Map;for(const e of this.inputRuntime.getDefinitions()){if("mesh"!==e.type)continue;const s=this.inputRuntime.getMesh(e.id);null!=s&&t.set(e.id,s)}if(this.gpuPrepared=await this.gpuWorld.prepare(this.sourceAsset,r,t),null!=this.gpuPrepared)return this.runtimeBackend="gpu",this.system=null,this.disposeSystem=null,this.particleSystemContainer=null,this.object.visible=!1,this.object.matrixAutoUpdate=!0,this.object.matrixWorldAutoUpdate=!0,void(this.inputsMaterialized=!0);if(this.backendFallbackReasons=this.gpuWorld.getFallbackReasons(this.sourceAsset,r),"gpu"===a){const t=S(this.sourceAsset,[new s.Vector3]).compatibility;this.backendFallbackReasons=t.issues.filter(t=>"warning"!==t.severity).map(t=>`${t.emitter}: ${t.message}`),0===this.backendFallbackReasons.length&&(this.backendFallbackReasons=["GPU template or material preparation failed; inspect earlier shader diagnostics."])}}const{system:o,dispose:l,container:p}=await u(this.sourceAsset,this.object,{getAsset:t=>this.assetLoader.getAsset(t),getMaterial:t=>this.assetLoader.getMaterialByAssetId(t),getTexture:t=>this.assetLoader.getTextureByAssetId(t),getMesh:async t=>(await this.assetManagerService.getMesh(await this.assetService.getAsset(t),{applyMaterials:!0,rescale:!0})).scene},this.view,this.physics,this.shaderProvider,this.assetService,this.assetManagerService,this.inputRuntime,(t,e,s,i,r)=>{if(null==this.onCollision)return;const a=this.collisionEvent;a.emitterId=t,a.emitterName=e,a.position.copy(s),a.normal.copy(i),r&&(a.position.applyMatrix4(this.object.matrixWorld),a.normal.transformDirection(this.object.matrixWorld)),this.onCollision(a)});this.system=o,this.disposeSystem=l,this.particleSystemContainer=p,this.inputsMaterialized=!0,this.object.visible=!1,this.object.matrixAutoUpdate=!1,this.object.matrixWorldAutoUpdate=!1,this.particleSystemContainer.matrixAutoUpdate=!1,this.particleSystemContainer.matrixWorldAutoUpdate=!1}setInput(t,e){const i=this.inputRuntime.getDefinition(t);if(this.inputsMaterialized&&"mesh"===i?.type)return void console.warn(`VFX mesh input "${i.name}" is creation-time only; recreate the VFX to replace it.`);this.inputOverrides.set(t,function(t){if(t instanceof s.Vector3||t instanceof s.Color)return t.clone();if(Array.isArray(t))return[...t];return t}(e)),this.inputRuntime.setInput(t,e);const r=this.inputRuntime.getDefinition(t);null!=r&&this.gpuHandle?.setInput(r.id,this.inputRuntime.getValue(r.id))}setInputs(t){for(const[e,s]of Object.entries(t))this.setInput(e,s)}play(){if(this.prepareForPlayback(),this.paused=!1,null!=this.gpuPrepared){if(this.object.getWorldPosition(this._worldPos),null!=this.gpuHandle&&this.gpuHandle.active)this.gpuHandle.play();else{this.gpuHandle=this.gpuPrepared.spawn(this._worldPos),this.gpuHandle?.setTransform(this.object.matrixWorld),this.gpuHandle?.setTimescale(this._timescale);for(const t of this.inputRuntime.getDefinitions())this.gpuHandle?.setInput(t.id,this.inputRuntime.getValue(t.id))}return}this.system.emitters.every(t=>t.dead)&&this.restart()}pause(){this.paused=!0,this.gpuHandle?.pause()}previewAt(t,e,s,i,r=0){null!=this.system&&(this.prepareForPlayback(),E(r,()=>{this.restart(),this.paused=!0;const r=Math.max(0,t),a=Math.min(r,Math.max(0,e)),n=r-a,o=Number(a>0)+Number(n>0),l=Math.max(o,Math.min(this.maxPreviewSteps,Math.max(1,Math.ceil(r/this.previewStepSeconds)))),p=a<=0?0:n<=0?l:Math.min(l-1,Math.max(1,Math.round(l*a/r)));this.simulatePreview(a,p),n>0&&(this.applyClipEndBehavior(s,i),this.simulatePreview(n,l-p))}))}clearPreview(){this.restart(),this.pause(),this.object.visible=!1,null!=this.particleSystemContainer&&(this.particleSystemContainer.visible=!1)}stop(){this.finishRequested=!0,null==this.gpuPrepared?null!=this.system?.emitters&&this.system.emitters.forEach(t=>{this.stopEmitterEmission(t)}):this.gpuHandle?.stop()}applyClipEndBehavior(t,e){const s=t??"finish";if("finish"!==s){if(null!=this.gpuPrepared)return this.finishRequested=!0,void("kill"===s?this.gpuHandle?.kill():this.gpuHandle?.expire(Math.max(0,e??0)));if(null!=this.system&&null!=this.system.emitters)for(const t of this.system.emitters)this.applyEmitterClipEndBehavior(t,s,e)}else this.stop()}restart(){if(null==this.gpuPrepared){if(null!=this.system&&null!=this.system.emitters){this.finishRequested=!1,this.completionEmitted=!1;for(const t of this.system.emitters)this.restartEmitter(t)}}else if(this.finishRequested=!1,this.completionEmitted=!1,null!=this.gpuHandle){if(!this.gpuHandle.active&&!this.paused)return void this.play();this.gpuHandle.restart()}}restartEmitter(t){const e=t.rate;e instanceof h?e.restart():e instanceof i&&(e.nextTime=0),null==t.clearParticlesRecursive?this.removeEmitterParticlesRecursive(t):t.clearParticlesRecursive()}removeEmitterParticlesRecursive(t){const e=t.childEmitters??[];for(const t of e)this.removeEmitterParticlesRecursive(t);t.removeAllParticles?.()}onUpdate(t){if(this.paused)null!=this.gpuHandle&&(this.object.getWorldPosition(this._worldPos),this.gpuHandle.setPosition(this._worldPos),this.gpuHandle.setTransform(this.object.matrixWorld));else{if(null!=this.gpuPrepared)return this.object.getWorldPosition(this._worldPos),this.gpuHandle?.setPosition(this._worldPos),this.gpuHandle?.setTransform(this.object.matrixWorld),void this.updateCompletionState();this.updateWorldSpaceEmitterTransforms(),this.updateSystem(t*this.timescale),this.updateCompletionState()}}prepareForPlayback(){null==this.gpuPrepared?(null!=this.particleSystemContainer&&null==this.particleSystemContainer.parent&&this.world.scene.add(this.particleSystemContainer),this.object.matrixAutoUpdate=!0,this.object.matrixWorldAutoUpdate=!0,null!=this.particleSystemContainer&&(this.particleSystemContainer.matrixAutoUpdate=!0,this.particleSystemContainer.matrixWorldAutoUpdate=!0),this.object.visible=!0,null!=this.particleSystemContainer&&(this.particleSystemContainer.visible=!0)):this.object.visible=!0}simulatePreview(t,e){if(t<=0)return;const s=t/e;for(let t=0;t<e;t++)this.updateWorldSpaceEmitterTransforms(),this.updateSystem(s)}updateWorldSpaceEmitterTransforms(){this.object.getWorldPosition(this._worldPos),this.object.getWorldQuaternion(this._worldRot),this.object.getWorldScale(this._worldScale),this._worldEul.setFromQuaternion(this._worldRot),this.system?.emitters.forEach(t=>{if("world"===t._space){t.setPosition(this._worldPos),t.setRotation(this._worldEul);(t._worldScale??(t._worldScale=new s.Vector3)).copy(this._worldScale)}})}getParticleCount(){return null!=this.gpuPrepared?0:this.system?.getCount()??0}getProfilingStats(){let t=0,e=0;return this.particleSystemContainer?.traverse(i=>{const r=i,a=r.geometry;if(null==a||!1===r.visible)return;const n=r instanceof s.InstancedMesh?r.count:1,o=a.index?.count??a.getAttribute("position")?.count??0;n<=0||o<=0||(t++,e+=Math.floor(o/3)*n)}),{particles:this.system?.getCount()??0,drawCalls:t,triangles:e,cpuTimeMs:this.simulationCpuMs}}onEndPlay(){this.stop(),null!=this.disposeSystem&&this.disposeSystem(),this.gpuHandle?.release(),this.gpuPrepared?.release(),this.gpuHandle=null,this.gpuPrepared=null,this.onComplete.complete()}updateCompletionState(){if(null!=this.gpuPrepared){if(this.completionEmitted||!1!==this.gpuHandle?.active)return;return this.completionEmitted=!0,void this.onComplete.next()}if(this.completionEmitted||null==this.system||!this.onComplete.observed)return;const t=this.system.emitters.every(t=>t.dead),e=this.finishRequested&&0===this.system.getCount();(t||e)&&(this.completionEmitted=!0,this.onComplete.next())}updateSystem(t){if(null==this.system)return;if(!this.profilingEnabled)return void this.system.update(t);const e=performance.now();this.system.update(t),this.simulationCpuMs=performance.now()-e}applyEmitterClipEndBehavior(t,e,s,i=!1){this.stopEmitterEmission(t,i);const r=t.particles??[];if("kill"===e)t.removeAllParticles?.();else{const t=Math.max(0,s??0);for(const e of r)e.life=Math.min(e.life,e.age+t)}const a=t.childEmitters??[];for(const t of a)this.applyEmitterClipEndBehavior(t,e,s,!0)}stopEmitterEmission(t,e=!1){const s=t.rate;s instanceof i&&(s.nextTime=1/0),e&&t.stopEmit?.()}};t([v({parameters:[],returns:b}),e("design:type",Function),e("design:paramtypes",[]),e("design:returntype",void 0)],x.prototype,"play",null),t([v({parameters:[],returns:b}),e("design:type",Function),e("design:paramtypes",[]),e("design:returntype",void 0)],x.prototype,"pause",null),t([v({parameters:[],returns:b}),e("design:type",Function),e("design:paramtypes",[]),e("design:returntype",void 0)],x.prototype,"stop",null),t([v({parameters:[],returns:b}),e("design:type",Function),e("design:paramtypes",[]),e("design:returntype",void 0)],x.prototype,"restart",null),x=t([r({typeId:w})],x);export{x as VfxActor};/*
1
+ import{__decorate as t,__metadata as e}from"tslib";import*as s from"three";import{Rate as i}from"@hology/nebula";import{Actor as r,BaseActor as o}from"../../gameplay/actors/actor.js";import{inject as a}from"../../gameplay/inject.js";import{AssetLoader as n}from"../../gameplay/services/asset-loader.js";import{ViewController as l}from"../../gameplay/services/render.js";import{World as p}from"../../gameplay/services/world.js";import{DelayRate as h}from"./rates.js";import{materializeVfx as u}from"./vfx-materializer.js";import{PhysicsSystem as m}from"../../gameplay/services/physics/physics-system.js";import{ShaderProvider as d}from"../../gameplay/services/shader-provider.js";import{AssetResourceLoader as c}from"../../scene/asset-resource-loader.js";import{AssetsProvider as f}from"../../scene/assets-provider.js";import{VfxInputRuntime as g}from"./vfx-input-runtime.js";import{Subject as y}from"rxjs";import{EventGraphCallable as v}from"../../gameplay/event-graph/decorators.js";import{EVENT_GRAPH_VOID_TYPE as b}from"../../gameplay/event-graph/types.js";import{VFX_ACTOR_TYPE_ID as w}from"../../gameplay/event-graph/asset-actor-types.js";import{GpuVfxWorldService as P}from"./gpu-vfx-world-service.js";import{compileGpuSpriteEmitters as S,requiresCpuVfxBackend as j}from"./gpu-sprite-compiler.js";import{withSeededRandom as x}from"./seeded-random.js";let E=class extends o{constructor(){super(...arguments),this.onComplete=new y,this.collisionEvent={emitterId:"",emitterName:"",position:new s.Vector3,normal:new s.Vector3},this._timescale=1,this.paused=!0,this.runtimeBackend="cpu",this.backendFallbackReasons=[],this.profilingEnabled=!1,this.simulationCpuMs=0,this.system=null,this.disposeSystem=null,this.assetLoader=a(n),this.assetManagerService=a(c),this.assetService=a(f),this.world=a(p),this.view=a(l),this.gpuWorld=a(P),this.particleSystemContainer=null,this.gpuPrepared=null,this.gpuHandle=null,this.physics=a(m),this.shaderProvider=a(d),this.inputRuntime=new g,this.inputOverrides=new Map,this.finishRequested=!1,this.completionEmitted=!1,this.inputsMaterialized=!1,this._worldPos=new s.Vector3,this._worldRot=new s.Quaternion,this._worldEul=new s.Euler,this._worldScale=new s.Vector3,this.previewStepSeconds=1/60,this.maxPreviewSteps=240,this.max=0}get timescale(){return this._timescale}set timescale(t){this._timescale=Math.max(0,t),this.gpuHandle?.setTimescale(this._timescale)}async fromAsset(t,e,i,r){if("vfx"!==t.type)throw"Asset must be a VFX asset but is "+t.type;null!=this.system&&(this.system.destroy(),this.system.emitters.forEach(t=>t.reset())),this.gpuHandle?.release(),this.gpuPrepared?.release(),this.gpuHandle=null,this.gpuPrepared=null,this.runtimeBackend="cpu",this.backendFallbackReasons=[],this.simulationCpuMs=0,this.sourceAsset=t,this.finishRequested=!1,this.completionEmitted=!1,this.inputsMaterialized=!1,this.disposeSystem&&this.disposeSystem(),null==this.object.parent&&this.world.scene.add(this.object),this.inputRuntime.setDefinitions(this.sourceAsset.vfx.inputs??[]);for(const t of this.inputRuntime.getDefinitions()){if("mesh"!==t.type||"string"!=typeof t.defaultValue||0===t.defaultValue.length)continue;const e=await this.assetService.getAsset(t.defaultValue);if(null==e)continue;const s=await this.assetManagerService.getMesh(e,{applyMaterials:!0,rescale:!0});this.inputRuntime.setInput(t.id,s.scene)}for(const[t,e]of this.inputOverrides)this.inputRuntime.setInput(t,e);null!=e&&this.setInputs(e);const o=i??this.view.vfxBackendPreference,a=j(this.sourceAsset);if("cpu"===o&&(this.backendFallbackReasons=["GPU VFX was not requested; the selected VFX backend preference is CPU."]),"cpu"!==o&&!a){const t=new Map;for(const e of this.inputRuntime.getDefinitions()){if("mesh"!==e.type)continue;const s=this.inputRuntime.getMesh(e.id);null!=s&&t.set(e.id,s)}if(this.gpuPrepared=await this.gpuWorld.prepare(this.sourceAsset,r,t),null!=this.gpuPrepared)return this.runtimeBackend="gpu",this.system=null,this.disposeSystem=null,this.particleSystemContainer=null,this.object.visible=!1,this.object.matrixAutoUpdate=!0,this.object.matrixWorldAutoUpdate=!0,void(this.inputsMaterialized=!0);if(this.backendFallbackReasons=this.gpuWorld.getFallbackReasons(this.sourceAsset,r),"gpu"===o){const t=S(this.sourceAsset,[new s.Vector3]).compatibility;this.backendFallbackReasons=t.issues.filter(t=>"warning"!==t.severity).map(t=>`${t.emitter}: ${t.message}`),0===this.backendFallbackReasons.length&&(this.backendFallbackReasons=["GPU template or material preparation failed; inspect earlier shader diagnostics."])}}const{system:n,dispose:l,container:p}=await u(this.sourceAsset,this.object,{getAsset:t=>this.assetLoader.getAsset(t),getMaterial:t=>this.assetLoader.getMaterialByAssetId(t),getTexture:t=>this.assetLoader.getTextureByAssetId(t),getMesh:async t=>(await this.assetManagerService.getMesh(await this.assetService.getAsset(t),{applyMaterials:!0,rescale:!0})).scene},this.view,this.physics,this.shaderProvider,this.assetService,this.assetManagerService,this.inputRuntime,(t,e,s,i,r)=>{if(null==this.onCollision)return;const o=this.collisionEvent;o.emitterId=t,o.emitterName=e,o.position.copy(s),o.normal.copy(i),r&&(o.position.applyMatrix4(this.object.matrixWorld),o.normal.transformDirection(this.object.matrixWorld)),this.onCollision(o)});this.system=n,this.disposeSystem=l,this.particleSystemContainer=p,this.inputsMaterialized=!0,this.object.visible=!1,this.object.matrixAutoUpdate=!1,this.object.matrixWorldAutoUpdate=!1,this.particleSystemContainer.matrixAutoUpdate=!1,this.particleSystemContainer.matrixWorldAutoUpdate=!1}setInput(t,e){const i=this.inputRuntime.getDefinition(t);if(this.inputsMaterialized&&"mesh"===i?.type)return void console.warn(`VFX mesh input "${i.name}" is creation-time only; recreate the VFX to replace it.`);this.inputOverrides.set(t,function(t){if(t instanceof s.Vector3||t instanceof s.Color)return t.clone();if(Array.isArray(t))return[...t];return t}(e)),this.inputRuntime.setInput(t,e);const r=this.inputRuntime.getDefinition(t);null!=r&&this.gpuHandle?.setInput(r.id,this.inputRuntime.getValue(r.id))}setInputs(t){for(const[e,s]of Object.entries(t))this.setInput(e,s)}play(){if(this.prepareForPlayback(),this.paused=!1,null!=this.gpuPrepared){if(this.object.getWorldPosition(this._worldPos),null!=this.gpuHandle&&this.gpuHandle.active)this.gpuHandle.setPosition(this._worldPos),this.gpuHandle.setTransform(this.object.matrixWorld),this.gpuHandle.play();else{this.gpuHandle=this.gpuPrepared.spawn(this._worldPos),this.gpuHandle?.setTransform(this.object.matrixWorld),this.gpuHandle?.setTimescale(this._timescale);for(const t of this.inputRuntime.getDefinitions())this.gpuHandle?.setInput(t.id,this.inputRuntime.getValue(t.id))}return}this.system.emitters.every(t=>t.dead)&&this.restart()}pause(){this.paused=!0,this.gpuHandle?.pause()}previewAt(t,e,s,i,r=0){null!=this.system&&(this.prepareForPlayback(),x(r,()=>{this.restart(),this.paused=!0;const r=Math.max(0,t),o=Math.min(r,Math.max(0,e)),a=r-o,n=Number(o>0)+Number(a>0),l=Math.max(n,Math.min(this.maxPreviewSteps,Math.max(1,Math.ceil(r/this.previewStepSeconds)))),p=o<=0?0:a<=0?l:Math.min(l-1,Math.max(1,Math.round(l*o/r)));this.simulatePreview(o,p),a>0&&(this.applyClipEndBehavior(s,i),this.simulatePreview(a,l-p))}))}clearPreview(){this.restart(),this.pause(),this.object.visible=!1,null!=this.particleSystemContainer&&(this.particleSystemContainer.visible=!1)}stop(){this.finishRequested=!0,null==this.gpuPrepared?null!=this.system?.emitters&&this.system.emitters.forEach(t=>{this.stopEmitterEmission(t)}):this.gpuHandle?.stop()}applyClipEndBehavior(t,e){const s=t??"finish";if("finish"!==s){if(null!=this.gpuPrepared)return this.finishRequested=!0,void("kill"===s?this.gpuHandle?.kill():this.gpuHandle?.expire(Math.max(0,e??0)));if(null!=this.system&&null!=this.system.emitters)for(const t of this.system.emitters)this.applyEmitterClipEndBehavior(t,s,e)}else this.stop()}restart(){if(null==this.gpuPrepared){if(null!=this.system&&null!=this.system.emitters){this.finishRequested=!1,this.completionEmitted=!1;for(const t of this.system.emitters)this.restartEmitter(t)}}else if(this.finishRequested=!1,this.completionEmitted=!1,null!=this.gpuHandle){if(!this.gpuHandle.active&&!this.paused)return void this.play();this.object.getWorldPosition(this._worldPos),this.gpuHandle.setPosition(this._worldPos),this.gpuHandle.setTransform(this.object.matrixWorld),this.gpuHandle.restart()}}restartEmitter(t){const e=t.rate;e instanceof h?e.restart():e instanceof i&&(e.nextTime=0),null==t.clearParticlesRecursive?this.removeEmitterParticlesRecursive(t):t.clearParticlesRecursive()}removeEmitterParticlesRecursive(t){const e=t.childEmitters??[];for(const t of e)this.removeEmitterParticlesRecursive(t);t.removeAllParticles?.()}onUpdate(t){if(this.paused)null!=this.gpuHandle&&(this.object.getWorldPosition(this._worldPos),this.gpuHandle.setPosition(this._worldPos),this.gpuHandle.setTransform(this.object.matrixWorld));else{if(null!=this.gpuPrepared)return this.object.getWorldPosition(this._worldPos),this.gpuHandle?.setPosition(this._worldPos),this.gpuHandle?.setTransform(this.object.matrixWorld),void this.updateCompletionState();this.updateWorldSpaceEmitterTransforms(),this.updateSystem(t*this.timescale),this.updateCompletionState()}}prepareForPlayback(){null==this.gpuPrepared?(null!=this.particleSystemContainer&&null==this.particleSystemContainer.parent&&this.world.scene.add(this.particleSystemContainer),this.object.matrixAutoUpdate=!0,this.object.matrixWorldAutoUpdate=!0,null!=this.particleSystemContainer&&(this.particleSystemContainer.matrixAutoUpdate=!0,this.particleSystemContainer.matrixWorldAutoUpdate=!0),this.object.visible=!0,null!=this.particleSystemContainer&&(this.particleSystemContainer.visible=!0)):this.object.visible=!0}simulatePreview(t,e){if(t<=0)return;const s=t/e;for(let t=0;t<e;t++)this.updateWorldSpaceEmitterTransforms(),this.updateSystem(s)}updateWorldSpaceEmitterTransforms(){this.object.getWorldPosition(this._worldPos),this.object.getWorldQuaternion(this._worldRot),this.object.getWorldScale(this._worldScale),this._worldEul.setFromQuaternion(this._worldRot),this.system?.emitters.forEach(t=>{if("world"===t._space){t.setPosition(this._worldPos),t.setRotation(this._worldEul);(t._worldScale??(t._worldScale=new s.Vector3)).copy(this._worldScale)}})}getParticleCount(){return null!=this.gpuPrepared?0:this.system?.getCount()??0}getProfilingStats(){let t=0,e=0;return this.particleSystemContainer?.traverse(i=>{const r=i,o=r.geometry;if(null==o||!1===r.visible)return;const a=r instanceof s.InstancedMesh?r.count:1,n=o.index?.count??o.getAttribute("position")?.count??0;a<=0||n<=0||(t++,e+=Math.floor(n/3)*a)}),{particles:this.system?.getCount()??0,drawCalls:t,triangles:e,cpuTimeMs:this.simulationCpuMs}}onEndPlay(){this.stop(),null!=this.disposeSystem&&this.disposeSystem(),this.gpuHandle?.release(),this.gpuPrepared?.release(),this.gpuHandle=null,this.gpuPrepared=null,this.onComplete.complete()}updateCompletionState(){if(null!=this.gpuPrepared){if(this.completionEmitted||!1!==this.gpuHandle?.active)return;return this.completionEmitted=!0,void this.onComplete.next()}if(this.completionEmitted||null==this.system||!this.onComplete.observed)return;const t=this.system.emitters.every(t=>t.dead),e=this.finishRequested&&0===this.system.getCount();(t||e)&&(this.completionEmitted=!0,this.onComplete.next())}updateSystem(t){if(null==this.system)return;if(!this.profilingEnabled)return void this.system.update(t);const e=performance.now();this.system.update(t),this.simulationCpuMs=performance.now()-e}applyEmitterClipEndBehavior(t,e,s,i=!1){this.stopEmitterEmission(t,i);const r=t.particles??[];if("kill"===e)t.removeAllParticles?.();else{const t=Math.max(0,s??0);for(const e of r)e.life=Math.min(e.life,e.age+t)}const o=t.childEmitters??[];for(const t of o)this.applyEmitterClipEndBehavior(t,e,s,!0)}stopEmitterEmission(t,e=!1){const s=t.rate;s instanceof i&&(s.nextTime=1/0),e&&t.stopEmit?.()}};t([v({parameters:[],returns:b}),e("design:type",Function),e("design:paramtypes",[]),e("design:returntype",void 0)],E.prototype,"play",null),t([v({parameters:[],returns:b}),e("design:type",Function),e("design:paramtypes",[]),e("design:returntype",void 0)],E.prototype,"pause",null),t([v({parameters:[],returns:b}),e("design:type",Function),e("design:paramtypes",[]),e("design:returntype",void 0)],E.prototype,"stop",null),t([v({parameters:[],returns:b}),e("design:type",Function),e("design:paramtypes",[]),e("design:returntype",void 0)],E.prototype,"restart",null),E=t([r({typeId:w})],E);export{E as VfxActor};/*
2
2
  * Copyright (©) 2026 Hology Interactive AB. All rights reserved.
3
3
  * See the LICENSE.md file for details.
4
4
  */
@@ -0,0 +1,26 @@
1
+ import { NodeState, type BehaviorTreeContext, type Node } from './bt.js';
2
+ /** Owns one tree instance. Build the topology before construction; do not share nodes. */
3
+ export declare class BehaviorTree implements BehaviorTreeContext {
4
+ readonly root: Node;
5
+ private readonly executions;
6
+ private readonly activeNodes;
7
+ private current;
8
+ private elapsed;
9
+ private visit;
10
+ private updating;
11
+ constructor(root: Node);
12
+ get dt(): number;
13
+ /** Unordered live view, intended for inspection. */
14
+ get active(): readonly Node[];
15
+ getState(node: Node): NodeState | undefined;
16
+ isActive(node: Node): boolean;
17
+ tick(dt: number): NodeState;
18
+ execute(node: Node, dt?: number): NodeState;
19
+ /** Abort a subtree, or the whole tree when called without an argument. */
20
+ abort(node?: Node): void;
21
+ retain(node: Node): void;
22
+ private finish;
23
+ private lookup;
24
+ private register;
25
+ }
26
+ //# sourceMappingURL=behavior-tree.d.ts.map
@@ -0,0 +1,4 @@
1
+ import{NodeState as t}from"./bt.js";export class BehaviorTree{constructor(t){this.root=t,this.executions=new Map,this.activeNodes=[],this.elapsed=0,this.visit=0,this.updating=!1,this.register(t,void 0)}get dt(){return this.elapsed}get active(){return this.activeNodes}getState(t){return this.lookup(t).result}isActive(t){return this.lookup(t).active}tick(t){if(this.updating)throw new Error("BehaviorTree.tick cannot be reentered");this.updating=!0,this.elapsed=t;try{return this.execute(this.root)}catch(t){try{this.abort(this.root)}catch{}throw t}finally{this.updating=!1}}execute(e,i=this.elapsed){const r=this.lookup(e);if(!this.updating||(this.current?r.parent!==this.current:e!==this.root))throw new Error("Execute nodes through tree.tick or their parent context");if(r.executing)throw new Error("A node cannot execute recursively");const s=this.current,o=this.elapsed,n=++this.visit;r.visited=n,r.executing=!0,this.current=r,this.elapsed=i;try{r.active||(r.active=!0,r.activeIndex=this.activeNodes.length,this.activeNodes.push(e),r.result=void 0,e.start());const s=e.tick(i,this);if(s!==t.RUNNING&&s!==t.SUCCESS&&s!==t.FAILURE)throw new Error("Node returned an invalid execution result");if(r.result=s,s!==t.RUNNING)this.finish(r,s===t.SUCCESS?"success":"failure");else for(const t of r.children)t.active&&t.visited<=n&&this.finish(t,"aborted");return s}finally{r.executing=!1,this.current=s,this.elapsed=o}}abort(t=this.root){const e=this.lookup(t);if(e.executing)throw new Error("Cannot abort a node during its own execution");this.finish(e,"aborted")}retain(t){const e=this.lookup(t);if(!this.current||e.parent!==this.current)throw new Error("Only the executing parent can retain its child");e.visited=++this.visit}finish(t,e){if(!t.active)return;let i,r=!1;for(const e of t.children)try{this.finish(e,"aborted")}catch(t){r||(i=t,r=!0)}if(t.active){t.active=!1;const s=this.activeNodes.pop();s!==t.node&&(this.activeNodes[t.activeIndex]=s,this.lookup(s).activeIndex=t.activeIndex),t.activeIndex=-1;try{t.node.stop(e)}catch(t){r||(i=t,r=!0)}}if(r)throw i}lookup(t){const e=this.executions.get(t);if(!e)throw new Error("Node is not part of this behavior tree");return e}register(t,e){if(this.executions.has(t))throw new Error("Behavior tree nodes cannot be shared or cyclic");const i={node:t,parent:e,children:[],active:!1,activeIndex:-1,executing:!1,visited:0,result:void 0};this.executions.set(t,i);for(const e of t.getChildren())i.children.push(this.register(e,i));return i}}/*
2
+ * Copyright (©) 2026 Hology Interactive AB. All rights reserved.
3
+ * See the LICENSE.md file for details.
4
+ */
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=behavior-tree.test.d.ts.map
@@ -0,0 +1,4 @@
1
+ import{describe as t,expect as e,it as a,vi as n}from"vitest";import{BehaviorTree as o}from"./behavior-tree.js";import{Node as c,NodeState as i,SequenceNode as s,GuardNode as r,ThrottleNode as l}from"./bt.js";class d extends c{constructor(t=i.RUNNING){super(),this.result=t,this.start=n.fn(),this.stop=n.fn(t=>{}),this.tick=n.fn((t,e)=>this.result)}}class h extends c{constructor(t){super(),this.child=t,this.enabled=!0}getChildren(){return[this.child]}tick(t,e){return this.enabled&&e.execute(this.child,t),i.RUNNING}}t("BehaviorTree lifecycle",()=>{a("starts once, stops once, and restarts after completion",()=>{const t=new d,a=new o(t);a.tick(.1),a.tick(.1),e(t.start).toHaveBeenCalledTimes(1),e(a.active).toEqual([t]),e(a.getState(t)).toBe(i.RUNNING),t.result=i.SUCCESS,a.tick(.1),e(t.stop).toHaveBeenCalledExactlyOnceWith("success"),e(a.active).toEqual([]),a.tick(.1),e(t.start).toHaveBeenCalledTimes(2),e(t.stop).toHaveBeenCalledTimes(2)}),a("pairs start and stop for immediate failure",()=>{const t=new d(i.FAILURE),a=new o(t);e(a.tick(.1)).toBe(i.FAILURE),e(t.start).toHaveBeenCalledTimes(1),e(t.stop).toHaveBeenCalledExactlyOnceWith("failure")}),a("stops descendants before their parent and makes abort idempotent",()=>{const t=[],a=new d,n=new h(a),c=new o(n);a.stop.mockImplementation(()=>{t.push("child")}),n.stop=()=>{t.push("parent")},c.tick(.1),c.abort(),c.abort(),e(t).toEqual(["child","parent"]),e(c.active).toEqual([]),c.tick(.1),e(a.start).toHaveBeenCalledTimes(2)}),a("automatically cancels an abandoned running child",()=>{const t=new d,a=new h(t),n=new o(a);n.tick(.1),a.enabled=!1,n.tick(.1),e(t.stop).toHaveBeenCalledExactlyOnceWith("aborted"),e(n.active).toEqual([a])}),a("retains a throttled subtree without restarting or ticking it",()=>{const t=new d,a=new s;a.addChild(t);const n=new l(a,1),c=new o(n);c.tick(.1),c.tick(.1),c.tick(.1),e(t.start).toHaveBeenCalledTimes(1),e(t.tick).toHaveBeenCalledTimes(1),e(t.stop).not.toHaveBeenCalled(),e(c.active).toHaveLength(3),c.abort(),e(t.stop).toHaveBeenCalledExactlyOnceWith("aborted")}),a("restores context dt after a child executes with accumulated time",()=>{const t=[],a=new d;new o(new class extends h{tick(e,a){return t.push(a.dt),a.execute(this.child,.75),t.push(a.dt),i.RUNNING}}(a)).tick(.1),e(t).toEqual([.1,.1]),e(a.tick.mock.calls[0][0]).toBe(.75)}),a("cleans up when start or tick throws and remains reusable",()=>{const t=new d,a=new o(t);t.start.mockImplementationOnce(()=>{throw new Error("start failure")}),e(()=>a.tick(.1)).toThrow("start failure"),e(t.stop).toHaveBeenCalledExactlyOnceWith("aborted"),e(a.active).toEqual([]),a.tick(.1),t.tick.mockImplementationOnce(()=>{throw new Error("tick failure")}),e(()=>a.tick(.1)).toThrow("tick failure"),e(a.active).toEqual([]),a.tick(.1),e(a.isActive(t)).toBe(!0)}),a("continues cleaning siblings and parents if a stop hook throws",()=>{const t=new d,a=new d;const s=new class extends c{constructor(){super(...arguments),this.stop=n.fn()}getChildren(){return[t,a]}tick(e,n){return n.execute(t,e),n.execute(a,e),i.RUNNING}},r=new o(s);t.stop.mockImplementationOnce(()=>{throw new Error("stop failure")}),r.tick(.1),e(()=>r.abort()).toThrow("stop failure"),e(a.stop).toHaveBeenCalledExactlyOnceWith("aborted"),e(s.stop).toHaveBeenCalledExactlyOnceWith("aborted"),e(r.active).toEqual([])}),a("rejects shared children, cycles, foreign nodes and reentrant ticks",()=>{const t=new d,a=new s;a.addChild(t),a.addChild(t),e(()=>new o(a)).toThrow("shared or cyclic");const n=new s;n.addChild(n),e(()=>new o(n)).toThrow("shared or cyclic");const c=new o(t);e(()=>c.execute(t)).toThrow("through tree.tick"),e(()=>c.abort(new d)).toThrow("not part"),t.tick.mockImplementationOnce(()=>c.tick(.1)),e(()=>c.tick(.1)).toThrow("reentered"),e(c.active).toEqual([])}),a("starts a fresh guarded execution after interruption",()=>{let t=!0;const a=new d,n=new r(a,()=>t),c=new o(n);c.tick(.1),t=!1,e(c.tick(.1)).toBe(i.FAILURE),e(a.stop).toHaveBeenCalledExactlyOnceWith("aborted"),t=!0,c.tick(.1),e(a.start).toHaveBeenCalledTimes(2)})});/*
2
+ * Copyright (©) 2026 Hology Interactive AB. All rights reserved.
3
+ * See the LICENSE.md file for details.
4
+ */
@@ -1,15 +1,26 @@
1
- export declare abstract class Node {
2
- /**
3
- * @param dt The amount of time passed between behaviour tree updates
4
- * to be used by actions that depend on the time elapsed in game.
5
- */
6
- abstract tick(dt: number): NodeState;
7
- }
8
1
  export declare enum NodeState {
9
2
  SUCCESS = 0,
10
3
  FAILURE = 1,
11
4
  RUNNING = 2
12
5
  }
6
+ export type StopReason = 'success' | 'failure' | 'aborted';
7
+ export interface BehaviorTreeContext {
8
+ readonly dt: number;
9
+ execute(node: Node, dt?: number): NodeState;
10
+ abort(node: Node): void;
11
+ /** Preserve a running subtree when intentionally skipping its update. */
12
+ retain(node: Node): void;
13
+ }
14
+ export declare abstract class Node {
15
+ /** dt is elapsed game time in seconds. */
16
+ abstract tick(dt: number, context: BehaviorTreeContext): NodeState;
17
+ /** Called once before the first update of each execution. */
18
+ start(): void;
19
+ /** Called once on completion or interruption, after active descendants stop. */
20
+ stop(_reason: StopReason): void;
21
+ /** Children are fixed for the lifetime of a runtime; nodes must not be shared. */
22
+ getChildren(): readonly Node[];
23
+ }
13
24
  export declare abstract class LeafNode extends Node {
14
25
  }
15
26
  export declare class ActionNode extends LeafNode {
@@ -27,6 +38,17 @@ export declare class FailNode extends LeafNode {
27
38
  constructor(action: () => unknown);
28
39
  tick(): NodeState;
29
40
  }
41
+ /** Starts once; aborted completions are ignored. Rejections produce FAILURE. */
42
+ export declare class AsyncActionNode extends LeafNode {
43
+ private action;
44
+ private result;
45
+ private execution;
46
+ constructor(action: () => Promise<NodeState>);
47
+ start(): void;
48
+ tick(): NodeState;
49
+ private finish;
50
+ stop(): void;
51
+ }
30
52
  export declare class ConditionNode extends LeafNode {
31
53
  private condition;
32
54
  constructor(condition: () => boolean);
@@ -34,68 +56,66 @@ export declare class ConditionNode extends LeafNode {
34
56
  }
35
57
  export declare abstract class CompositeNode extends Node {
36
58
  protected children: Node[];
59
+ getChildren(): readonly Node[];
37
60
  addChild(node: Node): void;
38
61
  }
62
+ /** Resumes the running child without repeating completed steps. */
39
63
  export declare class SequenceNode extends CompositeNode {
40
64
  private current;
41
- tick(dt: number): NodeState;
42
- reset(): void;
65
+ tick(dt: number, context: BehaviorTreeContext): NodeState;
66
+ stop(): void;
43
67
  }
68
+ /** Reactive selector: higher priorities are checked every tick. */
44
69
  export declare class SelectorNode extends CompositeNode {
45
- tick(dt: number): NodeState;
70
+ private runningChild;
71
+ tick(dt: number, context: BehaviorTreeContext): NodeState;
72
+ stop(): void;
46
73
  }
47
74
  export declare abstract class DecoratorNode extends Node {
48
75
  protected child: Node;
49
76
  constructor(child: Node);
77
+ getChildren(): readonly Node[];
50
78
  }
51
79
  export declare class InverterNode extends DecoratorNode {
52
- tick(dt: number): NodeState;
80
+ tick(dt: number, context: BehaviorTreeContext): NodeState;
53
81
  }
82
+ /** At most one iteration per tick, including for the default infinite limit. */
54
83
  export declare class RepeatTimesNode extends DecoratorNode {
55
84
  private limit;
56
85
  private counter;
57
86
  constructor(child: Node, limit?: number);
58
- tick(dt: number): NodeState;
87
+ tick(dt: number, context: BehaviorTreeContext): NodeState;
88
+ stop(): void;
59
89
  }
60
90
  export declare class RepeatNode extends DecoratorNode {
61
- constructor(child: Node);
62
- tick(dt: number): NodeState;
91
+ tick(dt: number, context: BehaviorTreeContext): NodeState;
63
92
  }
64
- /**
65
- * Repeats its child node until a specific condition is met, then stops.
66
- * Useful for looping actions like searching for the player until they’re found.
67
- */
68
93
  export declare class RepeatUntilNode extends DecoratorNode {
69
94
  private condition;
70
95
  constructor(child: Node, condition: () => boolean);
71
- tick(dt: number): NodeState;
96
+ tick(dt: number, context: BehaviorTreeContext): NodeState;
72
97
  }
73
98
  export declare class RepeatUntilFailNode extends DecoratorNode {
74
- constructor(child: Node);
75
- tick(dt: number): NodeState;
99
+ tick(dt: number, context: BehaviorTreeContext): NodeState;
76
100
  }
77
- /**
78
- * Delay starting the child node.
79
- * Can be used when performing multiple actions in parallel and one should start slightly later.
80
- */
101
+ /** Delay in milliseconds before starting the child. */
81
102
  export declare class DelayNode extends DecoratorNode {
82
103
  private delay;
83
- private state;
84
104
  private elapsed;
85
105
  constructor(child: Node, delay: number);
86
- tick(dt: number): NodeState;
106
+ tick(dt: number, context: BehaviorTreeContext): NodeState;
107
+ stop(): void;
87
108
  }
88
- /**
89
- * Delay after the child node
90
- * Used to stop from making any actions.
91
- */
109
+ /** Delay in milliseconds after the child finishes, preserving its result. */
92
110
  export declare class DelayAfterNode extends DecoratorNode {
93
111
  private delay;
94
- private state;
95
112
  private elapsed;
113
+ private state;
96
114
  constructor(child: Node, delay: number);
97
- tick(dt: number): NodeState;
115
+ tick(dt: number, context: BehaviorTreeContext): NodeState;
116
+ stop(): void;
98
117
  }
118
+ /** Wait duration in milliseconds. */
99
119
  export declare class WaitNode extends LeafNode {
100
120
  private delayMin;
101
121
  private delayMax?;
@@ -104,68 +124,61 @@ export declare class WaitNode extends LeafNode {
104
124
  constructor(delayMin: number, delayMax?: number);
105
125
  tick(dt: number): NodeState;
106
126
  reset(): void;
127
+ stop(): void;
128
+ start(): void;
107
129
  }
108
- /**
109
- * Prevents its child node from executing until a cooldown period has passed.
110
- * Useful for enforcing time constraints, like ensuring an NPC cannot attack again too quickly.
111
- */
130
+ /** Cooldown in wall-clock milliseconds, preserved across interruptions. */
112
131
  export declare class CooldownNode extends DecoratorNode {
113
132
  private cooldownTime;
114
133
  private lastExecutionTime;
115
134
  constructor(child: Node, cooldownTime: number);
116
- tick(dt: number): NodeState;
135
+ tick(dt: number, context: BehaviorTreeContext): NodeState;
117
136
  }
118
- /**
119
- * Execute an action for a limited amount of time, such as searching for a player for 10 seconds before giving up.
120
- */
137
+ /** Maximum execution duration in wall-clock milliseconds. */
121
138
  export declare class TimerNode extends DecoratorNode {
122
139
  private duration;
123
140
  private startTime;
124
141
  constructor(child: Node, duration: number);
125
- tick(dt: number): NodeState;
142
+ tick(dt: number, context: BehaviorTreeContext): NodeState;
143
+ stop(): void;
126
144
  }
127
- /**
128
- * Selects a child node randomly, but with weights assigned to each node to influence the likelihood of selection.
129
- * Add randomness to NPC behavior, like having a higher chance to patrol but occasionally stopping to rest.
130
- */
145
+ /** Retains the randomly selected child until it finishes. */
131
146
  export declare class WeightedRandomSelectorNode extends CompositeNode {
147
+ private current;
132
148
  private weights;
133
149
  private totalWeight;
134
150
  constructor(weights: number[]);
135
151
  private getRandomIndex;
136
- tick(dt: number): NodeState;
152
+ tick(dt: number, context: BehaviorTreeContext): NodeState;
153
+ stop(): void;
137
154
  }
138
- /**
139
- * Executes its child node only if a condition is met.
140
- * Add conditional logic to restrict when a node should run, such as checking if the player is within a certain range.
141
- *
142
- * Compared to ConditionNode, it can be used for any node when a precondition has to be met rathern
143
- * than using sequence nodes with consitions. For example, check that the character has a specific
144
- * resource before trying.
145
- */
146
155
  export declare class GuardNode extends DecoratorNode {
147
156
  private condition;
148
157
  constructor(child: Node, condition: () => boolean);
149
- tick(dt: number): NodeState;
158
+ tick(dt: number, context: BehaviorTreeContext): NodeState;
150
159
  }
151
- /**
152
- * Executes all children in parallel and returns success as soon as any child node succeeds. It fails only if all children fail.
153
- * Perform multiple checks simultaneously, such as detecting threats from multiple sources, and succeed if any are found.
154
- */
155
- export declare class ParallelSelectorNode extends CompositeNode {
156
- tick(dt: number): NodeState;
160
+ /** Stores terminal results until the parallel execution completes. */
161
+ declare abstract class ParallelNode extends CompositeNode {
162
+ private states;
163
+ protected tickParallel(dt: number, context: BehaviorTreeContext, terminal: NodeState): NodeState;
164
+ stop(): void;
157
165
  }
158
- /**
159
- * Executes all children in parallel and only succeeds when all children succeed. It fails as soon as any child fails.
160
- * Use this for tasks that must all complete simultaneously, such as preparing a coordinated attack.
161
- */
162
- export declare class ParallelSequenceNode extends CompositeNode {
163
- tick(dt: number): NodeState;
166
+ /** Succeeds on any success; fails when all children fail. */
167
+ export declare class ParallelSelectorNode extends ParallelNode {
168
+ tick(dt: number, context: BehaviorTreeContext): NodeState;
169
+ }
170
+ /** Succeeds when all children succeed; fails on any failure. */
171
+ export declare class ParallelSequenceNode extends ParallelNode {
172
+ tick(dt: number, context: BehaviorTreeContext): NodeState;
164
173
  }
174
+ /** Duration in seconds. Skipped updates accumulate into the child's dt. */
165
175
  export declare class ThrottleNode extends DecoratorNode {
166
176
  private duration;
177
+ private elapsed;
178
+ private started;
167
179
  constructor(child: Node, duration: number);
168
- private timeSinceLastTick;
169
- tick(dt: number): NodeState;
180
+ tick(dt: number, context: BehaviorTreeContext): NodeState;
181
+ stop(): void;
170
182
  }
183
+ export {};
171
184
  //# sourceMappingURL=bt.d.ts.map
@@ -1,4 +1,4 @@
1
- export class Node{}export var NodeState;!function(t){t[t.SUCCESS=0]="SUCCESS",t[t.FAILURE=1]="FAILURE",t[t.RUNNING=2]="RUNNING"}(NodeState||(NodeState={}));export class LeafNode extends Node{}export class ActionNode extends LeafNode{constructor(t){super(),this.action=t}tick(){return this.action()}}export class SuccessNode extends LeafNode{constructor(t){super(),this.action=t}tick(){return this.action(),NodeState.SUCCESS}}export class FailNode extends LeafNode{constructor(t){super(),this.action=t}tick(){return this.action(),NodeState.FAILURE}}export class ConditionNode extends LeafNode{constructor(t){super(),this.condition=t}tick(){return this.condition()?NodeState.SUCCESS:NodeState.FAILURE}}export class CompositeNode extends Node{constructor(){super(...arguments),this.children=[]}addChild(t){this.children.push(t)}}export class SequenceNode extends CompositeNode{constructor(){super(...arguments),this.current=0}tick(t){if(0===this.children.length)return this.reset(),NodeState.SUCCESS;const e=this.children[this.current].tick(t);if(e===NodeState.FAILURE)return this.reset(),NodeState.FAILURE;if(e===NodeState.SUCCESS){if(this.current==this.children.length-1)return this.reset(),NodeState.SUCCESS;this.current++}return NodeState.SUCCESS}reset(){this.current=0}}export class SelectorNode extends CompositeNode{tick(t){for(const e of this.children){const s=e.tick(t);if(s!==NodeState.FAILURE)return s}return NodeState.FAILURE}}export class DecoratorNode extends Node{constructor(t){super(),this.child=t}}export class InverterNode extends DecoratorNode{tick(t){const e=this.child.tick(t);return e===NodeState.SUCCESS?NodeState.FAILURE:e===NodeState.FAILURE?NodeState.SUCCESS:e}}export class RepeatTimesNode extends DecoratorNode{constructor(t,e=1/0){super(t),this.counter=0,this.limit=e}tick(t){for(;this.counter<this.limit;){if(this.child.tick(t)===NodeState.RUNNING)return NodeState.RUNNING;this.counter++}return this.counter=0,NodeState.SUCCESS}}export class RepeatNode extends DecoratorNode{constructor(t){super(t)}tick(t){return this.child.tick(t),NodeState.RUNNING}}export class RepeatUntilNode extends DecoratorNode{constructor(t,e){super(t),this.condition=e}tick(t){if(this.condition())return NodeState.SUCCESS;return this.child.tick(t)===NodeState.FAILURE?NodeState.FAILURE:NodeState.RUNNING}}export class RepeatUntilFailNode extends DecoratorNode{constructor(t){super(t)}tick(t){return this.child.tick(t)===NodeState.FAILURE?NodeState.FAILURE:NodeState.RUNNING}}export class DelayNode extends DecoratorNode{constructor(t,e){super(t),this.state=NodeState.RUNNING,this.elapsed=null,this.delay=e}tick(t){return null===this.elapsed&&(this.elapsed=0),this.elapsed+=1e3*t,this.elapsed>=this.delay&&(this.state=this.child.tick(t),this.state!==NodeState.RUNNING&&(this.elapsed=null)),this.state}}export class DelayAfterNode extends DecoratorNode{constructor(t,e){super(t),this.state=NodeState.RUNNING,this.elapsed=null,this.delay=e}tick(t){if(null==this.elapsed){if(this.state=this.child.tick(t),this.state===NodeState.RUNNING)return this.state;this.elapsed=0}return this.elapsed+=1e3*t,this.elapsed<this.delay?NodeState.RUNNING:(this.elapsed=null,this.state)}}export class WaitNode extends LeafNode{constructor(t,e){super(),this.delayMin=t,this.delayMax=e,this.elapsed=null,this.delay=this.delayMin,this.reset()}tick(t){return null===this.elapsed&&(this.elapsed=0),this.elapsed+=1e3*t,this.elapsed>=this.delay?(this.elapsed=null,this.reset(),NodeState.SUCCESS):NodeState.RUNNING}reset(){null!=this.delayMax?this.delay=(this.delayMax-this.delayMin)*Math.random()+this.delayMin:this.delay=this.delayMin}}export class CooldownNode extends DecoratorNode{constructor(t,e){super(t),this.cooldownTime=e,this.lastExecutionTime=null}tick(t){const e=Date.now();if(null===this.lastExecutionTime||e-this.lastExecutionTime>=this.cooldownTime){const s=this.child.tick(t);return s===NodeState.SUCCESS&&(this.lastExecutionTime=e),s}return NodeState.FAILURE}}export class TimerNode extends DecoratorNode{constructor(t,e){super(t),this.startTime=null,this.duration=e}tick(t){null===this.startTime&&(this.startTime=Date.now());return Date.now()-this.startTime<this.duration?this.child.tick(t):(this.startTime=null,NodeState.FAILURE)}}export class WeightedRandomSelectorNode extends CompositeNode{constructor(t){super(),this.weights=t,this.totalWeight=this.weights.reduce((t,e)=>t+e,0)}getRandomIndex(){let t=Math.random()*this.totalWeight;for(let e=0;e<this.weights.length;e++)if(t-=this.weights[e],t<=0)return e;return this.weights.length-1}tick(t){const e=this.getRandomIndex();return this.children[e].tick(t)}}export class GuardNode extends DecoratorNode{constructor(t,e){super(t),this.condition=e}tick(t){return this.condition()?this.child.tick(t):NodeState.FAILURE}}export class ParallelSelectorNode extends CompositeNode{tick(t){let e=!0;for(const s of this.children){const i=s.tick(t);if(i===NodeState.SUCCESS)return NodeState.SUCCESS;i===NodeState.RUNNING&&(e=!1)}return e?NodeState.FAILURE:NodeState.RUNNING}}export class ParallelSequenceNode extends CompositeNode{tick(t){let e=!0;for(const s of this.children){const i=s.tick(t);if(i===NodeState.FAILURE)return NodeState.FAILURE;i===NodeState.RUNNING&&(e=!1)}return e?NodeState.SUCCESS:NodeState.RUNNING}}export class ThrottleNode extends DecoratorNode{constructor(t,e){super(t),this.duration=e,this.timeSinceLastTick=0}tick(t){return 0===this.timeSinceLastTick||this.timeSinceLastTick>this.duration?(this.timeSinceLastTick=t,this.child.tick(this.timeSinceLastTick)):(this.timeSinceLastTick+=t,NodeState.RUNNING)}}/*
1
+ export var NodeState;!function(t){t[t.SUCCESS=0]="SUCCESS",t[t.FAILURE=1]="FAILURE",t[t.RUNNING=2]="RUNNING"}(NodeState||(NodeState={}));export class Node{start(){}stop(t){}getChildren(){return[]}}export class LeafNode extends Node{}export class ActionNode extends LeafNode{constructor(t){super(),this.action=t}tick(){return this.action()}}export class SuccessNode extends LeafNode{constructor(t){super(),this.action=t}tick(){return this.action(),NodeState.SUCCESS}}export class FailNode extends LeafNode{constructor(t){super(),this.action=t}tick(){return this.action(),NodeState.FAILURE}}export class AsyncActionNode extends LeafNode{constructor(t){super(),this.action=t,this.execution=0}start(){this.result=void 0;const t=++this.execution;try{this.action().then(e=>this.finish(t,e),()=>this.finish(t,NodeState.FAILURE))}catch{this.result=NodeState.FAILURE}}tick(){return this.result??NodeState.RUNNING}finish(t,e){t===this.execution&&(this.result=e)}stop(){this.execution++,this.result=void 0}}export class ConditionNode extends LeafNode{constructor(t){super(),this.condition=t}tick(){return this.condition()?NodeState.SUCCESS:NodeState.FAILURE}}export class CompositeNode extends Node{constructor(){super(...arguments),this.children=[]}getChildren(){return this.children}addChild(t){this.children.push(t)}}export class SequenceNode extends CompositeNode{constructor(){super(...arguments),this.current=0}tick(t,e){for(;this.current<this.children.length;){const s=e.execute(this.children[this.current],t);if(s===NodeState.RUNNING)return s;if(s===NodeState.FAILURE)return this.current=0,s;this.current++}return this.current=0,NodeState.SUCCESS}stop(){this.current=0}}export class SelectorNode extends CompositeNode{tick(t,e){for(const s of this.children){const i=e.execute(s,t);if(i!==NodeState.FAILURE)return this.runningChild!==s&&this.runningChild&&e.abort(this.runningChild),this.runningChild=i===NodeState.RUNNING?s:void 0,i;this.runningChild===s&&(this.runningChild=void 0)}return NodeState.FAILURE}stop(){this.runningChild=void 0}}export class DecoratorNode extends Node{constructor(t){super(),this.child=t}getChildren(){return[this.child]}}export class InverterNode extends DecoratorNode{tick(t,e){const s=e.execute(this.child,t);return s===NodeState.SUCCESS?NodeState.FAILURE:s===NodeState.FAILURE?NodeState.SUCCESS:s}}export class RepeatTimesNode extends DecoratorNode{constructor(t,e=1/0){super(t),this.limit=e,this.counter=0}tick(t,e){if(this.counter>=this.limit)return this.counter=0,NodeState.SUCCESS;const s=e.execute(this.child,t);return s===NodeState.RUNNING?s:(this.counter++,this.counter>=this.limit?(this.counter=0,NodeState.SUCCESS):NodeState.RUNNING)}stop(){this.counter=0}}export class RepeatNode extends DecoratorNode{tick(t,e){return e.execute(this.child,t),NodeState.RUNNING}}export class RepeatUntilNode extends DecoratorNode{constructor(t,e){super(t),this.condition=e}tick(t,e){return this.condition()?(e.abort(this.child),NodeState.SUCCESS):e.execute(this.child,t)===NodeState.FAILURE?NodeState.FAILURE:NodeState.RUNNING}}export class RepeatUntilFailNode extends DecoratorNode{tick(t,e){return e.execute(this.child,t)===NodeState.FAILURE?NodeState.FAILURE:NodeState.RUNNING}}export class DelayNode extends DecoratorNode{constructor(t,e){super(t),this.delay=e,this.elapsed=0}tick(t,e){if(this.elapsed+=1e3*t,this.elapsed<this.delay)return NodeState.RUNNING;const s=e.execute(this.child,t);return s!==NodeState.RUNNING&&(this.elapsed=0),s}stop(){this.elapsed=0}}export class DelayAfterNode extends DecoratorNode{constructor(t,e){super(t),this.delay=e,this.elapsed=null,this.state=NodeState.RUNNING}tick(t,e){if(null===this.elapsed){if(this.state=e.execute(this.child,t),this.state===NodeState.RUNNING)return this.state;if(this.elapsed=0,this.delay>0)return NodeState.RUNNING}else this.elapsed+=1e3*t;return this.elapsed<this.delay?NodeState.RUNNING:(this.elapsed=null,this.state)}stop(){this.elapsed=null,this.state=NodeState.RUNNING}}export class WaitNode extends LeafNode{constructor(t,e){super(),this.delayMin=t,this.delayMax=e,this.elapsed=0,this.delay=t}tick(t){return this.elapsed+=1e3*t,this.elapsed<this.delay?NodeState.RUNNING:(this.elapsed=0,NodeState.SUCCESS)}reset(){this.elapsed=0,this.delay=null==this.delayMax?this.delayMin:(this.delayMax-this.delayMin)*Math.random()+this.delayMin}stop(){this.elapsed=0}start(){this.reset()}}export class CooldownNode extends DecoratorNode{constructor(t,e){super(t),this.cooldownTime=e,this.lastExecutionTime=null}tick(t,e){const s=Date.now();if(null!==this.lastExecutionTime&&s-this.lastExecutionTime<this.cooldownTime)return NodeState.FAILURE;const i=e.execute(this.child,t);return i===NodeState.SUCCESS&&(this.lastExecutionTime=s),i}}export class TimerNode extends DecoratorNode{constructor(t,e){super(t),this.duration=e,this.startTime=null}tick(t,e){const s=Date.now();if(this.startTime??(this.startTime=s),s-this.startTime>=this.duration)return e.abort(this.child),NodeState.FAILURE;const i=e.execute(this.child,t);return i!==NodeState.RUNNING&&(this.startTime=null),i}stop(){this.startTime=null}}export class WeightedRandomSelectorNode extends CompositeNode{constructor(t){if(super(),t.some(t=>!Number.isFinite(t)||t<0))throw new Error("Selector weights must be finite and non-negative");if(this.weights=t.slice(),this.totalWeight=this.weights.reduce((t,e)=>t+e,0),!Number.isFinite(this.totalWeight))throw new Error("Selector total weight must be finite")}getRandomIndex(){let t=Math.random()*this.totalWeight;for(let e=0;e<this.weights.length;e++)if(t-=this.weights[e],t<0)return e;for(let t=this.weights.length-1;t>=0;t--)if(this.weights[t]>0)return t;return-1}tick(t,e){if(this.children.length!==this.weights.length||this.totalWeight<=0)return NodeState.FAILURE;this.current??(this.current=this.getRandomIndex());const s=e.execute(this.children[this.current],t);return s!==NodeState.RUNNING&&(this.current=void 0),s}stop(){this.current=void 0}}export class GuardNode extends DecoratorNode{constructor(t,e){super(t),this.condition=e}tick(t,e){return this.condition()?e.execute(this.child,t):(e.abort(this.child),NodeState.FAILURE)}}class t extends CompositeNode{constructor(){super(...arguments),this.states=[]}tickParallel(t,e,s){let i=!1;for(let o=0;o<this.children.length;o++){const r=this.states[o],d=void 0===r||r===NodeState.RUNNING?e.execute(this.children[o],t):r;if(this.states[o]=d,d===s)return s;d===NodeState.RUNNING&&(i=!0)}return i?NodeState.RUNNING:(this.states.length=0,s===NodeState.SUCCESS?NodeState.FAILURE:NodeState.SUCCESS)}stop(){this.states.length=0}}export class ParallelSelectorNode extends t{tick(t,e){return this.tickParallel(t,e,NodeState.SUCCESS)}}export class ParallelSequenceNode extends t{tick(t,e){return this.tickParallel(t,e,NodeState.FAILURE)}}export class ThrottleNode extends DecoratorNode{constructor(t,e){super(t),this.duration=e,this.elapsed=0,this.started=!1}tick(t,e){if(this.elapsed+=t,this.started&&this.elapsed<this.duration)return e.retain(this.child),NodeState.RUNNING;this.started=!0;const s=this.elapsed;this.elapsed=0;const i=e.execute(this.child,s);return i!==NodeState.RUNNING&&(this.started=!1),i}stop(){this.elapsed=0,this.started=!1}}/*
2
2
  * Copyright (©) 2026 Hology Interactive AB. All rights reserved.
3
3
  * See the LICENSE.md file for details.
4
4
  */
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=bt.test.d.ts.map
@@ -0,0 +1,4 @@
1
+ import{BehaviorTree as e}from"./behavior-tree.js";import{describe as t,expect as n,it as o,vi as s}from"vitest";import{AsyncActionNode as a,DelayAfterNode as r,DelayNode as i,GuardNode as l,Node as c,NodeState as d,ParallelSelectorNode as w,ParallelSequenceNode as C,RepeatTimesNode as S,RepeatUntilNode as m,SelectorNode as B,SequenceNode as N,ThrottleNode as U,TimerNode as u,WaitNode as p,WeightedRandomSelectorNode as R}from"./bt.js";class E extends c{constructor(e=d.RUNNING){super(),this.state=e,this.tick=s.fn(e=>this.state),this.start=s.fn(),this.stop=s.fn()}}const h=new WeakMap;function v(t){let n=h.get(t);return n||(n=new e(t),h.set(t,n)),n}function k(e,t=0){return v(e).tick(t)}function I(e,...t){return t.forEach(t=>e.addChild(t)),e}t("behavior tree execution",()=>{o("resumes a sequence and only reports success after all steps complete",()=>{const e=new E(d.SUCCESS),t=new E,o=new E(d.SUCCESS),s=I(new N,e,t,o);n(k(s,.1)).toBe(d.RUNNING),n(o.tick).not.toHaveBeenCalled(),t.state=d.SUCCESS,n(k(s,.1)).toBe(d.SUCCESS),n(e.tick).toHaveBeenCalledTimes(1),n(o.tick).toHaveBeenCalledTimes(1)}),o("restarts interrupted sequences from the beginning",()=>{const e=new E(d.SUCCESS),t=new E,o=I(new N,e,t);k(o,.1),v(o).abort(),n(t.stop).toHaveBeenCalledTimes(1),k(o,.1),n(e.tick).toHaveBeenCalledTimes(2)}),o("keeps selectors reactive and aborts the displaced branch once",()=>{const e=new E(d.FAILURE),t=new E,o=I(new B,e,t);k(o,.1),e.state=d.RUNNING,k(o,.1),k(o,.1),n(t.stop).toHaveBeenCalledTimes(1),n(t.tick).toHaveBeenCalledTimes(1),v(o).abort(),n(e.stop).toHaveBeenLastCalledWith("aborted")}),o("retains random choices and rejects unusable weights safely",()=>{const e=s.spyOn(Math,"random").mockReturnValue(0);try{const t=new E,o=new E,s=I(new R([1,1]),t,o);k(s,.1),e.mockReturnValue(.9),k(s,.1),n(o.tick).not.toHaveBeenCalled(),t.state=d.SUCCESS,k(s,.1),k(s,.1),n(o.tick).toHaveBeenCalledTimes(1),n(k(new R([]),.1)).toBe(d.FAILURE),n(()=>new R([-1])).toThrow()}finally{e.mockRestore()}}),o("does not repeat terminal parallel children and cancels unfinished siblings",()=>{const e=new E(d.SUCCESS),t=new E,o=I(new C,e,t);k(o,.1),k(o,.1),n(e.tick).toHaveBeenCalledTimes(1),t.state=d.FAILURE,n(k(o,.1)).toBe(d.FAILURE),e.state=d.RUNNING,t.state=d.SUCCESS;const s=I(new w,e,t);n(k(s,.1)).toBe(d.SUCCESS),n(e.stop).toHaveBeenLastCalledWith("aborted");const a=new E(d.FAILURE),r=new E,i=I(new w,a,r);k(i,.1),k(i,.1),n(a.tick).toHaveBeenCalledTimes(1)}),o("cancels guarded and repeated tasks when their conditions change",()=>{let e=!0;const t=new E,o=new l(t,()=>e);k(o,.1),e=!1,k(o,.1),k(o,.1),n(t.stop).toHaveBeenCalledTimes(1);const s=new m(t,()=>e);k(s,.1),e=!0,n(k(s,.1)).toBe(d.SUCCESS),n(t.stop).toHaveBeenCalledTimes(2)}),o("clears interrupted waits and does not return a stale delay result",()=>{const e=new p(100);k(e,.06),v(e).abort(),n(k(e,.06)).toBe(d.RUNNING),n(k(e,.04)).toBe(d.SUCCESS);const t=new i(new E(d.SUCCESS),100);n(k(t,.1)).toBe(d.SUCCESS),n(k(t,.05)).toBe(d.RUNNING),v(t).abort(),n(k(t,.05)).toBe(d.RUNNING)}),o("delays after completion without counting time before completion",()=>{const e=new r(new E(d.FAILURE),100);n(k(e,1)).toBe(d.RUNNING),n(k(e,.05)).toBe(d.RUNNING),n(k(e,.05)).toBe(d.FAILURE),n(k(e,1)).toBe(d.RUNNING)}),o("bounds repeat work even with the default infinite limit",()=>{const e=new E(d.SUCCESS);n(k(new S(e),.1)).toBe(d.RUNNING),n(e.tick).toHaveBeenCalledTimes(1);const t=new S(e,2);n(k(t,.1)).toBe(d.RUNNING),n(k(t,.1)).toBe(d.SUCCESS)}),o("passes accumulated elapsed time to throttled running tasks",()=>{const e=new E,t=new U(e,.3);k(t,.1),k(t,.1),k(t,.1),k(t,.1),n(e.tick).toHaveBeenCalledTimes(2),n(e.tick.mock.calls[1][0]).toBeCloseTo(.3),v(t).abort(),n(e.stop).toHaveBeenCalledTimes(1)}),o("resets timer deadlines after completion and aborts on timeout",()=>{const e=s.spyOn(Date,"now").mockReturnValue(0);try{const t=new E(d.SUCCESS),o=new u(t,100);n(k(o,.1)).toBe(d.SUCCESS),e.mockReturnValue(200),t.state=d.RUNNING,n(k(o,.1)).toBe(d.RUNNING),e.mockReturnValue(300),n(k(o,.1)).toBe(d.FAILURE),n(t.stop).toHaveBeenLastCalledWith("aborted")}finally{e.mockRestore()}}),o("starts async work once and ignores completion from an aborted execution",async()=>{let e;const t=s.fn(()=>new Promise(t=>{e=t})),o=new a(t);k(o),k(o),n(t).toHaveBeenCalledTimes(1);const r=e;v(o).abort(),k(o),r(d.SUCCESS),await Promise.resolve(),n(k(o)).toBe(d.RUNNING),e(d.FAILURE),await Promise.resolve(),n(k(o)).toBe(d.FAILURE),k(o),n(t).toHaveBeenCalledTimes(3)}),o("reports async rejections and synchronous throws as failure",async()=>{const e=new a(()=>Promise.reject(new Error("failed")));k(e),await Promise.resolve(),n(k(e)).toBe(d.FAILURE),n(k(new a(()=>{throw new Error("failed")}))).toBe(d.FAILURE)})});/*
2
+ * Copyright (©) 2026 Hology Interactive AB. All rights reserved.
3
+ * See the LICENSE.md file for details.
4
+ */
@@ -23,6 +23,7 @@ export declare class CharacterMoveToNode extends LeafNode {
23
23
  constructor(navigation: Navigation, movement: CharacterMovementLike);
24
24
  private cardinalAxis?;
25
25
  tick(dt: number): NodeState;
26
+ stop(): void;
26
27
  private snapInputToCardinalAxis;
27
28
  }
28
29
  //# sourceMappingURL=move.d.ts.map
@@ -1,4 +1,4 @@
1
- import{Vector3 as t}from"three";import{LeafNode as i,NodeState as e}from"./bt.js";export class CharacterMoveToNode extends i{constructor(t,i){super(),this.navigation=t,this.movement=i,this.distanceFrom=.5,this.speedScale=1,this.lateralSpeedScale=1,this.cardinalMovement=!1}tick(t){const i=this.movement.actor.object.position,s=null!=this.target?this.target():null;if(null==s)return this.movement.directionInput.vector.set(0,0),this.cardinalAxis=void 0,e.FAILURE;if(i.distanceTo(s)<this.distanceFrom)return this.movement.directionInput.vector.set(0,0),this.cardinalAxis=void 0,e.SUCCESS;const{success:a,path:o}=this.navigation.findPath(i,s);if(!a||o.length<2)return this.movement.directionInput.vector.set(0,0),this.cardinalAxis=void 0,e.FAILURE;const r=o[1],c=n.subVectors(r,i);c.y=0,c.normalize();const h=this.facingTarget?.(),l=null!=h?h.x-i.x:0,m=null!=h?h.z-i.z:0,d=l*l+m*m;if(null!=h&&d>1e-4){const t=Math.atan2(l,m),i=1/Math.sqrt(d),e=l*i,n=m*i,s=n,a=-e;this.movement.rotationInput.rotation.y=t,this.movement.directionInput.vector.set(-(c.x*s+c.z*a),c.x*e+c.z*n)}else this.movement.rotationInput.rotation.y=Math.atan2(c.x,c.z),this.movement.directionInput.vector.set(0,1);return this.cardinalMovement&&this.snapInputToCardinalAxis(),Math.abs(this.movement.directionInput.vector.x)>Math.abs(this.movement.directionInput.vector.y)&&this.movement.directionInput.vector.multiplyScalar(Math.max(0,Math.min(1,this.lateralSpeedScale))),this.movement.directionInput.vector.multiplyScalar(Math.max(0,Math.min(1,this.speedScale))),e.RUNNING}snapInputToCardinalAxis(){const t=this.movement.directionInput.vector,i=Math.abs(t.x),e=Math.abs(t.y);if(i+e<=1e-4)return void(this.cardinalAxis=void 0);const n=i>e?"x":"y";if(null==this.cardinalAxis)this.cardinalAxis=n;else{const t="x"===this.cardinalAxis?i:e;("x"===this.cardinalAxis?e:i)>t*s&&(this.cardinalAxis="x"===this.cardinalAxis?"y":"x")}"x"===this.cardinalAxis?t.set(Math.sign(t.x),0):t.set(0,Math.sign(t.y))}}const n=new t,s=1.25;/*
1
+ import{Vector3 as t}from"three";import{LeafNode as i,NodeState as e}from"./bt.js";export class CharacterMoveToNode extends i{constructor(t,i){super(),this.navigation=t,this.movement=i,this.distanceFrom=.5,this.speedScale=1,this.lateralSpeedScale=1,this.cardinalMovement=!1}tick(t){const i=this.movement.actor.object.position,s=null!=this.target?this.target():null;if(null==s)return this.movement.directionInput.vector.set(0,0),this.cardinalAxis=void 0,e.FAILURE;if(i.distanceTo(s)<this.distanceFrom)return this.movement.directionInput.vector.set(0,0),this.cardinalAxis=void 0,e.SUCCESS;const{success:a,path:o}=this.navigation.findPath(i,s);if(!a||o.length<2)return this.movement.directionInput.vector.set(0,0),this.cardinalAxis=void 0,e.FAILURE;const r=o[1],c=n.subVectors(r,i);c.y=0,c.normalize();const h=this.facingTarget?.(),l=null!=h?h.x-i.x:0,m=null!=h?h.z-i.z:0,d=l*l+m*m;if(null!=h&&d>1e-4){const t=Math.atan2(l,m),i=1/Math.sqrt(d),e=l*i,n=m*i,s=n,a=-e;this.movement.rotationInput.rotation.y=t,this.movement.directionInput.vector.set(-(c.x*s+c.z*a),c.x*e+c.z*n)}else this.movement.rotationInput.rotation.y=Math.atan2(c.x,c.z),this.movement.directionInput.vector.set(0,1);return this.cardinalMovement&&this.snapInputToCardinalAxis(),Math.abs(this.movement.directionInput.vector.x)>Math.abs(this.movement.directionInput.vector.y)&&this.movement.directionInput.vector.multiplyScalar(Math.max(0,Math.min(1,this.lateralSpeedScale))),this.movement.directionInput.vector.multiplyScalar(Math.max(0,Math.min(1,this.speedScale))),e.RUNNING}stop(){this.cardinalAxis=void 0}snapInputToCardinalAxis(){const t=this.movement.directionInput.vector,i=Math.abs(t.x),e=Math.abs(t.y);if(i+e<=1e-4)return void(this.cardinalAxis=void 0);const n=i>e?"x":"y";if(null==this.cardinalAxis)this.cardinalAxis=n;else{const t="x"===this.cardinalAxis?i:e;("x"===this.cardinalAxis?e:i)>t*s&&(this.cardinalAxis="x"===this.cardinalAxis?"y":"x")}"x"===this.cardinalAxis?t.set(Math.sign(t.x),0):t.set(0,Math.sign(t.y))}}const n=new t,s=1.25;/*
2
2
  * Copyright (©) 2026 Hology Interactive AB. All rights reserved.
3
3
  * See the LICENSE.md file for details.
4
4
  */
@@ -2,4 +2,5 @@ export * from './navigation.js';
2
2
  export * from './navigation-areas.js';
3
3
  export * from './behavior-tree/bt.js';
4
4
  export * from './behavior-tree/move.js';
5
+ export * from './behavior-tree/behavior-tree.js';
5
6
  //# sourceMappingURL=index.d.ts.map
@@ -1,4 +1,4 @@
1
- export*from"./navigation.js";export*from"./navigation-areas.js";export*from"./behavior-tree/bt.js";export*from"./behavior-tree/move.js";/*
1
+ export*from"./navigation.js";export*from"./navigation-areas.js";export*from"./behavior-tree/bt.js";export*from"./behavior-tree/move.js";export*from"./behavior-tree/behavior-tree.js";/*
2
2
  * Copyright (©) 2026 Hology Interactive AB. All rights reserved.
3
3
  * See the LICENSE.md file for details.
4
4
  */
@@ -58,6 +58,9 @@ export declare class ViewController {
58
58
  registerWebGpuRenderGraphExtension(extension: ExperimentalWebGpuRenderGraphExtension): () => void;
59
59
  /** @deprecated Use registerWebGpuRenderGraphExtension. */
60
60
  registerExperimentalWebGpuRenderGraphExtension(extension: ExperimentalWebGpuRenderGraphExtension): () => void;
61
+ private _updateFrameIndex;
62
+ /** Monotonic frame token shared by per-frame gameplay preparation jobs. */
63
+ get updateFrameIndex(): number;
61
64
  private handleFrame;
62
65
  set timeScale(value: number);
63
66
  get timeScale(): number;
@@ -1,4 +1,4 @@
1
- import{__decorate as e,__metadata as t}from"tslib";import{Service as a}from"typedi";import{Camera as i}from"three";import{RenderingView as r}from"../../rendering.js";import{BehaviorSubject as s,Subject as n,takeUntil as h}from"rxjs";import*as o from"three";import{GameTimeScheduler as m}from"./game-time-scheduler.js";import{createCameraShakeSample as l,resetCameraShakeSample as u,sampleCameraShake as c}from"./camera-shake.js";export{defaultPostProcessEffectStage,postProcessEffectStages}from"../../rendering/post-process-effect.js";let d=class{constructor(e){this.view=e,this.tick=new n,this.lateTick=new n,this.audioListener=new o.AudioListener,this.pausedChanged=new s(!1),this.mutedChanged=new s(!1),this._muted=!1,this._masterVolume=1,this._cameraShakeIntensity=1,this._timeScale=1,this._hitStopRemaining=0,this.scheduler=new m,this.cameraOverrideId=0,this.cameraOverrides=[],this.cameraShakeId=0,this.gameplayCameraShakes=[],this.cameraShakeContributions=new Map,this.presentationCamera=null,this.accumulatedCameraShakeSample=l(),this.scratchCameraShakeSample=l(),this.outlined=[],this.baseCamera=e.camera,this.sourceCamera=e.camera,e.onLoop(e=>{this.handleFrame(e)}),e.camera.add(this.audioListener),window.hology_view=this}get activeBackend(){return this.view.activeBackend}get requestedBackend(){return this.view.requestedBackend}get vfxBackendPreference(){return this.view.vfxBackendPreference}get webgpuVfxInstancesPerTemplate(){return this.view.webgpuVfxInstancesPerTemplate}get webgpuVfxTemplateMemoryBudgetBytes(){return this.view.webgpuVfxTemplateMemoryBudgetBytes}get webgpuVfxWorldMemoryBudgetBytes(){return this.view.webgpuVfxWorldMemoryBudgetBytes}get webgpuVfxColdTemplateSeconds(){return this.view.webgpuVfxColdTemplateSeconds}get webGpuDevice(){return this.view.webGpuDevice}get experimentalWebGpuDevice(){return this.webGpuDevice}registerWebGpuRenderGraphExtension(e){return this.view.registerWebGpuRenderGraphExtension(e)}registerExperimentalWebGpuRenderGraphExtension(e){return this.registerWebGpuRenderGraphExtension(e)}handleFrame(e){let t=this._timeScale,a=0;this._hitStopRemaining>0?(this._hitStopRemaining-=1e3*e,this._hitStopRemaining<=0&&(this._hitStopRemaining=0),t=0):a=e*this._timeScale,this.advanceGameplayCameraShakes(a),this.tick.next(a),this.scheduler.update(1e3*a),this.lateTick.next(a),this.view.simulationTimeScale=t,this.refreshCameraPresentation()}set timeScale(e){this._timeScale=Math.max(0,e)}get timeScale(){return this._timeScale}applyHitStop(e){this._hitStopRemaining=Math.max(this._hitStopRemaining,e)}set fpsCap(e){this.view.fpsCap=e}get fpsCap(){return this.view.fpsCap}set showStats(e){this.view.showStats=e}get showStats(){return this.view.showStats}setProfilingEnabled(e){this.view.setProfilingEnabled(e)}getLastFrameProfile(){return this.view.getLastFrameProfile()}setLightVolume(e){this.view.lightVolume=e}getLightVolume(){return this.view.lightVolume}addPostProcessVolume(e){this.view.addPostProcessVolume(e)}removePostProcessVolume(e){this.view.removePostProcessVolume(e)}addPostProcessEffect(e,t){return this.view.addPostProcessEffect(e,t)}onUpdate(e){return null==e?this.tick:this.tick.pipe(h(e.disposed))}onLateUpdate(e){return null==e?this.lateTick:this.lateTick.pipe(h(e.disposed))}setCamera(e){const t=e instanceof i?e:e.camera.instance;this.baseCamera=t,0===this.cameraOverrides.length&&(this.sourceCamera=t,this.refreshCameraPresentation())}getCamera(){return this.view.camera}getSourceCamera(){return this.sourceCamera}getBaseCamera(){return this.baseCamera}applyCameraShake(e){const t=function(e){return{duration:Math.max(0,e.duration??0),attack:e.attack??0,decay:e.decay??0,frequency:e.frequency??24,positionAmplitude:e.positionAmplitude?[...e.positionAmplitude]:[0,0,0],rotationAmplitude:e.rotationAmplitude?[...e.rotationAmplitude]:[0,0,0],seed:e.seed}}(e);if(t.duration<=0)return{release:()=>{}};const a={id:++this.cameraShakeId,request:t,elapsedTime:0,seedOverride:t.seed};this.gameplayCameraShakes.push(a),this.refreshCameraPresentation();let i=!1;return{release:()=>{i||(i=!0,this.releaseGameplayCameraShake(a.id))}}}setCameraShakeContribution(e,t,a,i,r){if(a.duration<=0)return void this.removeCameraShakeContribution(e,t);let s=this.cameraShakeContributions.get(e);s||(s=new Map,this.cameraShakeContributions.set(e,s));const n=s.get(t);n?(n.request=a,n.elapsedTime=Math.max(0,i),n.seedOverride=r):s.set(t,{key:t,request:a,elapsedTime:Math.max(0,i),seedOverride:r}),this.refreshCameraPresentation()}removeCameraShakeContribution(e,t){const a=this.cameraShakeContributions.get(e);a?.delete(t)&&(0===a.size&&this.cameraShakeContributions.delete(e),this.refreshCameraPresentation())}clearCameraShakeContributionsForOwner(e){this.cameraShakeContributions.delete(e)&&this.refreshCameraPresentation()}pushCameraOverride(e,t,a=0){const i={id:++this.cameraOverrideId,camera:e,owner:t,priority:a};this.cameraOverrides.push(i),this.applyTopCameraOverride();let r=!1;return{release:()=>{r||(r=!0,this.releaseCameraOverrideEntry(i))}}}releaseCameraOverridesForOwner(e){const t=this.cameraOverrides.filter(t=>t.owner!==e);t.length!==this.cameraOverrides.length&&(this.cameraOverrides=t,this.applyTopCameraOverride())}releaseCameraOverrideEntry(e){const t=this.cameraOverrides.indexOf(e);-1!==t&&(this.cameraOverrides.splice(t,1),this.applyTopCameraOverride())}applyTopCameraOverride(){const e=this.getTopCameraOverride();this.sourceCamera=e?.camera??this.baseCamera,this.refreshCameraPresentation()}getTopCameraOverride(){let e;for(const t of this.cameraOverrides)(!e||t.priority>e.priority||t.priority===e.priority&&t.id>e.id)&&(e=t);return e}applyRenderedCamera(e){this.view.camera!==e&&this.view.setCamera(e),e.add(this.audioListener)}releaseGameplayCameraShake(e){const t=this.gameplayCameraShakes.findIndex(t=>t.id===e);-1!==t&&(this.gameplayCameraShakes.splice(t,1),this.refreshCameraPresentation())}advanceGameplayCameraShakes(e){if(!(e<=0||0===this.gameplayCameraShakes.length))for(let t=this.gameplayCameraShakes.length-1;t>=0;t--){const a=this.gameplayCameraShakes[t];a.elapsedTime+=e,a.elapsedTime>a.request.duration&&this.gameplayCameraShakes.splice(t,1)}}refreshCameraPresentation(){if(!this.hasActiveCameraShake())return void this.applyRenderedCamera(this.sourceCamera);const e=this.ensurePresentationCamera();this.copyCameraState(this.sourceCamera,e),this.accumulateCameraShakeSamples(),this.applyCameraShakeSample(e),this.applyRenderedCamera(e)}hasActiveCameraShake(){if(this.gameplayCameraShakes.length>0)return!0;for(const e of this.cameraShakeContributions.values())if(e.size>0)return!0;return!1}ensurePresentationCamera(){return this.presentationCamera||(this.presentationCamera=new o.PerspectiveCamera,this.presentationCamera.name="View Controller Presentation Camera"),this.presentationCamera}copyCameraState(e,t){e.updateWorldMatrix(!0,!1),t.position.copy(e.getWorldPosition(S)),t.quaternion.copy(e.getWorldQuaternion(w)),t.scale.copy(e.getWorldScale(f)),t.layers.mask=e.layers.mask,e instanceof o.PerspectiveCamera?(t.fov=e.fov,t.near=e.near,t.far=e.far,t.aspect=e.aspect,t.zoom=e.zoom,t.focus=e.focus,t.filmGauge=e.filmGauge,t.filmOffset=e.filmOffset,t.view=null==e.view?null:{enabled:e.view.enabled,fullWidth:e.view.fullWidth,fullHeight:e.view.fullHeight,offsetX:e.view.offsetX,offsetY:e.view.offsetY,width:e.view.width,height:e.view.height},t.updateProjectionMatrix()):(t.projectionMatrix.copy(e.projectionMatrix),t.projectionMatrixInverse.copy(e.projectionMatrixInverse))}accumulateCameraShakeSamples(){u(this.accumulatedCameraShakeSample);for(let e=0;e<this.gameplayCameraShakes.length;e++)this.accumulateCameraShakeEntry(this.gameplayCameraShakes[e]);for(const e of this.cameraShakeContributions.values())for(const t of e.values())this.accumulateCameraShakeEntry(t)}accumulateCameraShakeEntry(e){const t=c(e.request,e.elapsedTime,this.scratchCameraShakeSample,e.seedOverride);if(t)for(let e=0;e<3;e++)this.accumulatedCameraShakeSample.position[e]+=t.position[e],this.accumulatedCameraShakeSample.rotation[e]+=t.rotation[e]}applyCameraShakeSample(e){k.copy(e.quaternion),y.set(this.accumulatedCameraShakeSample.position[0]*this._cameraShakeIntensity,this.accumulatedCameraShakeSample.position[1]*this._cameraShakeIntensity,this.accumulatedCameraShakeSample.position[2]*this._cameraShakeIntensity),y.applyQuaternion(k),e.position.add(y),x.set(this.accumulatedCameraShakeSample.rotation[0]*this._cameraShakeIntensity,this.accumulatedCameraShakeSample.rotation[1]*this._cameraShakeIntensity,this.accumulatedCameraShakeSample.rotation[2]*this._cameraShakeIntensity),P.setFromEuler(x),e.quaternion.copy(k).multiply(P),e.updateMatrixWorld(!0)}setMuted(e){this._muted=e,this.applyAudioGain(),this.mutedChanged.next(e)}getMuted(){return this._muted}set masterVolume(e){this._masterVolume=Math.min(Math.max(e,0),1),this.applyAudioGain()}get masterVolume(){return this._masterVolume}set cameraShakeIntensity(e){this._cameraShakeIntensity=Math.min(Math.max(e,0),1),this.refreshCameraPresentation()}get cameraShakeIntensity(){return this._cameraShakeIntensity}applyAudioGain(){this.audioListener.gain.gain.setValueAtTime(this._muted?0:this._masterVolume,this.audioListener.context.currentTime)}setOutlined(e){this.outlined.length=0;const t=this.outlined;for(let a=0;a<e.length;a++)t[a]=e[a];this.view.setSelectedObjects(t),this.view.setEnableOutlines(e.length>0)}getOutlined(){return this.outlined}addOutlined(e){this.outlined.includes(e)||(this.outlined.push(e),this.view.setSelectedObjects(this.outlined),this.view.setEnableOutlines(!0))}removeOutlined(e){const t=this.outlined.indexOf(e);-1!==t&&(this.outlined.splice(t,1),this.view.setSelectedObjects(this.outlined))}setOutlineColor(e){this.view.outlinePass?.visibleEdgeColor.copy(e)}getOutlineColor(){return this.view.outlinePass?.visibleEdgeColor}setOutlineThickness(e){this.view.outlinePass.edgeThickness=e}getOutlineThickness(){return this.view.outlinePass.edgeThickness}get htmlElement(){return this.view.container}getViewportSize(e=g){return this.view.getClientSize(e)}get paused(){return this.view.paused}set paused(e){e!=this.paused&&(e?this.pauseRendering():this.unpauseRendering())}pauseRendering(){this.view.paused=!0,this.pausedChanged.next(this.view.paused),this.scheduler.pause()}unpauseRendering(){this.view.paused=!1,this.pausedChanged.next(this.view.paused),this.scheduler.resume()}setInterval(e,t,a){return this.scheduler.setInterval(e,t,a)}clearInterval(e){this.scheduler.clearInterval(e)}dispose(e=!0){e&&this.view.running&&this.view.stop(),this.scheduler.dispose(),this.cameraOverrides.length=0,this.gameplayCameraShakes.length=0,this.cameraShakeContributions.clear(),this.audioListener.removeFromParent(),this.tick.complete(),this.lateTick.complete(),this.paused=!0}createTimer(e){return this.scheduler.createTimer(e)}getScreenPosition(e,t=C,a=this.getViewportSize()){const i=this.getCamera();return p.multiplyMatrices(i.projectionMatrix,i.matrixWorldInverse),i instanceof o.PerspectiveCamera&&(v.setFromProjectionMatrix(p),!v.containsPoint(e))?null:(t.copy(e),t.project(i),t.x=(t.x+1)/2*a.x,t.y=(1-t.y)/2*a.y,t)}};d=e([a(),t("design:paramtypes",[r])],d);export{d as ViewController};const p=new o.Matrix4,C=new o.Vector3,g=new o.Vector2,v=new o.Frustum,S=new o.Vector3,f=new o.Vector3,w=new o.Quaternion,k=new o.Quaternion,y=new o.Vector3,x=new o.Euler(0,0,0,"XYZ"),P=new o.Quaternion;/*
1
+ import{__decorate as e,__metadata as t}from"tslib";import{Service as a}from"typedi";import{Camera as i}from"three";import{RenderingView as r}from"../../rendering.js";import{BehaviorSubject as s,Subject as n,takeUntil as h}from"rxjs";import*as o from"three";import{GameTimeScheduler as m}from"./game-time-scheduler.js";import{createCameraShakeSample as u,resetCameraShakeSample as l,sampleCameraShake as c}from"./camera-shake.js";export{defaultPostProcessEffectStage,postProcessEffectStages}from"../../rendering/post-process-effect.js";let d=class{constructor(e){this.view=e,this.tick=new n,this.lateTick=new n,this.audioListener=new o.AudioListener,this.pausedChanged=new s(!1),this.mutedChanged=new s(!1),this._muted=!1,this._masterVolume=1,this._cameraShakeIntensity=1,this._timeScale=1,this._hitStopRemaining=0,this.scheduler=new m,this.cameraOverrideId=0,this.cameraOverrides=[],this.cameraShakeId=0,this.gameplayCameraShakes=[],this.cameraShakeContributions=new Map,this.presentationCamera=null,this.accumulatedCameraShakeSample=u(),this.scratchCameraShakeSample=u(),this._updateFrameIndex=0,this.outlined=[],this.baseCamera=e.camera,this.sourceCamera=e.camera,e.onLoop(e=>{this.handleFrame(e)}),e.camera.add(this.audioListener),window.hology_view=this}get activeBackend(){return this.view.activeBackend}get requestedBackend(){return this.view.requestedBackend}get vfxBackendPreference(){return this.view.vfxBackendPreference}get webgpuVfxInstancesPerTemplate(){return this.view.webgpuVfxInstancesPerTemplate}get webgpuVfxTemplateMemoryBudgetBytes(){return this.view.webgpuVfxTemplateMemoryBudgetBytes}get webgpuVfxWorldMemoryBudgetBytes(){return this.view.webgpuVfxWorldMemoryBudgetBytes}get webgpuVfxColdTemplateSeconds(){return this.view.webgpuVfxColdTemplateSeconds}get webGpuDevice(){return this.view.webGpuDevice}get experimentalWebGpuDevice(){return this.webGpuDevice}registerWebGpuRenderGraphExtension(e){return this.view.registerWebGpuRenderGraphExtension(e)}registerExperimentalWebGpuRenderGraphExtension(e){return this.registerWebGpuRenderGraphExtension(e)}get updateFrameIndex(){return this._updateFrameIndex}handleFrame(e){this._updateFrameIndex++;let t=this._timeScale,a=0;this._hitStopRemaining>0?(this._hitStopRemaining-=1e3*e,this._hitStopRemaining<=0&&(this._hitStopRemaining=0),t=0):a=e*this._timeScale,this.advanceGameplayCameraShakes(a),this.tick.next(a),this.scheduler.update(1e3*a),this.lateTick.next(a),this.view.simulationTimeScale=t,this.refreshCameraPresentation()}set timeScale(e){this._timeScale=Math.max(0,e)}get timeScale(){return this._timeScale}applyHitStop(e){this._hitStopRemaining=Math.max(this._hitStopRemaining,e)}set fpsCap(e){this.view.fpsCap=e}get fpsCap(){return this.view.fpsCap}set showStats(e){this.view.showStats=e}get showStats(){return this.view.showStats}setProfilingEnabled(e){this.view.setProfilingEnabled(e)}getLastFrameProfile(){return this.view.getLastFrameProfile()}setLightVolume(e){this.view.lightVolume=e}getLightVolume(){return this.view.lightVolume}addPostProcessVolume(e){this.view.addPostProcessVolume(e)}removePostProcessVolume(e){this.view.removePostProcessVolume(e)}addPostProcessEffect(e,t){return this.view.addPostProcessEffect(e,t)}onUpdate(e){return null==e?this.tick:this.tick.pipe(h(e.disposed))}onLateUpdate(e){return null==e?this.lateTick:this.lateTick.pipe(h(e.disposed))}setCamera(e){const t=e instanceof i?e:e.camera.instance;this.baseCamera=t,0===this.cameraOverrides.length&&(this.sourceCamera=t,this.refreshCameraPresentation())}getCamera(){return this.view.camera}getSourceCamera(){return this.sourceCamera}getBaseCamera(){return this.baseCamera}applyCameraShake(e){const t=function(e){return{duration:Math.max(0,e.duration??0),attack:e.attack??0,decay:e.decay??0,frequency:e.frequency??24,positionAmplitude:e.positionAmplitude?[...e.positionAmplitude]:[0,0,0],rotationAmplitude:e.rotationAmplitude?[...e.rotationAmplitude]:[0,0,0],seed:e.seed}}(e);if(t.duration<=0)return{release:()=>{}};const a={id:++this.cameraShakeId,request:t,elapsedTime:0,seedOverride:t.seed};this.gameplayCameraShakes.push(a),this.refreshCameraPresentation();let i=!1;return{release:()=>{i||(i=!0,this.releaseGameplayCameraShake(a.id))}}}setCameraShakeContribution(e,t,a,i,r){if(a.duration<=0)return void this.removeCameraShakeContribution(e,t);let s=this.cameraShakeContributions.get(e);s||(s=new Map,this.cameraShakeContributions.set(e,s));const n=s.get(t);n?(n.request=a,n.elapsedTime=Math.max(0,i),n.seedOverride=r):s.set(t,{key:t,request:a,elapsedTime:Math.max(0,i),seedOverride:r}),this.refreshCameraPresentation()}removeCameraShakeContribution(e,t){const a=this.cameraShakeContributions.get(e);a?.delete(t)&&(0===a.size&&this.cameraShakeContributions.delete(e),this.refreshCameraPresentation())}clearCameraShakeContributionsForOwner(e){this.cameraShakeContributions.delete(e)&&this.refreshCameraPresentation()}pushCameraOverride(e,t,a=0){const i={id:++this.cameraOverrideId,camera:e,owner:t,priority:a};this.cameraOverrides.push(i),this.applyTopCameraOverride();let r=!1;return{release:()=>{r||(r=!0,this.releaseCameraOverrideEntry(i))}}}releaseCameraOverridesForOwner(e){const t=this.cameraOverrides.filter(t=>t.owner!==e);t.length!==this.cameraOverrides.length&&(this.cameraOverrides=t,this.applyTopCameraOverride())}releaseCameraOverrideEntry(e){const t=this.cameraOverrides.indexOf(e);-1!==t&&(this.cameraOverrides.splice(t,1),this.applyTopCameraOverride())}applyTopCameraOverride(){const e=this.getTopCameraOverride();this.sourceCamera=e?.camera??this.baseCamera,this.refreshCameraPresentation()}getTopCameraOverride(){let e;for(const t of this.cameraOverrides)(!e||t.priority>e.priority||t.priority===e.priority&&t.id>e.id)&&(e=t);return e}applyRenderedCamera(e){this.view.camera!==e&&this.view.setCamera(e),e.add(this.audioListener)}releaseGameplayCameraShake(e){const t=this.gameplayCameraShakes.findIndex(t=>t.id===e);-1!==t&&(this.gameplayCameraShakes.splice(t,1),this.refreshCameraPresentation())}advanceGameplayCameraShakes(e){if(!(e<=0||0===this.gameplayCameraShakes.length))for(let t=this.gameplayCameraShakes.length-1;t>=0;t--){const a=this.gameplayCameraShakes[t];a.elapsedTime+=e,a.elapsedTime>a.request.duration&&this.gameplayCameraShakes.splice(t,1)}}refreshCameraPresentation(){if(!this.hasActiveCameraShake())return void this.applyRenderedCamera(this.sourceCamera);const e=this.ensurePresentationCamera();this.copyCameraState(this.sourceCamera,e),this.accumulateCameraShakeSamples(),this.applyCameraShakeSample(e),this.applyRenderedCamera(e)}hasActiveCameraShake(){if(this.gameplayCameraShakes.length>0)return!0;for(const e of this.cameraShakeContributions.values())if(e.size>0)return!0;return!1}ensurePresentationCamera(){return this.presentationCamera||(this.presentationCamera=new o.PerspectiveCamera,this.presentationCamera.name="View Controller Presentation Camera"),this.presentationCamera}copyCameraState(e,t){e.updateWorldMatrix(!0,!1),t.position.copy(e.getWorldPosition(S)),t.quaternion.copy(e.getWorldQuaternion(w)),t.scale.copy(e.getWorldScale(f)),t.layers.mask=e.layers.mask,e instanceof o.PerspectiveCamera?(t.fov=e.fov,t.near=e.near,t.far=e.far,t.aspect=e.aspect,t.zoom=e.zoom,t.focus=e.focus,t.filmGauge=e.filmGauge,t.filmOffset=e.filmOffset,t.view=null==e.view?null:{enabled:e.view.enabled,fullWidth:e.view.fullWidth,fullHeight:e.view.fullHeight,offsetX:e.view.offsetX,offsetY:e.view.offsetY,width:e.view.width,height:e.view.height},t.updateProjectionMatrix()):(t.projectionMatrix.copy(e.projectionMatrix),t.projectionMatrixInverse.copy(e.projectionMatrixInverse))}accumulateCameraShakeSamples(){l(this.accumulatedCameraShakeSample);for(let e=0;e<this.gameplayCameraShakes.length;e++)this.accumulateCameraShakeEntry(this.gameplayCameraShakes[e]);for(const e of this.cameraShakeContributions.values())for(const t of e.values())this.accumulateCameraShakeEntry(t)}accumulateCameraShakeEntry(e){const t=c(e.request,e.elapsedTime,this.scratchCameraShakeSample,e.seedOverride);if(t)for(let e=0;e<3;e++)this.accumulatedCameraShakeSample.position[e]+=t.position[e],this.accumulatedCameraShakeSample.rotation[e]+=t.rotation[e]}applyCameraShakeSample(e){k.copy(e.quaternion),y.set(this.accumulatedCameraShakeSample.position[0]*this._cameraShakeIntensity,this.accumulatedCameraShakeSample.position[1]*this._cameraShakeIntensity,this.accumulatedCameraShakeSample.position[2]*this._cameraShakeIntensity),y.applyQuaternion(k),e.position.add(y),x.set(this.accumulatedCameraShakeSample.rotation[0]*this._cameraShakeIntensity,this.accumulatedCameraShakeSample.rotation[1]*this._cameraShakeIntensity,this.accumulatedCameraShakeSample.rotation[2]*this._cameraShakeIntensity),P.setFromEuler(x),e.quaternion.copy(k).multiply(P),e.updateMatrixWorld(!0)}setMuted(e){this._muted=e,this.applyAudioGain(),this.mutedChanged.next(e)}getMuted(){return this._muted}set masterVolume(e){this._masterVolume=Math.min(Math.max(e,0),1),this.applyAudioGain()}get masterVolume(){return this._masterVolume}set cameraShakeIntensity(e){this._cameraShakeIntensity=Math.min(Math.max(e,0),1),this.refreshCameraPresentation()}get cameraShakeIntensity(){return this._cameraShakeIntensity}applyAudioGain(){this.audioListener.gain.gain.setValueAtTime(this._muted?0:this._masterVolume,this.audioListener.context.currentTime)}setOutlined(e){this.outlined.length=0;const t=this.outlined;for(let a=0;a<e.length;a++)t[a]=e[a];this.view.setSelectedObjects(t),this.view.setEnableOutlines(e.length>0)}getOutlined(){return this.outlined}addOutlined(e){this.outlined.includes(e)||(this.outlined.push(e),this.view.setSelectedObjects(this.outlined),this.view.setEnableOutlines(!0))}removeOutlined(e){const t=this.outlined.indexOf(e);-1!==t&&(this.outlined.splice(t,1),this.view.setSelectedObjects(this.outlined))}setOutlineColor(e){this.view.outlinePass?.visibleEdgeColor.copy(e)}getOutlineColor(){return this.view.outlinePass?.visibleEdgeColor}setOutlineThickness(e){this.view.outlinePass.edgeThickness=e}getOutlineThickness(){return this.view.outlinePass.edgeThickness}get htmlElement(){return this.view.container}getViewportSize(e=g){return this.view.getClientSize(e)}get paused(){return this.view.paused}set paused(e){e!=this.paused&&(e?this.pauseRendering():this.unpauseRendering())}pauseRendering(){this.view.paused=!0,this.pausedChanged.next(this.view.paused),this.scheduler.pause()}unpauseRendering(){this.view.paused=!1,this.pausedChanged.next(this.view.paused),this.scheduler.resume()}setInterval(e,t,a){return this.scheduler.setInterval(e,t,a)}clearInterval(e){this.scheduler.clearInterval(e)}dispose(e=!0){e&&this.view.running&&this.view.stop(),this.scheduler.dispose(),this.cameraOverrides.length=0,this.gameplayCameraShakes.length=0,this.cameraShakeContributions.clear(),this.audioListener.removeFromParent(),this.tick.complete(),this.lateTick.complete(),this.paused=!0}createTimer(e){return this.scheduler.createTimer(e)}getScreenPosition(e,t=C,a=this.getViewportSize()){const i=this.getCamera();return p.multiplyMatrices(i.projectionMatrix,i.matrixWorldInverse),i instanceof o.PerspectiveCamera&&(v.setFromProjectionMatrix(p),!v.containsPoint(e))?null:(t.copy(e),t.project(i),t.x=(t.x+1)/2*a.x,t.y=(1-t.y)/2*a.y,t)}};d=e([a(),t("design:paramtypes",[r])],d);export{d as ViewController};const p=new o.Matrix4,C=new o.Vector3,g=new o.Vector2,v=new o.Frustum,S=new o.Vector3,f=new o.Vector3,w=new o.Quaternion,k=new o.Quaternion,y=new o.Vector3,x=new o.Euler(0,0,0,"XYZ"),P=new o.Quaternion;/*
2
2
  * Copyright (©) 2026 Hology Interactive AB. All rights reserved.
3
3
  * See the LICENSE.md file for details.
4
4
  */
@@ -63,6 +63,7 @@ export declare class NodeMaterialWebGpuResolver {
63
63
  private readonly mappedMaterialsSignatures;
64
64
  private readonly observedNodeMaterials;
65
65
  private readonly outlineMaterials;
66
+ private readonly outlineUniformVersions;
66
67
  private readonly worldEnvironmentMaterials;
67
68
  private worldEnvironment;
68
69
  private worldEnvironmentTexture;