@codexo/exojs 0.6.12 → 0.7.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1424 -0
- package/dist/esm/animation/Tween.d.ts +60 -1
- package/dist/esm/animation/Tween.js +60 -1
- package/dist/esm/animation/Tween.js.map +1 -1
- package/dist/esm/animation/TweenManager.d.ts +10 -0
- package/dist/esm/animation/TweenManager.js +10 -0
- package/dist/esm/animation/TweenManager.js.map +1 -1
- package/dist/esm/animation/types.d.ts +15 -0
- package/dist/esm/animation/types.js +6 -0
- package/dist/esm/animation/types.js.map +1 -1
- package/dist/esm/audio/AbstractMedia.d.ts +29 -0
- package/dist/esm/audio/AbstractMedia.js +76 -0
- package/dist/esm/audio/AbstractMedia.js.map +1 -1
- package/dist/esm/audio/AudioAnalyser.d.ts +83 -23
- package/dist/esm/audio/AudioAnalyser.js +277 -57
- package/dist/esm/audio/AudioAnalyser.js.map +1 -1
- package/dist/esm/audio/AudioBus.d.ts +82 -0
- package/dist/esm/audio/AudioBus.js +255 -0
- package/dist/esm/audio/AudioBus.js.map +1 -0
- package/dist/esm/audio/AudioFilter.d.ts +16 -0
- package/dist/esm/audio/AudioFilter.js +13 -0
- package/dist/esm/audio/AudioFilter.js.map +1 -0
- package/dist/esm/audio/AudioListener.d.ts +39 -0
- package/dist/esm/audio/AudioListener.js +97 -0
- package/dist/esm/audio/AudioListener.js.map +1 -0
- package/dist/esm/audio/AudioManager.d.ts +68 -0
- package/dist/esm/audio/AudioManager.js +139 -0
- package/dist/esm/audio/AudioManager.js.map +1 -0
- package/dist/esm/audio/BeatDetector.d.ts +142 -0
- package/dist/esm/audio/BeatDetector.js +957 -0
- package/dist/esm/audio/BeatDetector.js.map +1 -0
- package/dist/esm/audio/Envelope.d.ts +44 -0
- package/dist/esm/audio/Envelope.js +60 -0
- package/dist/esm/audio/Envelope.js.map +1 -0
- package/dist/esm/audio/Media.d.ts +11 -0
- package/dist/esm/audio/Music.d.ts +20 -0
- package/dist/esm/audio/Music.js +45 -4
- package/dist/esm/audio/Music.js.map +1 -1
- package/dist/esm/audio/OscillatorSound.d.ts +98 -0
- package/dist/esm/audio/OscillatorSound.js +342 -0
- package/dist/esm/audio/OscillatorSound.js.map +1 -0
- package/dist/esm/audio/Sound.d.ts +119 -9
- package/dist/esm/audio/Sound.js +301 -117
- package/dist/esm/audio/Sound.js.map +1 -1
- package/dist/esm/audio/audio-context.d.ts +48 -0
- package/dist/esm/audio/audio-context.js +48 -5
- package/dist/esm/audio/audio-context.js.map +1 -1
- package/dist/esm/audio/crossFade.d.ts +19 -0
- package/dist/esm/audio/crossFade.js +26 -0
- package/dist/esm/audio/crossFade.js.map +1 -0
- package/dist/esm/audio/dsp/fft.d.ts +22 -0
- package/dist/esm/audio/dsp/mel.d.ts +43 -0
- package/dist/esm/audio/dsp/tempogram.d.ts +51 -0
- package/dist/esm/audio/filters/ChorusFilter.d.ts +52 -0
- package/dist/esm/audio/filters/ChorusFilter.js +143 -0
- package/dist/esm/audio/filters/ChorusFilter.js.map +1 -0
- package/dist/esm/audio/filters/CompressorFilter.d.ts +43 -0
- package/dist/esm/audio/filters/CompressorFilter.js +108 -0
- package/dist/esm/audio/filters/CompressorFilter.js.map +1 -0
- package/dist/esm/audio/filters/DelayFilter.d.ts +34 -0
- package/dist/esm/audio/filters/DelayFilter.js +110 -0
- package/dist/esm/audio/filters/DelayFilter.js.map +1 -0
- package/dist/esm/audio/filters/DuckingFilter.d.ts +44 -0
- package/dist/esm/audio/filters/DuckingFilter.js +164 -0
- package/dist/esm/audio/filters/DuckingFilter.js.map +1 -0
- package/dist/esm/audio/filters/EqualizerFilter.d.ts +49 -0
- package/dist/esm/audio/filters/EqualizerFilter.js +134 -0
- package/dist/esm/audio/filters/EqualizerFilter.js.map +1 -0
- package/dist/esm/audio/filters/GranularFilter.d.ts +62 -0
- package/dist/esm/audio/filters/GranularFilter.js +176 -0
- package/dist/esm/audio/filters/GranularFilter.js.map +1 -0
- package/dist/esm/audio/filters/HighpassFilter.d.ts +29 -0
- package/dist/esm/audio/filters/HighpassFilter.js +71 -0
- package/dist/esm/audio/filters/HighpassFilter.js.map +1 -0
- package/dist/esm/audio/filters/LowpassFilter.d.ts +29 -0
- package/dist/esm/audio/filters/LowpassFilter.js +71 -0
- package/dist/esm/audio/filters/LowpassFilter.js.map +1 -0
- package/dist/esm/audio/filters/PitchShiftFilter.d.ts +44 -0
- package/dist/esm/audio/filters/PitchShiftFilter.js +132 -0
- package/dist/esm/audio/filters/PitchShiftFilter.js.map +1 -0
- package/dist/esm/audio/filters/ReverbFilter.d.ts +36 -0
- package/dist/esm/audio/filters/ReverbFilter.js +118 -0
- package/dist/esm/audio/filters/ReverbFilter.js.map +1 -0
- package/dist/esm/audio/filters/VocoderFilter.d.ts +49 -0
- package/dist/esm/audio/filters/VocoderFilter.js +174 -0
- package/dist/esm/audio/filters/VocoderFilter.js.map +1 -0
- package/dist/esm/audio/filters/WorkletFilter.d.ts +51 -0
- package/dist/esm/audio/filters/WorkletFilter.js +106 -0
- package/dist/esm/audio/filters/WorkletFilter.js.map +1 -0
- package/dist/esm/audio/filters/index.d.ts +12 -0
- package/dist/esm/audio/index.d.ts +15 -1
- package/dist/esm/audio/worklet/registerWorklet.d.ts +10 -0
- package/dist/esm/audio/worklet/registerWorklet.js +44 -0
- package/dist/esm/audio/worklet/registerWorklet.js.map +1 -0
- package/dist/esm/core/Application.d.ts +82 -0
- package/dist/esm/core/Application.js +137 -2
- package/dist/esm/core/Application.js.map +1 -1
- package/dist/esm/core/Bounds.d.ts +23 -0
- package/dist/esm/core/Bounds.js +23 -0
- package/dist/esm/core/Bounds.js.map +1 -1
- package/dist/esm/core/Clock.d.ts +21 -0
- package/dist/esm/core/Clock.js +21 -0
- package/dist/esm/core/Clock.js.map +1 -1
- package/dist/esm/core/Color.d.ts +35 -0
- package/dist/esm/core/Color.js +35 -0
- package/dist/esm/core/Color.js.map +1 -1
- package/dist/esm/core/Scene.d.ts +46 -0
- package/dist/esm/core/Scene.js +27 -0
- package/dist/esm/core/Scene.js.map +1 -1
- package/dist/esm/core/SceneManager.d.ts +62 -0
- package/dist/esm/core/SceneManager.js +49 -0
- package/dist/esm/core/SceneManager.js.map +1 -1
- package/dist/esm/core/SceneNode.d.ts +40 -1
- package/dist/esm/core/SceneNode.js +75 -6
- package/dist/esm/core/SceneNode.js.map +1 -1
- package/dist/esm/core/Signal.d.ts +44 -0
- package/dist/esm/core/Signal.js +39 -0
- package/dist/esm/core/Signal.js.map +1 -1
- package/dist/esm/core/Time.d.ts +21 -0
- package/dist/esm/core/Time.js +22 -1
- package/dist/esm/core/Time.js.map +1 -1
- package/dist/esm/core/Timer.d.ts +7 -0
- package/dist/esm/core/Timer.js +7 -0
- package/dist/esm/core/Timer.js.map +1 -1
- package/dist/esm/core/capabilities.d.ts +12 -0
- package/dist/esm/core/capabilities.js +12 -0
- package/dist/esm/core/capabilities.js.map +1 -1
- package/dist/esm/core/index.d.ts +0 -1
- package/dist/esm/core/types.d.ts +37 -0
- package/dist/esm/core/utils.d.ts +48 -0
- package/dist/esm/core/utils.js +48 -0
- package/dist/esm/core/utils.js.map +1 -1
- package/dist/esm/debug/BoundingBoxesLayer.d.ts +26 -0
- package/dist/esm/debug/BoundingBoxesLayer.js +136 -0
- package/dist/esm/debug/BoundingBoxesLayer.js.map +1 -0
- package/dist/esm/debug/DebugLayer.d.ts +42 -0
- package/dist/esm/debug/DebugLayer.js +34 -0
- package/dist/esm/debug/DebugLayer.js.map +1 -0
- package/dist/esm/debug/DebugOverlay.d.ts +59 -0
- package/dist/esm/debug/DebugOverlay.js +123 -0
- package/dist/esm/debug/DebugOverlay.js.map +1 -0
- package/dist/esm/debug/HitTestLayer.d.ts +32 -0
- package/dist/esm/debug/HitTestLayer.js +118 -0
- package/dist/esm/debug/HitTestLayer.js.map +1 -0
- package/dist/esm/debug/PerformanceLayer.d.ts +37 -0
- package/dist/esm/debug/PerformanceLayer.js +193 -0
- package/dist/esm/debug/PerformanceLayer.js.map +1 -0
- package/dist/esm/debug/PointerStackLayer.d.ts +31 -0
- package/dist/esm/debug/PointerStackLayer.js +160 -0
- package/dist/esm/debug/PointerStackLayer.js.map +1 -0
- package/dist/esm/debug/index.d.ts +6 -0
- package/dist/esm/debug/index.js +7 -0
- package/dist/esm/debug/index.js.map +1 -0
- package/dist/esm/index.js +30 -4
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/input/ArcadeStickGamepadMapping.d.ts +7 -0
- package/dist/esm/input/ArcadeStickGamepadMapping.js +7 -0
- package/dist/esm/input/ArcadeStickGamepadMapping.js.map +1 -1
- package/dist/esm/input/GameCubeGamepadMapping.d.ts +9 -0
- package/dist/esm/input/GameCubeGamepadMapping.js +9 -0
- package/dist/esm/input/GameCubeGamepadMapping.js.map +1 -1
- package/dist/esm/input/Gamepad.d.ts +48 -0
- package/dist/esm/input/Gamepad.js +43 -0
- package/dist/esm/input/Gamepad.js.map +1 -1
- package/dist/esm/input/GamepadChannels.d.ts +8 -0
- package/dist/esm/input/GamepadChannels.js +8 -0
- package/dist/esm/input/GamepadChannels.js.map +1 -1
- package/dist/esm/input/GamepadControl.d.ts +18 -0
- package/dist/esm/input/GamepadControl.js +13 -0
- package/dist/esm/input/GamepadControl.js.map +1 -1
- package/dist/esm/input/GamepadDefinitions.d.ts +64 -1
- package/dist/esm/input/GamepadDefinitions.js +40 -1
- package/dist/esm/input/GamepadDefinitions.js.map +1 -1
- package/dist/esm/input/GamepadMapping.d.ts +32 -2
- package/dist/esm/input/GamepadMapping.js +24 -0
- package/dist/esm/input/GamepadMapping.js.map +1 -1
- package/dist/esm/input/GamepadPromptLayouts.d.ts +32 -1
- package/dist/esm/input/GamepadPromptLayouts.js +25 -1
- package/dist/esm/input/GamepadPromptLayouts.js.map +1 -1
- package/dist/esm/input/GenericDualAnalogGamepadMapping.d.ts +10 -0
- package/dist/esm/input/GenericDualAnalogGamepadMapping.js +10 -0
- package/dist/esm/input/GenericDualAnalogGamepadMapping.js.map +1 -1
- package/dist/esm/input/GestureRecognizer.d.ts +11 -2
- package/dist/esm/input/GestureRecognizer.js +11 -2
- package/dist/esm/input/GestureRecognizer.js.map +1 -1
- package/dist/esm/input/Input.d.ts +24 -0
- package/dist/esm/input/Input.js +22 -0
- package/dist/esm/input/Input.js.map +1 -1
- package/dist/esm/input/InputManager.d.ts +43 -0
- package/dist/esm/input/InputManager.js +68 -5
- package/dist/esm/input/InputManager.js.map +1 -1
- package/dist/esm/input/InteractionEvent.d.ts +31 -0
- package/dist/esm/input/InteractionEvent.js +37 -0
- package/dist/esm/input/InteractionEvent.js.map +1 -0
- package/dist/esm/input/InteractionManager.d.ts +163 -0
- package/dist/esm/input/InteractionManager.js +575 -0
- package/dist/esm/input/InteractionManager.js.map +1 -0
- package/dist/esm/input/JoyConLeftGamepadMapping.d.ts +9 -0
- package/dist/esm/input/JoyConLeftGamepadMapping.js +9 -0
- package/dist/esm/input/JoyConLeftGamepadMapping.js.map +1 -1
- package/dist/esm/input/JoyConRightGamepadMapping.d.ts +9 -0
- package/dist/esm/input/JoyConRightGamepadMapping.js +9 -0
- package/dist/esm/input/JoyConRightGamepadMapping.js.map +1 -1
- package/dist/esm/input/PlayStationGamepadMapping.d.ts +8 -0
- package/dist/esm/input/PlayStationGamepadMapping.js +8 -0
- package/dist/esm/input/PlayStationGamepadMapping.js.map +1 -1
- package/dist/esm/input/Pointer.d.ts +22 -0
- package/dist/esm/input/Pointer.js +22 -0
- package/dist/esm/input/Pointer.js.map +1 -1
- package/dist/esm/input/SteamControllerGamepadMapping.d.ts +8 -0
- package/dist/esm/input/SteamControllerGamepadMapping.js +8 -0
- package/dist/esm/input/SteamControllerGamepadMapping.js.map +1 -1
- package/dist/esm/input/SwitchProGamepadMapping.d.ts +9 -0
- package/dist/esm/input/SwitchProGamepadMapping.js +9 -0
- package/dist/esm/input/SwitchProGamepadMapping.js.map +1 -1
- package/dist/esm/input/XboxGamepadMapping.d.ts +8 -0
- package/dist/esm/input/XboxGamepadMapping.js +8 -0
- package/dist/esm/input/XboxGamepadMapping.js.map +1 -1
- package/dist/esm/input/index.d.ts +2 -0
- package/dist/esm/input/interaction-hooks.d.ts +34 -0
- package/dist/esm/input/interaction-hooks.js +35 -0
- package/dist/esm/input/interaction-hooks.js.map +1 -0
- package/dist/esm/input/types.d.ts +20 -0
- package/dist/esm/input/types.js +20 -0
- package/dist/esm/input/types.js.map +1 -1
- package/dist/esm/math/AbstractVector.d.ts +83 -0
- package/dist/esm/math/AbstractVector.js +83 -0
- package/dist/esm/math/AbstractVector.js.map +1 -1
- package/dist/esm/math/Circle.d.ts +44 -2
- package/dist/esm/math/Circle.js +116 -16
- package/dist/esm/math/Circle.js.map +1 -1
- package/dist/esm/math/CircleLike.d.ts +1 -0
- package/dist/esm/math/Collision.d.ts +39 -0
- package/dist/esm/math/Collision.js +5 -0
- package/dist/esm/math/Collision.js.map +1 -1
- package/dist/esm/math/Ellipse.d.ts +11 -0
- package/dist/esm/math/Ellipse.js +18 -2
- package/dist/esm/math/Ellipse.js.map +1 -1
- package/dist/esm/math/EllipseLike.d.ts +3 -0
- package/dist/esm/math/Flags.d.ts +31 -0
- package/dist/esm/math/Flags.js +31 -0
- package/dist/esm/math/Flags.js.map +1 -1
- package/dist/esm/math/Interval.d.ts +15 -0
- package/dist/esm/math/Interval.js +16 -1
- package/dist/esm/math/Interval.js.map +1 -1
- package/dist/esm/math/Line.d.ts +17 -1
- package/dist/esm/math/Line.js +17 -1
- package/dist/esm/math/Line.js.map +1 -1
- package/dist/esm/math/LineLike.d.ts +1 -0
- package/dist/esm/math/Matrix.d.ts +44 -3
- package/dist/esm/math/Matrix.js +44 -3
- package/dist/esm/math/Matrix.js.map +1 -1
- package/dist/esm/math/ObservableSize.d.ts +7 -0
- package/dist/esm/math/ObservableSize.js +7 -0
- package/dist/esm/math/ObservableSize.js.map +1 -1
- package/dist/esm/math/ObservableVector.d.ts +11 -2
- package/dist/esm/math/ObservableVector.js +13 -2
- package/dist/esm/math/ObservableVector.js.map +1 -1
- package/dist/esm/math/PointLike.d.ts +1 -0
- package/dist/esm/math/PolarVector.d.ts +15 -0
- package/dist/esm/math/PolarVector.js +16 -1
- package/dist/esm/math/PolarVector.js.map +1 -1
- package/dist/esm/math/Polygon.d.ts +35 -1
- package/dist/esm/math/Polygon.js +78 -6
- package/dist/esm/math/Polygon.js.map +1 -1
- package/dist/esm/math/PolygonLike.d.ts +4 -0
- package/dist/esm/math/Quadtree.d.ts +84 -0
- package/dist/esm/math/Quadtree.js +204 -0
- package/dist/esm/math/Quadtree.js.map +1 -0
- package/dist/esm/math/Random.d.ts +25 -0
- package/dist/esm/math/Random.js +26 -1
- package/dist/esm/math/Random.js.map +1 -1
- package/dist/esm/math/Rectangle.d.ts +15 -0
- package/dist/esm/math/Rectangle.js +17 -2
- package/dist/esm/math/Rectangle.js.map +1 -1
- package/dist/esm/math/RectangleLike.d.ts +1 -0
- package/dist/esm/math/Segment.d.ts +7 -0
- package/dist/esm/math/Segment.js +7 -0
- package/dist/esm/math/Segment.js.map +1 -1
- package/dist/esm/math/ShapeLike.d.ts +6 -0
- package/dist/esm/math/Size.d.ts +9 -0
- package/dist/esm/math/Size.js +10 -1
- package/dist/esm/math/Size.js.map +1 -1
- package/dist/esm/math/Vector.d.ts +16 -0
- package/dist/esm/math/Vector.js +17 -1
- package/dist/esm/math/Vector.js.map +1 -1
- package/dist/esm/math/collision-detection.d.ts +47 -1
- package/dist/esm/math/collision-detection.js +231 -17
- package/dist/esm/math/collision-detection.js.map +1 -1
- package/dist/esm/math/collision-primitives.d.ts +23 -0
- package/dist/esm/math/collision-primitives.js +23 -0
- package/dist/esm/math/collision-primitives.js.map +1 -1
- package/dist/esm/math/geometry.d.ts +42 -0
- package/dist/esm/math/geometry.js +37 -0
- package/dist/esm/math/geometry.js.map +1 -1
- package/dist/esm/math/index.d.ts +1 -0
- package/dist/esm/math/utils.d.ts +41 -0
- package/dist/esm/math/utils.js +41 -0
- package/dist/esm/math/utils.js.map +1 -1
- package/dist/esm/particles/Particle.d.ts +37 -0
- package/dist/esm/particles/Particle.js +37 -0
- package/dist/esm/particles/Particle.js.map +1 -1
- package/dist/esm/particles/ParticleProperties.d.ts +15 -0
- package/dist/esm/particles/ParticleSystem.d.ts +69 -0
- package/dist/esm/particles/ParticleSystem.js +70 -0
- package/dist/esm/particles/ParticleSystem.js.map +1 -1
- package/dist/esm/particles/affectors/ColorAffector.d.ts +13 -0
- package/dist/esm/particles/affectors/ColorAffector.js +13 -0
- package/dist/esm/particles/affectors/ColorAffector.js.map +1 -1
- package/dist/esm/particles/affectors/ForceAffector.d.ts +11 -0
- package/dist/esm/particles/affectors/ForceAffector.js +11 -0
- package/dist/esm/particles/affectors/ForceAffector.js.map +1 -1
- package/dist/esm/particles/affectors/ParticleAffector.d.ts +13 -0
- package/dist/esm/particles/affectors/ScaleAffector.d.ts +10 -0
- package/dist/esm/particles/affectors/ScaleAffector.js +10 -0
- package/dist/esm/particles/affectors/ScaleAffector.js.map +1 -1
- package/dist/esm/particles/affectors/TorqueAffector.d.ts +11 -0
- package/dist/esm/particles/affectors/TorqueAffector.js +12 -1
- package/dist/esm/particles/affectors/TorqueAffector.js.map +1 -1
- package/dist/esm/particles/emitters/ParticleEmitter.d.ts +13 -0
- package/dist/esm/particles/emitters/ParticleOptions.d.ts +16 -0
- package/dist/esm/particles/emitters/ParticleOptions.js +16 -0
- package/dist/esm/particles/emitters/ParticleOptions.js.map +1 -1
- package/dist/esm/particles/emitters/UniversalEmitter.d.ts +23 -0
- package/dist/esm/particles/emitters/UniversalEmitter.js +23 -0
- package/dist/esm/particles/emitters/UniversalEmitter.js.map +1 -1
- package/dist/esm/rendering/CallbackRenderPass.d.ts +12 -0
- package/dist/esm/rendering/CallbackRenderPass.js +12 -0
- package/dist/esm/rendering/CallbackRenderPass.js.map +1 -1
- package/dist/esm/rendering/Container.d.ts +44 -0
- package/dist/esm/rendering/Container.js +62 -0
- package/dist/esm/rendering/Container.js.map +1 -1
- package/dist/esm/rendering/Drawable.d.ts +22 -0
- package/dist/esm/rendering/Drawable.js +22 -0
- package/dist/esm/rendering/Drawable.js.map +1 -1
- package/dist/esm/rendering/RenderBackend.d.ts +13 -0
- package/dist/esm/rendering/RenderBackendType.d.ts +4 -0
- package/dist/esm/rendering/RenderBackendType.js +4 -0
- package/dist/esm/rendering/RenderBackendType.js.map +1 -1
- package/dist/esm/rendering/RenderNode.d.ts +45 -0
- package/dist/esm/rendering/RenderNode.js +62 -0
- package/dist/esm/rendering/RenderNode.js.map +1 -1
- package/dist/esm/rendering/RenderPass.d.ts +8 -0
- package/dist/esm/rendering/RenderStats.d.ts +19 -0
- package/dist/esm/rendering/RenderStats.js +7 -0
- package/dist/esm/rendering/RenderStats.js.map +1 -1
- package/dist/esm/rendering/RenderTarget.d.ts +17 -0
- package/dist/esm/rendering/RenderTarget.js +17 -0
- package/dist/esm/rendering/RenderTarget.js.map +1 -1
- package/dist/esm/rendering/RenderTargetPass.d.ts +11 -0
- package/dist/esm/rendering/RenderTargetPass.js +7 -0
- package/dist/esm/rendering/RenderTargetPass.js.map +1 -1
- package/dist/esm/rendering/Renderer.d.ts +15 -0
- package/dist/esm/rendering/RendererRegistry.d.ts +7 -0
- package/dist/esm/rendering/RendererRegistry.js +7 -0
- package/dist/esm/rendering/RendererRegistry.js.map +1 -1
- package/dist/esm/rendering/View.d.ts +73 -6
- package/dist/esm/rendering/View.js +69 -2
- package/dist/esm/rendering/View.js.map +1 -1
- package/dist/esm/rendering/filters/BlurFilter.d.ts +9 -0
- package/dist/esm/rendering/filters/BlurFilter.js +8 -0
- package/dist/esm/rendering/filters/BlurFilter.js.map +1 -1
- package/dist/esm/rendering/filters/ColorFilter.d.ts +7 -0
- package/dist/esm/rendering/filters/ColorFilter.js +7 -0
- package/dist/esm/rendering/filters/ColorFilter.js.map +1 -1
- package/dist/esm/rendering/filters/Filter.d.ts +23 -0
- package/dist/esm/rendering/filters/Filter.js +20 -0
- package/dist/esm/rendering/filters/Filter.js.map +1 -1
- package/dist/esm/rendering/filters/WebGl2ShaderFilter.d.ts +114 -0
- package/dist/esm/rendering/filters/WebGl2ShaderFilter.js +273 -0
- package/dist/esm/rendering/filters/WebGl2ShaderFilter.js.map +1 -0
- package/dist/esm/rendering/filters/WebGpuShaderFilter.d.ts +116 -0
- package/dist/esm/rendering/filters/WebGpuShaderFilter.js +402 -0
- package/dist/esm/rendering/filters/WebGpuShaderFilter.js.map +1 -0
- package/dist/esm/rendering/index.d.ts +3 -0
- package/dist/esm/rendering/mesh/Mesh.d.ts +2 -0
- package/dist/esm/rendering/mesh/Mesh.js +3 -0
- package/dist/esm/rendering/mesh/Mesh.js.map +1 -1
- package/dist/esm/rendering/primitives/Graphics.d.ts +34 -0
- package/dist/esm/rendering/primitives/Graphics.js +34 -0
- package/dist/esm/rendering/primitives/Graphics.js.map +1 -1
- package/dist/esm/rendering/shader/Shader.d.ts +36 -0
- package/dist/esm/rendering/shader/Shader.js +26 -0
- package/dist/esm/rendering/shader/Shader.js.map +1 -1
- package/dist/esm/rendering/shader/ShaderAttribute.d.ts +13 -0
- package/dist/esm/rendering/shader/ShaderAttribute.js +13 -0
- package/dist/esm/rendering/shader/ShaderAttribute.js.map +1 -1
- package/dist/esm/rendering/shader/ShaderUniform.d.ts +27 -0
- package/dist/esm/rendering/shader/ShaderUniform.js +28 -1
- package/dist/esm/rendering/shader/ShaderUniform.js.map +1 -1
- package/dist/esm/rendering/shader/upgradeFragmentShaderToGl300.d.ts +34 -0
- package/dist/esm/rendering/shader/upgradeFragmentShaderToGl300.js +60 -0
- package/dist/esm/rendering/shader/upgradeFragmentShaderToGl300.js.map +1 -0
- package/dist/esm/rendering/sprite/AnimatedSprite.d.ts +38 -0
- package/dist/esm/rendering/sprite/AnimatedSprite.js +36 -0
- package/dist/esm/rendering/sprite/AnimatedSprite.js.map +1 -1
- package/dist/esm/rendering/sprite/Sprite.d.ts +62 -1
- package/dist/esm/rendering/sprite/Sprite.js +97 -19
- package/dist/esm/rendering/sprite/Sprite.js.map +1 -1
- package/dist/esm/rendering/sprite/Spritesheet.d.ts +25 -0
- package/dist/esm/rendering/sprite/Spritesheet.js +20 -0
- package/dist/esm/rendering/sprite/Spritesheet.js.map +1 -1
- package/dist/esm/rendering/text/Text.d.ts +2 -0
- package/dist/esm/rendering/text/Text.js +2 -0
- package/dist/esm/rendering/text/Text.js.map +1 -1
- package/dist/esm/rendering/text/TextStyle.d.ts +29 -0
- package/dist/esm/rendering/text/TextStyle.js +24 -0
- package/dist/esm/rendering/text/TextStyle.js.map +1 -1
- package/dist/esm/rendering/text/types.d.ts +1 -0
- package/dist/esm/rendering/texture/RenderTexture.d.ts +16 -0
- package/dist/esm/rendering/texture/RenderTexture.js +16 -0
- package/dist/esm/rendering/texture/RenderTexture.js.map +1 -1
- package/dist/esm/rendering/texture/Sampler.d.ts +23 -0
- package/dist/esm/rendering/texture/Sampler.js +13 -0
- package/dist/esm/rendering/texture/Sampler.js.map +1 -1
- package/dist/esm/rendering/texture/Texture.d.ts +30 -0
- package/dist/esm/rendering/texture/Texture.js +30 -0
- package/dist/esm/rendering/texture/Texture.js.map +1 -1
- package/dist/esm/rendering/types.d.ts +29 -0
- package/dist/esm/rendering/types.js +29 -0
- package/dist/esm/rendering/types.js.map +1 -1
- package/dist/esm/rendering/utils.d.ts +20 -0
- package/dist/esm/rendering/utils.js +10 -0
- package/dist/esm/rendering/utils.js.map +1 -1
- package/dist/esm/rendering/video/Video.d.ts +39 -1
- package/dist/esm/rendering/video/Video.js +68 -6
- package/dist/esm/rendering/video/Video.js.map +1 -1
- package/dist/esm/rendering/webgl2/AbstractWebGl2BatchedRenderer.d.ts +7 -0
- package/dist/esm/rendering/webgl2/AbstractWebGl2BatchedRenderer.js +7 -0
- package/dist/esm/rendering/webgl2/AbstractWebGl2BatchedRenderer.js.map +1 -1
- package/dist/esm/rendering/webgl2/WebGl2Backend.d.ts +17 -4
- package/dist/esm/rendering/webgl2/WebGl2Backend.js +20 -16
- package/dist/esm/rendering/webgl2/WebGl2Backend.js.map +1 -1
- package/dist/esm/rendering/webgl2/WebGl2RenderBuffer.d.ts +8 -0
- package/dist/esm/rendering/webgl2/WebGl2RenderBuffer.js +8 -0
- package/dist/esm/rendering/webgl2/WebGl2RenderBuffer.js.map +1 -1
- package/dist/esm/rendering/webgpu/WebGpuBackend.d.ts +45 -8
- package/dist/esm/rendering/webgpu/WebGpuBackend.js +160 -40
- package/dist/esm/rendering/webgpu/WebGpuBackend.js.map +1 -1
- package/dist/esm/resources/AbstractAssetFactory.d.ts +20 -0
- package/dist/esm/resources/AbstractAssetFactory.js +20 -0
- package/dist/esm/resources/AbstractAssetFactory.js.map +1 -1
- package/dist/esm/resources/AssetFactory.d.ts +26 -0
- package/dist/esm/resources/AssetManifest.d.ts +45 -0
- package/dist/esm/resources/AssetManifest.js +25 -0
- package/dist/esm/resources/AssetManifest.js.map +1 -1
- package/dist/esm/resources/CacheFirstStrategy.d.ts +12 -0
- package/dist/esm/resources/CacheFirstStrategy.js +12 -0
- package/dist/esm/resources/CacheFirstStrategy.js.map +1 -1
- package/dist/esm/resources/CacheStore.d.ts +23 -0
- package/dist/esm/resources/CacheStrategy.d.ts +20 -0
- package/dist/esm/resources/Database.d.ts +45 -0
- package/dist/esm/resources/FactoryRegistry.d.ts +28 -0
- package/dist/esm/resources/FactoryRegistry.js +24 -0
- package/dist/esm/resources/FactoryRegistry.js.map +1 -1
- package/dist/esm/resources/IndexedDbDatabase.d.ts +22 -1
- package/dist/esm/resources/IndexedDbDatabase.js +21 -0
- package/dist/esm/resources/IndexedDbDatabase.js.map +1 -1
- package/dist/esm/resources/IndexedDbStore.d.ts +20 -0
- package/dist/esm/resources/IndexedDbStore.js +12 -0
- package/dist/esm/resources/IndexedDbStore.js.map +1 -1
- package/dist/esm/resources/Loader.d.ts +167 -0
- package/dist/esm/resources/Loader.js +110 -0
- package/dist/esm/resources/Loader.js.map +1 -1
- package/dist/esm/resources/NetworkOnlyStrategy.d.ts +8 -0
- package/dist/esm/resources/NetworkOnlyStrategy.js +8 -0
- package/dist/esm/resources/NetworkOnlyStrategy.js.map +1 -1
- package/dist/esm/resources/factories/BinaryFactory.d.ts +13 -0
- package/dist/esm/resources/factories/BinaryFactory.js +13 -0
- package/dist/esm/resources/factories/BinaryFactory.js.map +1 -1
- package/dist/esm/resources/factories/FontFactory.d.ts +37 -0
- package/dist/esm/resources/factories/FontFactory.js +26 -0
- package/dist/esm/resources/factories/FontFactory.js.map +1 -1
- package/dist/esm/resources/factories/ImageFactory.d.ts +24 -0
- package/dist/esm/resources/factories/ImageFactory.js +19 -0
- package/dist/esm/resources/factories/ImageFactory.js.map +1 -1
- package/dist/esm/resources/factories/JsonFactory.d.ts +13 -0
- package/dist/esm/resources/factories/JsonFactory.js +13 -0
- package/dist/esm/resources/factories/JsonFactory.js.map +1 -1
- package/dist/esm/resources/factories/MusicFactory.d.ts +36 -0
- package/dist/esm/resources/factories/MusicFactory.js +44 -4
- package/dist/esm/resources/factories/MusicFactory.js.map +1 -1
- package/dist/esm/resources/factories/SoundFactory.d.ts +29 -0
- package/dist/esm/resources/factories/SoundFactory.js +18 -0
- package/dist/esm/resources/factories/SoundFactory.js.map +1 -1
- package/dist/esm/resources/factories/SvgFactory.d.ts +19 -0
- package/dist/esm/resources/factories/SvgFactory.js +19 -0
- package/dist/esm/resources/factories/SvgFactory.js.map +1 -1
- package/dist/esm/resources/factories/TextFactory.d.ts +11 -0
- package/dist/esm/resources/factories/TextFactory.js +11 -0
- package/dist/esm/resources/factories/TextFactory.js.map +1 -1
- package/dist/esm/resources/factories/TextureFactory.d.ts +25 -0
- package/dist/esm/resources/factories/TextureFactory.js +20 -0
- package/dist/esm/resources/factories/TextureFactory.js.map +1 -1
- package/dist/esm/resources/factories/VideoFactory.d.ts +37 -0
- package/dist/esm/resources/factories/VideoFactory.js +48 -5
- package/dist/esm/resources/factories/VideoFactory.js.map +1 -1
- package/dist/esm/resources/factories/VttFactory.d.ts +18 -0
- package/dist/esm/resources/factories/VttFactory.js +24 -0
- package/dist/esm/resources/factories/VttFactory.js.map +1 -1
- package/dist/esm/resources/factories/WasmFactory.d.ts +16 -0
- package/dist/esm/resources/factories/WasmFactory.js +16 -0
- package/dist/esm/resources/factories/WasmFactory.js.map +1 -1
- package/dist/esm/resources/utils.d.ts +10 -0
- package/dist/esm/resources/utils.js +10 -0
- package/dist/esm/resources/utils.js.map +1 -1
- package/dist/exo.esm.js +11182 -2538
- package/dist/exo.esm.js.map +1 -1
- package/package.json +20 -9
- package/dist/esm/core/Quadtree.d.ts +0 -20
- package/dist/esm/core/Quadtree.js +0 -86
- package/dist/esm/core/Quadtree.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,1430 @@ All notable changes to ExoJS are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.7.12] - 2026-05-07
|
|
8
|
+
|
|
9
|
+
API audit cleanup pass — implements collision-response computation that was
|
|
10
|
+
silently returning zero/null, exports previously-internal type aliases that
|
|
11
|
+
callers couldn't otherwise type, and removes a handful of small API papercuts
|
|
12
|
+
surfaced during the JSDoc-coverage pass that landed across `0.7.x`.
|
|
13
|
+
|
|
14
|
+
### Fixed — Collision-response computation
|
|
15
|
+
|
|
16
|
+
Four collision paths were returning a `CollisionResponse` whose `projectionN`
|
|
17
|
+
and `projectionV` were zero vectors (or returning `null` outright), making
|
|
18
|
+
the response unusable for separation/sliding logic.
|
|
19
|
+
|
|
20
|
+
- **`getCollisionRectangleRectangle`** — now returns the minimum-translation
|
|
21
|
+
vector (MTV) along the axis with the smaller penetration, signed by the
|
|
22
|
+
centre-to-centre direction. Existing `overlap` and containment flags are
|
|
23
|
+
unchanged.
|
|
24
|
+
- **`getCollisionCircleRectangle`** — rewritten to use the standard
|
|
25
|
+
closest-point-on-rect algorithm. Previously computed distance against an
|
|
26
|
+
out-of-rect anchor point, producing a wrong result whenever the circle
|
|
27
|
+
centre was inside the rectangle. Normal points from rect surface toward
|
|
28
|
+
circle; falls back to the smaller-exit axis when the circle centre is
|
|
29
|
+
inside the rect.
|
|
30
|
+
- **`Ellipse.collidesWith`** — implements `Ellipse`-vs-`Rectangle` and
|
|
31
|
+
`Ellipse`-vs-`Circle` via the directional ellipse-boundary equation
|
|
32
|
+
`1 / sqrt((dx/rx)² + (dy/ry)²)`. Other targets (ellipse-vs-ellipse,
|
|
33
|
+
ellipse-vs-polygon, ellipse-vs-line) still return `null` —
|
|
34
|
+
`intersectsWith` remains the boolean fallback.
|
|
35
|
+
- **`Line.collidesWith`** — kept returning `null` (lines have no
|
|
36
|
+
meaningful SAT response), but the JSDoc now states the contract
|
|
37
|
+
explicitly so callers don't expect a vector.
|
|
38
|
+
|
|
39
|
+
`Rectangle.collidesWith` and `Circle.collidesWith` route ellipse targets to
|
|
40
|
+
the new functions via the existing `swap` flag.
|
|
41
|
+
|
|
42
|
+
### Fixed — Object-URL leak (re-emphasised; was 0.7.11 fix)
|
|
43
|
+
|
|
44
|
+
The 0.7.11 fix for `MusicFactory` and `VideoFactory` URL revocation is
|
|
45
|
+
unchanged in 0.7.12 — listing it here for completeness because the pre-1.0
|
|
46
|
+
audit findings memory carries a forward reference to it.
|
|
47
|
+
|
|
48
|
+
### Changed — Visibility / readonly tightening (potentially breaking)
|
|
49
|
+
|
|
50
|
+
Pre-1.0 cleanups that narrow the public surface where callers could
|
|
51
|
+
previously poke at internal state:
|
|
52
|
+
|
|
53
|
+
- **`GamepadMapping.buttons` / `.axes`** typed `ReadonlyArray<GamepadControl>`
|
|
54
|
+
instead of `Array<GamepadControl>`. Internal `destroy()` retains the
|
|
55
|
+
`length = 0` clear via a local cast. **Breaking** for callers that were
|
|
56
|
+
pushing or splicing the arrays directly.
|
|
57
|
+
- **`View.updateTransform()` / `.updateBounds()`** changed from `public` to
|
|
58
|
+
`protected`. They were never safe to call externally — invoking them
|
|
59
|
+
bypassed the dirty-flag clearing in `getTransform()` / `getBounds()` and
|
|
60
|
+
could cause redundant recalculation. **Breaking** if you relied on them.
|
|
61
|
+
- **`IndexedDbDatabase.getObjectStore()`** changed from `public` to
|
|
62
|
+
`protected`. Only callers were the class's own `load`/`save`/`delete`
|
|
63
|
+
methods. **Breaking** if any subclass referenced it externally.
|
|
64
|
+
- **`GamepadDefinitions.normalizeIds`** is no longer exported. It was an
|
|
65
|
+
in-file helper that leaked through the barrel. **Breaking** for any
|
|
66
|
+
caller importing it directly.
|
|
67
|
+
- **`GamepadPromptLayouts.buildControlChannelMap()`** renamed to
|
|
68
|
+
`getControlChannelMap()` — the name now matches the behaviour (returns a
|
|
69
|
+
pre-built constant; never builds anything). **Breaking** rename.
|
|
70
|
+
|
|
71
|
+
### Added — API surface
|
|
72
|
+
|
|
73
|
+
Additive changes; not breaking:
|
|
74
|
+
|
|
75
|
+
- `EqualizerFilter` now exposes runtime setters for `lowFrequency`,
|
|
76
|
+
`midFrequency`, and `highFrequency` (previously only construction-time).
|
|
77
|
+
Smooth ramp via `setTargetAtTime` to avoid clicks.
|
|
78
|
+
- `Filter` (abstract base for post-process filters) now declares a
|
|
79
|
+
`destroy()` method with a no-op default. `BlurFilter` / `ColorFilter` /
|
|
80
|
+
`WebGl2ShaderFilter` / `WebGpuShaderFilter` mark their existing
|
|
81
|
+
implementations as `override`. Generic-filter consumers no longer need
|
|
82
|
+
a cast to release filters.
|
|
83
|
+
- `getCollisionEllipseRectangle` and `getCollisionEllipseCircle` are
|
|
84
|
+
exported from the math barrel for direct use.
|
|
85
|
+
|
|
86
|
+
### Changed — Internal cleanups
|
|
87
|
+
|
|
88
|
+
Doc-only and signature-only refactors:
|
|
89
|
+
|
|
90
|
+
- `Sprite._invalidateSubtreeTransform` / `._invalidateBoundsCascade` tagged
|
|
91
|
+
`@internal` (they are `public` only because of TS friend-class limits).
|
|
92
|
+
- `_getDebugQuadtree` (InteractionManager) and `_walkBounds` (Quadtree)
|
|
93
|
+
tagged `@internal` to mark the friend-class link to the debug layer.
|
|
94
|
+
- `PerformanceLayer` declares `viewMode` explicitly to match the other
|
|
95
|
+
debug layers.
|
|
96
|
+
- `PointerStackLayer._buildLines` lost its two unused `_panelX` / `_panelY`
|
|
97
|
+
parameters. Internal-only; not user-visible.
|
|
98
|
+
- `intersectionCirclePoly` got an inline comment explaining the
|
|
99
|
+
negated-frame coordinate transform.
|
|
100
|
+
- `AudioAnalyserOptions` interface picked up per-field JSDoc with documented
|
|
101
|
+
defaults.
|
|
102
|
+
- `SoundFactoryOptions.poolSize` JSDoc names the implicit `Sound` default (8).
|
|
103
|
+
- `ChorusFilter` lost a redundant `as AudioParam` cast.
|
|
104
|
+
- `Video.setupWithAudioContext` is now an arrow-bound field instead of a
|
|
105
|
+
context-bound method; cleaner internally, no API change.
|
|
106
|
+
- `ShaderUniform.propName` uses `String.prototype.substring` instead of the
|
|
107
|
+
deprecated `substr`.
|
|
108
|
+
- `Tween.repeat` JSDoc now ships an `@example` block clarifying that
|
|
109
|
+
`repeat(2)` runs the animation three times total.
|
|
110
|
+
- `Line.collidesWith` documents the always-`null` behaviour as intentional.
|
|
111
|
+
- `RenderTarget.addDestroyListener` / `.removeDestroyListener` got JSDoc
|
|
112
|
+
pointing out that `RenderTexture` (which extends `RenderTarget`) inherits
|
|
113
|
+
them; the audit finding that claimed otherwise was incorrect.
|
|
114
|
+
|
|
115
|
+
## [0.7.11] - 2026-05-07
|
|
116
|
+
|
|
117
|
+
Performance pass — adds a multi-domain benchmark suite, an auto-profiler
|
|
118
|
+
that finds Top-3-Wins from baseline data, and three measured optimizations
|
|
119
|
+
those benchmarks identified. Includes a breaking change to
|
|
120
|
+
`InteractionManager` (the `useSpatialIndex` flag is removed; spatial
|
|
121
|
+
indexing is now automatic and persistent).
|
|
122
|
+
|
|
123
|
+
### Added — Performance infrastructure
|
|
124
|
+
|
|
125
|
+
- **`test/perf/` benchmark suite** covering five domains: rendering,
|
|
126
|
+
audio, collision, scene-graph, interaction. Each domain has its own
|
|
127
|
+
script (`npm run perf:bench:rendering`, `:audio`, `:collision`,
|
|
128
|
+
`:scene-graph`, `:interaction`) plus `:all` aggregator. Output: JSON
|
|
129
|
+
+ Markdown to `test/perf/results/`.
|
|
130
|
+
- **Baseline snapshot** committed as `test/perf/results/baseline.md` —
|
|
131
|
+
reference numbers at 0.7.10 for future regression detection.
|
|
132
|
+
- **Auto-profiler** (`npm run perf:profile`, `:gc` variant with
|
|
133
|
+
`--expose-gc`) that re-runs the hottest scenarios with granular
|
|
134
|
+
sub-timings, heap-delta tracking, and call counters. Writes
|
|
135
|
+
`test/perf/results/findings.md` with auto-derived Top-3 Wins
|
|
136
|
+
recommendations.
|
|
137
|
+
- Profile helpers (`SubTimingTracker`, `CallCounter`, `MemoryTracker`)
|
|
138
|
+
in `test/perf/profile-runner.ts` for future ad-hoc profiling.
|
|
139
|
+
|
|
140
|
+
### Performance — Win 1: `Polygon.getNormals()` cached
|
|
141
|
+
|
|
142
|
+
Mirrors the 0.6.19 dirty-flag pattern from `Sprite.getNormals()` and the
|
|
143
|
+
0.7.8 work on `Circle.getNormals()`. `Polygon.getNormals()` now caches
|
|
144
|
+
the result and recomputes only when shape mutates. Returns the same
|
|
145
|
+
array reference on subsequent calls. Eliminates per-call allocation of
|
|
146
|
+
N `Vector` instances during SAT collision — significant for collision-
|
|
147
|
+
heavy scenes. Cache invalidated on `setPoints`, `setPosition`, `set`,
|
|
148
|
+
`copy`, and the `x` / `y` / `position` setters.
|
|
149
|
+
|
|
150
|
+
The legacy `normals` getter is now `@deprecated` — call `getNormals()`
|
|
151
|
+
directly. Behavior is identical; the getter just delegates.
|
|
152
|
+
|
|
153
|
+
### Performance — Win 2: `Quadtree.queryPoint()` documented buffer reuse
|
|
154
|
+
|
|
155
|
+
The `results?: Array<QuadtreeItem<T>>` parameter has been there since
|
|
156
|
+
0.6.16 but was undocumented. JSDoc now explicitly documents the
|
|
157
|
+
buffer-reuse pattern for zero-allocation hot-path queries. Added a
|
|
158
|
+
`Quadtree.remove(item)` method (needed internally by Win 3); also
|
|
159
|
+
publicly available for users who want to maintain quadtrees externally.
|
|
160
|
+
|
|
161
|
+
### Performance — Win 3: Persistent Spatial-Index (BREAKING)
|
|
162
|
+
|
|
163
|
+
`InteractionManager`'s spatial index now lives across frames and is
|
|
164
|
+
incrementally maintained — replaces the per-frame full rebuild. This
|
|
165
|
+
also makes the `useSpatialIndex` opt-in flag unnecessary and **the
|
|
166
|
+
flag has been removed entirely**.
|
|
167
|
+
|
|
168
|
+
**How it works now:**
|
|
169
|
+
- A persistent quadtree is created lazily when the first interactive
|
|
170
|
+
node enters the scene.
|
|
171
|
+
- `Container.addChild` / `removeChild` walk subtrees and add/remove
|
|
172
|
+
interactive descendants from the index.
|
|
173
|
+
- `RenderNode.interactive = true/false` toggles registration.
|
|
174
|
+
- Transform mutations on interactive nodes (position / rotation / scale)
|
|
175
|
+
mark the node as "stale" via `_invalidateBoundsCascade`.
|
|
176
|
+
- Stale entries are lazy-updated at the start of `InteractionManager.update()`
|
|
177
|
+
on the next frame, before queries are dispatched.
|
|
178
|
+
- When the last interactive node is removed, the quadtree is disposed
|
|
179
|
+
and lifecycle returns to zero overhead.
|
|
180
|
+
|
|
181
|
+
**Practical effect:** scenes with many interactive nodes get the same
|
|
182
|
+
~5× faster hit-testing the old `useSpatialIndex = true` provided, but
|
|
183
|
+
without the per-frame rebuild cost. Mostly-static scenes (the common
|
|
184
|
+
case) see particularly large wins — incremental updates only fire on
|
|
185
|
+
actually-moved nodes.
|
|
186
|
+
|
|
187
|
+
### Changed (BREAKING)
|
|
188
|
+
|
|
189
|
+
- **`InteractionManager.useSpatialIndex` removed.** Spatial indexing is
|
|
190
|
+
now automatic. Code that explicitly set the flag (`= true` or
|
|
191
|
+
`= false`) gets a TypeScript error; the value should simply be
|
|
192
|
+
removed. Old `useSpatialIndex = true` users get the same speedup
|
|
193
|
+
automatically. Old `useSpatialIndex = false` users get a faster hit
|
|
194
|
+
path with negligible mutation overhead.
|
|
195
|
+
- **`RenderNode.interactive` is now a getter/setter** (was a public
|
|
196
|
+
field). External behavior is identical for normal usage
|
|
197
|
+
(`node.interactive = true`). Any code that relied on the field's
|
|
198
|
+
shape (descriptor inspection, etc.) needs to adapt. Reading the value
|
|
199
|
+
is a getter call — same observable behavior.
|
|
200
|
+
- The `HitTestLayer` debug overlay no longer requires
|
|
201
|
+
`useSpatialIndex = true` to draw quadtree quadrants; it draws them
|
|
202
|
+
whenever the persistent quadtree is non-null (i.e., whenever any
|
|
203
|
+
interactive node exists in the active scene).
|
|
204
|
+
|
|
205
|
+
### Migration
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
// Before:
|
|
209
|
+
app.interaction.useSpatialIndex = true; // flag opt-in
|
|
210
|
+
|
|
211
|
+
// After:
|
|
212
|
+
// Nothing — index is automatic. Just have at least one interactive
|
|
213
|
+
// node in the scene and queries use the persistent quadtree.
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Notes
|
|
217
|
+
|
|
218
|
+
- This release adds 30 net new tests (Polygon-cache + persistent-index
|
|
219
|
+
lifecycle), removes a few `useSpatialIndex`-flag assertion tests, and
|
|
220
|
+
modifies `interaction.test.ts` `TestSprite` to expose `getBounds()`
|
|
221
|
+
for the persistent index. Test count: 1196 → 1212.
|
|
222
|
+
- The benchmark suite and auto-profiler are dev infrastructure — they
|
|
223
|
+
live in `test/perf/` and are not shipped via npm (the `files` array
|
|
224
|
+
in package.json controls what's packed).
|
|
225
|
+
- The findings.md committed alongside baseline.md is a snapshot of
|
|
226
|
+
performance characteristics at 0.7.11 baseline — re-running profiles
|
|
227
|
+
will overwrite locally but the committed reference remains for
|
|
228
|
+
diff comparisons.
|
|
229
|
+
- Future perf passes can use the same auto-profiler tooling to identify
|
|
230
|
+
the next round of Wins. CI-integrated regression detection is a
|
|
231
|
+
future Phase 4 if there's demand.
|
|
232
|
+
|
|
233
|
+
## [0.7.10] - 2026-05-07
|
|
234
|
+
|
|
235
|
+
Closes the audio chapter. Adds the long-deferred fade transition helper,
|
|
236
|
+
a procedural tone generator, and four custom-DSP filter classes that
|
|
237
|
+
demonstrate the WorkletFilter foundation from 0.7.1. After this release,
|
|
238
|
+
ExoJS audio is feature-complete for the originally-planned scope.
|
|
239
|
+
|
|
240
|
+
### Added
|
|
241
|
+
|
|
242
|
+
- **`crossFade(from, to, durationMs, options?): Promise<void>`** — top-
|
|
243
|
+
level helper that calls `from.fadeOut()` and `to.fadeIn()` in parallel,
|
|
244
|
+
optionally auto-playing `to` if paused. Resolves after `durationMs`
|
|
245
|
+
elapses. Replaces the manual `await` + dual-fade pattern documented in
|
|
246
|
+
0.6.20.
|
|
247
|
+
- **`Envelope`** — ADSR (Attack-Decay-Sustain-Release) generator usable
|
|
248
|
+
on any `AudioParam`. Schedules a gain curve via `trigger()` (attack →
|
|
249
|
+
decay → sustain) and `release()` (sustain → 0). Independent of any
|
|
250
|
+
specific media class — apply to oscillators, filters, or custom
|
|
251
|
+
AudioParam targets.
|
|
252
|
+
- **`OscillatorSound`** — procedural tone generator. No AudioBuffer
|
|
253
|
+
needed — each `play()` synthesizes via WebAudio's `OscillatorNode`.
|
|
254
|
+
Configurable `frequency`, `type` (`sine` | `square` | `sawtooth` |
|
|
255
|
+
`triangle`), `detune` (cents), optional `Envelope`. Pool semantics
|
|
256
|
+
match `Sound` (default `poolSize: 8`, `SoundPoolStrategy.FirstInFirstOut`).
|
|
257
|
+
Static helper `OscillatorSound.midiToFrequency(midiNote)` and
|
|
258
|
+
`setNote(midiNote)` for music apps. Default-routes to `mixer.sound`.
|
|
259
|
+
- **`ChorusFilter`** — modulated-delay chorus / vibrato effect. Native
|
|
260
|
+
WebAudio nodes only (DelayNode + Oscillator LFO + GainNodes), no
|
|
261
|
+
worklet. Configurable `delayMs`, `depthMs`, `rateHz`, `wet`. Use as
|
|
262
|
+
an Audio bus filter:
|
|
263
|
+
```ts
|
|
264
|
+
bus.addFilter(new ChorusFilter({ rateHz: 1.5, depthMs: 5 }));
|
|
265
|
+
```
|
|
266
|
+
- **`PitchShiftFilter`** — granular real-time pitch shifter (WorkletFilter).
|
|
267
|
+
Configurable `pitch` (0.25× to 4×), `wet`, internal `grainSize`. V1
|
|
268
|
+
quality is good for ±1 octave; beyond that, audible granular artifacts.
|
|
269
|
+
Higher-quality phase-vocoder pitch shifting is V2.
|
|
270
|
+
- **`VocoderFilter`** — classic 16-band vocoder (WorkletFilter, 2-input).
|
|
271
|
+
Takes a `modulator: AudioBus` whose spectral envelope shapes the
|
|
272
|
+
carrier signal (the bus the filter is attached to). Configurable
|
|
273
|
+
`numBands`, `minHz`, `maxHz`, `bandQ`, `wet`, `envelopeSmoothing`.
|
|
274
|
+
Per-band biquad bandpass filters + envelope follower entirely in the
|
|
275
|
+
worklet for sample-accurate processing.
|
|
276
|
+
- **`GranularFilter`** — granular synthesis effect. Slices recent input
|
|
277
|
+
audio into Hann-windowed grains and replays them with randomized
|
|
278
|
+
offset and pitch. Configurable `grainSize`, `density`, `spread`,
|
|
279
|
+
`pitchMin`, `pitchMax`, `wet`. Suitable for ambient textures, glitch
|
|
280
|
+
effects, time-stretching, pitch clouds.
|
|
281
|
+
|
|
282
|
+
### Notes
|
|
283
|
+
|
|
284
|
+
- `OscillatorSound` does NOT support spatial audio in V1 (no
|
|
285
|
+
`position` / `velocity` properties). For spatial procedural audio,
|
|
286
|
+
attach the OscillatorSound to a spatial `Sound` bus or wait for a
|
|
287
|
+
future enhancement. `Sound`'s spatial path covers AudioBuffer-based
|
|
288
|
+
sources.
|
|
289
|
+
- All four custom-DSP filters extend the `WorkletFilter` base from
|
|
290
|
+
0.7.1, except `ChorusFilter` which uses native nodes (sufficient for
|
|
291
|
+
modulated-delay topology).
|
|
292
|
+
- The audio chapter as originally scoped is now closed:
|
|
293
|
+
- 0.7.0 — AudioMixer + Buses + Filters + Spatial + Pool
|
|
294
|
+
- 0.7.1 — AudioWorklet foundation + DuckingFilter migration
|
|
295
|
+
- 0.7.2 — BeatDetector (Stage 1+2) + AudioAnalyser rewrite
|
|
296
|
+
- 0.7.7 — 3/4 time-signature detection + AudioListener bugfix
|
|
297
|
+
- 0.7.10 — crossFade + OscillatorSound + Envelope + 4 custom-DSP
|
|
298
|
+
filters (Chorus, PitchShift, Vocoder, Granular)
|
|
299
|
+
- Items deferred indefinitely: HRTF binaural panning, ambisonic /
|
|
300
|
+
surround output, MIDI playback, voice chat, ASR/TTS, format
|
|
301
|
+
conversion, audio editor / waveform UI, custom-loudness
|
|
302
|
+
normalization. These remain out-of-scope per the original audio
|
|
303
|
+
modernization roadmap.
|
|
304
|
+
|
|
305
|
+
## [0.7.9] - 2026-05-07
|
|
306
|
+
|
|
307
|
+
Fixes a GLSL compile-error in the 0.7.8 shader auto-upgrade path.
|
|
308
|
+
|
|
309
|
+
### Fixed
|
|
310
|
+
|
|
311
|
+
- **`upgradeFragmentShaderToGl300()` now always prepends `precision highp
|
|
312
|
+
float;`** before the `out vec4 fragColor;` declaration. Previously, if
|
|
313
|
+
the user's source already contained a precision declaration anywhere
|
|
314
|
+
(e.g., `precision lowp float;` mid-source), the upgrader skipped its
|
|
315
|
+
own injection — but the user's declaration came AFTER the
|
|
316
|
+
`out vec4 fragColor;` line, which itself uses a float-typed variable.
|
|
317
|
+
GLSL ES 3.00 requires precision to be declared before any float-typed
|
|
318
|
+
declaration, so the compiler rejected the output with
|
|
319
|
+
`0:2: '' : No precision specified for (float).`
|
|
320
|
+
|
|
321
|
+
Multiple precision declarations are legal in GLSL ES 3.00 with
|
|
322
|
+
last-precision-wins semantics. The fix always injects `precision highp
|
|
323
|
+
float;` at line 2 (before `out vec4 fragColor;`); the user's own
|
|
324
|
+
precision declaration further down still applies to their code via
|
|
325
|
+
the standard last-precision-wins rule. No semantic change for
|
|
326
|
+
user-provided shader logic; previously-broken shaders with custom
|
|
327
|
+
precision declarations now compile correctly.
|
|
328
|
+
|
|
329
|
+
## [0.7.8] - 2026-05-04
|
|
330
|
+
|
|
331
|
+
GLSL 1.00 → 3.00 auto-upgrade for `WebGl2ShaderFilter` (Shadertoy/ISF
|
|
332
|
+
shaders work out of the box) plus a code-hygiene pass — `Circle.getNormals()`
|
|
333
|
+
now caches via dirty-flag (matching 0.6.19's Sprite pattern), Rectangle-vs-
|
|
334
|
+
Rectangle collision response now reports correct `overlap` value, and the
|
|
335
|
+
`destroy()` audit cleans up TODO comments across 8 value classes (with
|
|
336
|
+
real cleanup logic added to `ObservableVector` and `Circle` where needed).
|
|
337
|
+
|
|
338
|
+
### Added
|
|
339
|
+
|
|
340
|
+
- **`upgradeFragmentShaderToGl300(source)`** — exported utility function.
|
|
341
|
+
Upgrades GLSL ES 1.00 fragment shader source to 3.00 with documented
|
|
342
|
+
transformations (adds `#version 300 es`, `precision highp float`,
|
|
343
|
+
`out vec4 fragColor`, replaces `gl_FragColor` / `texture2D(` /
|
|
344
|
+
`textureCube(` / `texture2DProj(` / `varying`). Idempotent: 3.00
|
|
345
|
+
source returns unchanged. Edge cases not handled (`gl_FragData[N]`,
|
|
346
|
+
`textureLod` variants, etc.) produce GLSL compile errors that the
|
|
347
|
+
user must port manually.
|
|
348
|
+
- **`WebGl2ShaderFilterOptions.autoUpgrade: boolean`** (default `true`)
|
|
349
|
+
— when enabled, the constructor passes the user's `fragmentSource`
|
|
350
|
+
through `upgradeFragmentShaderToGl300()` before storing. Set to
|
|
351
|
+
`false` for strict 3.00 input (legacy code becomes a compile error
|
|
352
|
+
— useful for CI / linting setups that want to catch legacy shaders
|
|
353
|
+
as bugs). Vertex shader source is never auto-upgraded; legacy
|
|
354
|
+
vertex sources must be ported manually.
|
|
355
|
+
|
|
356
|
+
### Performance
|
|
357
|
+
|
|
358
|
+
- **`Circle.getNormals()` cached via dirty flag** (matching the 0.6.19
|
|
359
|
+
pattern for `Sprite.getNormals()`). Returns a stable array of `Vector`
|
|
360
|
+
references on subsequent calls; recomputes only when radius / position
|
|
361
|
+
/ x / y change. Reduces GC pressure in collision-detection hot paths
|
|
362
|
+
(especially SAT polygon-vs-circle).
|
|
363
|
+
- **`Circle.getCollisionVertices()` invalidation bug fixed.** The
|
|
364
|
+
cache existed since the initial commit but was never invalidated on
|
|
365
|
+
position / radius changes — moving a Circle after first collision
|
|
366
|
+
check returned stale vertex positions. Now invalidates correctly via
|
|
367
|
+
`_verticesDirty` flag.
|
|
368
|
+
|
|
369
|
+
### Fixed
|
|
370
|
+
|
|
371
|
+
- **`getCollisionRectangleRectangle.overlap`** now returns the correct
|
|
372
|
+
minimum axis overlap (`min(overlapX, overlapY)`) instead of hardcoded
|
|
373
|
+
`0`. Required for any collision-response logic that pushes shapes
|
|
374
|
+
apart by their overlap distance. Other collision shapes (Circle-vs-
|
|
375
|
+
Circle, Circle-vs-Rectangle, polygon-via-SAT) already computed this
|
|
376
|
+
correctly.
|
|
377
|
+
|
|
378
|
+
### Changed
|
|
379
|
+
|
|
380
|
+
- **`destroy()` audit complete** across 8 value classes:
|
|
381
|
+
- `Vector`, `Size`, `Interval`, `Random`, `Time`, `TorqueAffector`
|
|
382
|
+
— kept as no-op; `// todo` comments replaced with explanatory
|
|
383
|
+
"no-op — pure value class, kept for `Destroyable` interface
|
|
384
|
+
conformance" comments.
|
|
385
|
+
- `ObservableVector` — `destroy()` now nulls the change callback
|
|
386
|
+
to prevent leaks if the instance is held in external scope.
|
|
387
|
+
Field type widened to `(() => void) | null`; all internal call
|
|
388
|
+
sites already used optional-chaining, so no functional change for
|
|
389
|
+
live instances.
|
|
390
|
+
- `Circle` — `destroy()` now destroys all cached `Vector` instances
|
|
391
|
+
in `_collisionVertices` and `_normals` arrays (added in this
|
|
392
|
+
release).
|
|
393
|
+
|
|
394
|
+
### Notes
|
|
395
|
+
|
|
396
|
+
- The autoUpgrade default is `true` so Shadertoy/ISF/legacy shaders
|
|
397
|
+
work without any flag. Strict-3.00 codebases can opt out per filter.
|
|
398
|
+
- Removed the private `Circle.getCollisionVertex` helper — its logic
|
|
399
|
+
was inlined into `getCollisionVertices` for the cache-reuse pattern.
|
|
400
|
+
Internal change, no external impact.
|
|
401
|
+
|
|
402
|
+
## [0.7.7] - 2026-05-04
|
|
403
|
+
|
|
404
|
+
Critical bugfix in `AudioListener` and adds 3/4 time-signature detection
|
|
405
|
+
to `BeatDetector`.
|
|
406
|
+
|
|
407
|
+
### Fixed
|
|
408
|
+
|
|
409
|
+
- **`AudioListener._tick()` no longer crashes in real browsers.** The
|
|
410
|
+
WebAudio `AudioListener` interface does not expose a `.context`
|
|
411
|
+
property — that's an undocumented quirk that does not exist in any
|
|
412
|
+
spec-compliant browser. The previous `_tick()` implementation read
|
|
413
|
+
`_audioListener.context.currentTime`, which crashed
|
|
414
|
+
deterministically on the first frame after audio-context unlock.
|
|
415
|
+
Tests passed because the jsdom mock incorrectly defined a `.context`
|
|
416
|
+
property; that has been removed from the mock as well.
|
|
417
|
+
|
|
418
|
+
**Severity**: production-critical. The bug fired in every ExoJS app
|
|
419
|
+
that triggered `getAudioContext()` (i.e. any app using `Sound`,
|
|
420
|
+
`Music`, `BeatDetector`, `AudioAnalyser`, or `Video` audio), because
|
|
421
|
+
`AudioMixer.update()` ticks the listener every frame regardless of
|
|
422
|
+
whether the user explicitly set `listener.target`.
|
|
423
|
+
|
|
424
|
+
**Fix**: `AudioListener` now stores its `AudioContext` reference in
|
|
425
|
+
a private `_ctx` field at setup time and reads `_ctx.currentTime`
|
|
426
|
+
instead. Mirrors the pattern used elsewhere in the audio stack.
|
|
427
|
+
|
|
428
|
+
### Added
|
|
429
|
+
|
|
430
|
+
- **3/4 time-signature detection in `BeatDetector`** — the worklet
|
|
431
|
+
now tracks parallel posteriors over 4-beat and 3-beat bar
|
|
432
|
+
structures. Active time signature is selected via hysteresis:
|
|
433
|
+
- **EMA confidences** (smoothing α=0.1) for each candidate
|
|
434
|
+
- **Sustain-margin guard**: switching requires the alternate TS's
|
|
435
|
+
confidence to exceed the active by 1.4× for ~12-16 consecutive
|
|
436
|
+
beats. Bridges and breakdowns don't trigger spurious switches.
|
|
437
|
+
- **Settling**: first 8 beats stay 4/4 regardless of evidence
|
|
438
|
+
- **`BeatDetectorOptions.enableTimeSignatureDetection: boolean`**
|
|
439
|
+
(default `true`) — set to `false` to lock detection to 4/4.
|
|
440
|
+
- **`BeatDetector.timeSignature`** stops being hardcoded to
|
|
441
|
+
`{numerator: 4, denominator: 4}` — now reflects the active
|
|
442
|
+
detected TS. Public API unchanged.
|
|
443
|
+
- **`BeatDetector.barLength` and `barPosition`** dynamically reflect
|
|
444
|
+
the active TS (3 vs 4 positions). The `lookahead` array marks
|
|
445
|
+
downbeats based on the active bar length.
|
|
446
|
+
|
|
447
|
+
### Notes
|
|
448
|
+
|
|
449
|
+
- 6/8, 5/4, 7/8 and other odd time signatures are not detected.
|
|
450
|
+
Default-fallback is 4/4 in all ambiguous cases.
|
|
451
|
+
- 3/4 detection works best on stable, percussive 3/4 material
|
|
452
|
+
(waltz-feel music). Performance on Jazz / Rubato / Free-form
|
|
453
|
+
remains weak — consistent with Stage 1+2 limitations from 0.7.2.
|
|
454
|
+
- The mock-cleanup means existing test fixtures that relied on
|
|
455
|
+
`audioContext.listener.context` had to be updated; the production
|
|
456
|
+
path no longer reads that property at all.
|
|
457
|
+
|
|
458
|
+
## [0.7.6] - 2026-05-04
|
|
459
|
+
|
|
460
|
+
Closes the remaining WebGPU / WebGL2 backend parity gaps and cleans up
|
|
461
|
+
vestigial backend API. Adds device-loss / context-loss recovery signals
|
|
462
|
+
on both backends, unifies them under `Application.onBackendLost`, moves
|
|
463
|
+
`setCursor` to Application, and removes dead-code throws from WebGPU.
|
|
464
|
+
|
|
465
|
+
### Added
|
|
466
|
+
|
|
467
|
+
- **`Application.onBackendLost: Signal<[]>`** — unified signal that
|
|
468
|
+
fires when either backend's GPU context is lost (WebGl2 context-lost
|
|
469
|
+
event or WebGpu device-lost promise). User code listens once and
|
|
470
|
+
doesn't care which backend they're on. Useful for showing a "GPU
|
|
471
|
+
driver issue, please reload" dialog.
|
|
472
|
+
- **`WebGl2Backend.onContextLost: Signal<[]>`** — backend-specific
|
|
473
|
+
signal mirroring the existing `webglcontextlost` handler.
|
|
474
|
+
- **`WebGl2Backend.onContextRestored: Signal<[]>`** — backend-specific
|
|
475
|
+
signal mirroring the existing `webglcontextrestored` handler.
|
|
476
|
+
- **`WebGpuBackend.onDeviceLost: Signal<[GPUDeviceLostInfo]>`** —
|
|
477
|
+
WebGPU's `device.lost` promise is now subscribed at initialization;
|
|
478
|
+
resolution dispatches this signal with the loss info. Note: WebGPU
|
|
479
|
+
device loss is irrecoverable on the same device — user code must
|
|
480
|
+
reload, retry, or recreate the application to recover. V1 only
|
|
481
|
+
signals; user decides response strategy.
|
|
482
|
+
- **`WebGpuBackend.deviceLost: boolean`** — getter for current
|
|
483
|
+
device-loss state.
|
|
484
|
+
- **`WebGpuBackend.clearColor: Color`** + **`setClearColor(color)`** —
|
|
485
|
+
persistent clear color, matching WebGl2's API. `clear()` without
|
|
486
|
+
arguments uses the persistent color.
|
|
487
|
+
- **`Application.setCursor(cursor)`** + **`cursor` property** — moved
|
|
488
|
+
here from `WebGl2Backend`. Accepts CSS cursor strings or a
|
|
489
|
+
`Texture` / `HTMLImageElement` / `HTMLCanvasElement` (converted to
|
|
490
|
+
a `url(...)` cursor). Sets `canvas.style.cursor` directly.
|
|
491
|
+
|
|
492
|
+
### Changed (BREAKING)
|
|
493
|
+
|
|
494
|
+
- **`WebGl2Backend.setCursor()` and `cursor` getter removed.** Use
|
|
495
|
+
`app.setCursor(...)` or `app.cursor = ...` instead. Cursor is a DOM
|
|
496
|
+
concern, not a backend concern; this corrects the misplacement.
|
|
497
|
+
- **`WebGpuBackend.setShader()` removed.** Was a vestigial throw with
|
|
498
|
+
no callers. WebGPU's pipeline-based architecture doesn't fit the
|
|
499
|
+
imperative `setShader` pattern. Custom shaders go through
|
|
500
|
+
`WebGpuShaderFilter` (since 0.7.4).
|
|
501
|
+
- **`WebGpuBackend.setVao()` removed.** VAOs are a WebGL concept;
|
|
502
|
+
WebGPU uses bind groups + pipelines. Method had no callers.
|
|
503
|
+
- **`WebGpuBackend.setTexture()` and `setRenderTarget()` no-longer-
|
|
504
|
+
throwing on RenderTarget subclass guards.** Throws were unreachable
|
|
505
|
+
because `RenderTexture` is the only `RenderTarget` subclass. The
|
|
506
|
+
guards are gone; the type system already prevents misuse.
|
|
507
|
+
- **`WebGpuBackend.setBlendMode()` no-longer-throwing**. Internal
|
|
508
|
+
renderers call this during their pipeline setup; the previous throw
|
|
509
|
+
for unrecognized modes was unreachable (covered all 5 valid blend
|
|
510
|
+
modes). Method now silently returns; the actual blend logic lives in
|
|
511
|
+
the pipeline-creation paths inside `WebGpuBlendState` and the
|
|
512
|
+
individual renderers.
|
|
513
|
+
|
|
514
|
+
### Migration
|
|
515
|
+
|
|
516
|
+
```ts
|
|
517
|
+
// Before:
|
|
518
|
+
app.backend.setCursor('pointer');
|
|
519
|
+
const cursor = app.backend.cursor;
|
|
520
|
+
|
|
521
|
+
// After:
|
|
522
|
+
app.setCursor('pointer'); // or
|
|
523
|
+
app.cursor = 'pointer';
|
|
524
|
+
const cursor = app.cursor;
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
```ts
|
|
528
|
+
// New: react to backend loss
|
|
529
|
+
app.onBackendLost.add(() => {
|
|
530
|
+
showReloadDialog();
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
// Or backend-specific:
|
|
534
|
+
if (app.backend.backendType === RenderBackendType.WebGpu) {
|
|
535
|
+
(app.backend as WebGpuBackend).onDeviceLost.add((info) => {
|
|
536
|
+
console.error('GPU device lost:', info.message, info.reason);
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
### Notes
|
|
542
|
+
|
|
543
|
+
- Device-loss is irrecoverable on WebGPU (the lost device cannot be
|
|
544
|
+
reused; recovery requires creating a fresh device, which means
|
|
545
|
+
re-initializing the application). V1 dispatches the signal and stops;
|
|
546
|
+
the user's app code decides whether to reload, retry, or fall back.
|
|
547
|
+
- `setBlendMode` could be removed entirely in a future cleanup if the
|
|
548
|
+
pipeline-creation path is the only place blend state is set, but it
|
|
549
|
+
remains as a no-op for now to preserve internal call sites.
|
|
550
|
+
|
|
551
|
+
## [0.7.5] - 2026-05-04
|
|
552
|
+
|
|
553
|
+
Expands the debug overlay with three new layers: `BoundingBoxesLayer`,
|
|
554
|
+
`HitTestLayer`, and `PointerStackLayer`. Adds a master visibility switch
|
|
555
|
+
on `DebugOverlay`. Layers can now opt into world-space rendering for
|
|
556
|
+
overlays that need to align with scene content. F2 / F3 / F4 keys are
|
|
557
|
+
hardcoded to toggle the new layers (matching the existing F1 for
|
|
558
|
+
Performance).
|
|
559
|
+
|
|
560
|
+
### Added
|
|
561
|
+
|
|
562
|
+
- **`BoundingBoxesLayer`** — renders AABB outlines for every visible
|
|
563
|
+
RenderNode in the active scene. Color cycles through HSL hue based
|
|
564
|
+
on `zIndex` (`hue = (zIndex * 30) % 360`), so layered nodes are
|
|
565
|
+
visually distinct. Toggle via F2 or
|
|
566
|
+
`debug.layers.boundingBoxes.visible = true`.
|
|
567
|
+
- **`HitTestLayer`** — outlines for `interactive` nodes only, with
|
|
568
|
+
state-based colors:
|
|
569
|
+
- **Magenta** (idle interactive)
|
|
570
|
+
- **Yellow** (currently hovered, via `app.interaction.getHoveredNode()`)
|
|
571
|
+
- **Cyan** (captured by an active drag, via the new
|
|
572
|
+
`getCapturedNodes()` accessor)
|
|
573
|
+
- When `useSpatialIndex` is enabled on InteractionManager,
|
|
574
|
+
additionally draws faint quadtree quadrant outlines.
|
|
575
|
+
- Toggle via F3.
|
|
576
|
+
- **`PointerStackLayer`** — fixed top-right text panel listing all
|
|
577
|
+
RenderNodes in the active scene whose `contains(worldX, worldY)`
|
|
578
|
+
matches the primary pointer position. Sorted by `zIndex`
|
|
579
|
+
descending (top of stack first). Limited to 10 entries to avoid
|
|
580
|
+
overflow. Useful for debugging "why isn't this clickable" — see
|
|
581
|
+
exactly what's stacked under the cursor. Toggle via F4.
|
|
582
|
+
- **`DebugOverlay.visible: boolean`** (default `true`) — master gate
|
|
583
|
+
that suppresses all layer rendering when `false` while preserving
|
|
584
|
+
individual layer states. Restoring `debug.visible = true` brings
|
|
585
|
+
layers back without rewiring.
|
|
586
|
+
- **`DebugLayer.viewMode: 'screen' | 'world'`** — abstract getter
|
|
587
|
+
(default `'screen'`); subclasses override. The DebugOverlay groups
|
|
588
|
+
layers by viewMode and swaps `backend.view` accordingly: world-mode
|
|
589
|
+
layers render in the active scene's view (matching scene
|
|
590
|
+
coordinates), screen-mode layers render in canvas-pixel space.
|
|
591
|
+
- **`InteractionManager.getCapturedNodes(): ReadonlyArray<RenderNode>`** —
|
|
592
|
+
returns the nodes currently captured by active drags. Used by
|
|
593
|
+
HitTestLayer; also generally useful.
|
|
594
|
+
- **`InputManager.getPrimaryPointerPosition()`** — returns the canvas-
|
|
595
|
+
pixel position of the primary pointer (or null if none active).
|
|
596
|
+
|
|
597
|
+
### Notes
|
|
598
|
+
|
|
599
|
+
- F2 / F3 / F4 are hardcoded for V1 (matching F1 from 0.6.17). A
|
|
600
|
+
`keybindings: false` opt-out comes when there's concrete demand.
|
|
601
|
+
- BoundingBoxes color cycle is intentionally simple (`hue = z * 30 % 360`).
|
|
602
|
+
Adapts to any z range without per-frame normalization. If two nodes
|
|
603
|
+
share zIndex, they share color — that's fine, the layer's purpose is
|
|
604
|
+
visualizing depth differences.
|
|
605
|
+
- World-mode layers (BoundingBoxes, HitTest) render BEFORE screen-mode
|
|
606
|
+
layers (Performance, PointerStack) in each frame, so text panels
|
|
607
|
+
appear on top of outlines.
|
|
608
|
+
|
|
609
|
+
## [0.7.4] - 2026-05-04
|
|
610
|
+
|
|
611
|
+
Renames `ShaderFilter` → `WebGl2ShaderFilter` and adds `WebGpuShaderFilter`
|
|
612
|
+
— full backend-specific custom shader support. Custom post-process
|
|
613
|
+
shaders now work on both WebGL2 (GLSL) and WebGPU (WGSL) backends with
|
|
614
|
+
explicit, type-safe class names matching the rest of the codebase
|
|
615
|
+
(WebGl2Backend / WebGpuBackend, WebGl2SpriteRenderer / WebGpuSpriteRenderer,
|
|
616
|
+
etc.).
|
|
617
|
+
|
|
618
|
+
### Added
|
|
619
|
+
|
|
620
|
+
- **`WebGpuShaderFilter`** — full WGSL fragment shader support on the
|
|
621
|
+
WebGPU backend. API mirrors `WebGl2ShaderFilter` — accepts WGSL source,
|
|
622
|
+
exposes a mutable `uniforms` map, applies as a post-process Filter via
|
|
623
|
+
`node.filters = [filter]`. Internally creates GPUShaderModules,
|
|
624
|
+
bind-group layouts, render pipeline, and fullscreen-quad vertex buffer
|
|
625
|
+
using the same patterns as `WebGpuMaskCompositor`.
|
|
626
|
+
- **WGSL auto-bindings** in `@group(0)`:
|
|
627
|
+
- `@binding(0) var<uniform> uResolution: vec2<f32>` — output dimensions
|
|
628
|
+
- `@binding(1) var uTexture: texture_2d<f32>` — input texture
|
|
629
|
+
- `@binding(2) var uSampler: sampler` — linear sampler
|
|
630
|
+
- **User uniforms** in `@group(1)` — packed into a uniform buffer with
|
|
631
|
+
16-byte alignment per slot (per WGSL alignment rules; vec3 is 16-byte
|
|
632
|
+
aligned, not 12). Texture uniforms get separate bind group entries.
|
|
633
|
+
- **WGSL default vertex shader** when omitted — fullscreen pass-through
|
|
634
|
+
with a `vUv: vec2<f32>` varying.
|
|
635
|
+
|
|
636
|
+
### Changed (BREAKING)
|
|
637
|
+
|
|
638
|
+
- **`ShaderFilter` → `WebGl2ShaderFilter`** — the class was always
|
|
639
|
+
WebGL2-only; the name now reflects that. Same API otherwise.
|
|
640
|
+
- **`ShaderFilterOptions` → `WebGl2ShaderFilterOptions`**.
|
|
641
|
+
- **`wgsl` option removed from `WebGl2ShaderFilterOptions`** — was
|
|
642
|
+
reserved API surface for future WGSL support, now superseded by the
|
|
643
|
+
separate `WebGpuShaderFilter`.
|
|
644
|
+
- **Backend guard messages updated**:
|
|
645
|
+
- `WebGl2ShaderFilter` on WebGPU: `'WebGl2ShaderFilter requires the
|
|
646
|
+
WebGL2 backend. Use WebGpuShaderFilter on WebGPU.'`
|
|
647
|
+
- `WebGpuShaderFilter` on WebGL2: `'WebGpuShaderFilter requires the
|
|
648
|
+
WebGPU backend. Use WebGl2ShaderFilter on WebGL2.'`
|
|
649
|
+
|
|
650
|
+
`ShaderFilterUniformValue` (the polymorphic uniform value type) is
|
|
651
|
+
**unchanged** and shared between both backends — same value shapes
|
|
652
|
+
(number / tuples / TypedArrays / Texture).
|
|
653
|
+
|
|
654
|
+
### Migration
|
|
655
|
+
|
|
656
|
+
```ts
|
|
657
|
+
// Before (0.7.3):
|
|
658
|
+
import { ShaderFilter } from '@codexo/exojs';
|
|
659
|
+
const filter = new ShaderFilter({ fragmentSource: glsl, uniforms: { ... } });
|
|
660
|
+
|
|
661
|
+
// After (0.7.4):
|
|
662
|
+
import { WebGl2ShaderFilter } from '@codexo/exojs';
|
|
663
|
+
const filter = new WebGl2ShaderFilter({ fragmentSource: glsl, uniforms: { ... } });
|
|
664
|
+
|
|
665
|
+
// New on WebGPU:
|
|
666
|
+
import { WebGpuShaderFilter } from '@codexo/exojs';
|
|
667
|
+
const filter = new WebGpuShaderFilter({ fragmentSource: wgsl, uniforms: { ... } });
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
### Notes
|
|
671
|
+
|
|
672
|
+
- Two separate classes (rather than one polymorphic class with both
|
|
673
|
+
shader sources) reflects the reality that GLSL and WGSL are entirely
|
|
674
|
+
different languages with different binding models. Users writing a
|
|
675
|
+
custom shader inherently know their backend; the explicit class name
|
|
676
|
+
matches that mental model.
|
|
677
|
+
- 0.7.3 is effectively replaced — it shipped with the wrong name and a
|
|
678
|
+
WebGPU stub. Window of exposure was minutes; this is corrective.
|
|
679
|
+
- WGSL alignment rules differ from GLSL std140: vec3 occupies 16 bytes
|
|
680
|
+
(not 12). The user's WGSL struct must declare members accordingly.
|
|
681
|
+
- Performance for fullscreen pixel-shader rendering is equivalent on
|
|
682
|
+
both backends — choose based on browser support, ecosystem
|
|
683
|
+
familiarity (GLSL has more tutorials / Shadertoy), or future-proofing
|
|
684
|
+
preference (WebGPU is the long-term direction).
|
|
685
|
+
|
|
686
|
+
## [0.7.3] - 2026-05-04
|
|
687
|
+
|
|
688
|
+
Adds `ShaderFilter` — a high-level Filter subclass that renders the input
|
|
689
|
+
through a user-provided GLSL fragment shader. Unlocks custom post-process
|
|
690
|
+
effects: visualizers, demoscene shaders, glitch/scanline/dithering passes,
|
|
691
|
+
LUT color grading, chromatic aberration, etc.
|
|
692
|
+
|
|
693
|
+
### Added
|
|
694
|
+
|
|
695
|
+
- **`ShaderFilter`** — accepts a fragment shader source string + uniforms,
|
|
696
|
+
applies it as a post-process filter on any `RenderNode` via
|
|
697
|
+
`node.filters = [shaderFilter]`. Internally lazy-compiles the shader on
|
|
698
|
+
first apply, allocates a per-instance fullscreen-quad vertex buffer,
|
|
699
|
+
and uses the existing `RenderTargetPass` orchestration shared with
|
|
700
|
+
built-in filters like `BlurFilter`.
|
|
701
|
+
- **Auto-bound uniforms** for the user shader:
|
|
702
|
+
- `uniform sampler2D uTexture` — the filter's input
|
|
703
|
+
- `uniform vec2 uResolution` — output dimensions
|
|
704
|
+
- `in vec2 vUv` (varying) — 0..1 UVs across the quad
|
|
705
|
+
- **`ShaderFilter.uniforms`** — mutable map for user uniforms. Set values
|
|
706
|
+
via property assignment; flushed before each apply():
|
|
707
|
+
```ts
|
|
708
|
+
filter.uniforms.uTime = performance.now() / 1000;
|
|
709
|
+
filter.uniforms.uColor = [1, 0.5, 0, 1]; // vec4
|
|
710
|
+
```
|
|
711
|
+
- **Polymorphic uniform values**: scalar `number`, tuple `[a, b]` /
|
|
712
|
+
`[a, b, c]` / `[a, b, c, d]`, `Float32Array` / `Int32Array`, or
|
|
713
|
+
`Texture` / `RenderTexture` (auto-bound to a sampler slot).
|
|
714
|
+
- **Default vertex shader** when `vertexSource` is omitted — pass-through
|
|
715
|
+
fullscreen quad. User can supply a custom vertex shader for warps /
|
|
716
|
+
vertex displacement effects.
|
|
717
|
+
- **`wgsl` option** in `ShaderFilterOptions` — reserved API surface for
|
|
718
|
+
WebGPU support landing in a future release.
|
|
719
|
+
|
|
720
|
+
### Notes
|
|
721
|
+
|
|
722
|
+
- **WebGL2-only in V1.** Constructor accepts `wgsl` source, but `apply()`
|
|
723
|
+
on the WebGPU backend throws `'ShaderFilter does not yet support the
|
|
724
|
+
WebGPU backend. WGSL support is planned for a future release. Use the
|
|
725
|
+
WebGL2 backend for now.'` Document this limitation; reasoning: WebGPU
|
|
726
|
+
requires a separate WGSL pipeline implementation that's substantial
|
|
727
|
+
on its own. Coming when there's concrete user demand.
|
|
728
|
+
- `fragmentSource` is required at construction. Constructor throws if
|
|
729
|
+
missing.
|
|
730
|
+
- Internally reuses the existing `Shader` + `WebGl2ShaderProgram`
|
|
731
|
+
infrastructure — no new public Backend methods added.
|
|
732
|
+
- Vertex buffer is per-instance (4 vertices × 16 bytes = 64 bytes per
|
|
733
|
+
filter). Pooling across instances was considered but rejected for V1
|
|
734
|
+
to avoid cross-instance lifecycle coupling.
|
|
735
|
+
|
|
736
|
+
### Usage
|
|
737
|
+
|
|
738
|
+
```ts
|
|
739
|
+
import { ShaderFilter } from '@codexo/exojs';
|
|
740
|
+
|
|
741
|
+
const filter = new ShaderFilter({
|
|
742
|
+
fragmentSource: `#version 300 es
|
|
743
|
+
precision highp float;
|
|
744
|
+
in vec2 vUv;
|
|
745
|
+
uniform sampler2D uTexture;
|
|
746
|
+
uniform vec2 uResolution;
|
|
747
|
+
uniform float uTime;
|
|
748
|
+
out vec4 outColor;
|
|
749
|
+
void main() {
|
|
750
|
+
vec2 uv = vUv;
|
|
751
|
+
uv.x += sin(uv.y * 10.0 + uTime) * 0.01; // wavy distort
|
|
752
|
+
outColor = texture(uTexture, uv);
|
|
753
|
+
}
|
|
754
|
+
`,
|
|
755
|
+
uniforms: {
|
|
756
|
+
uTime: 0,
|
|
757
|
+
},
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
sprite.filters = [filter];
|
|
761
|
+
|
|
762
|
+
app.onFrame.add((delta) => {
|
|
763
|
+
filter.uniforms.uTime = performance.now() / 1000;
|
|
764
|
+
});
|
|
765
|
+
```
|
|
766
|
+
|
|
767
|
+
## [0.7.2] - 2026-05-04
|
|
768
|
+
|
|
769
|
+
Adds `BeatDetector` (Stage 1+2: causal DSP hybrid tracker with bar-aware
|
|
770
|
+
state model) and rewrites `AudioAnalyser` with a polymorphic source
|
|
771
|
+
setter and convenience helpers. **Breaking change** to AudioAnalyser
|
|
772
|
+
API — see migration below. Pure-additive on BeatDetector.
|
|
773
|
+
|
|
774
|
+
### Added
|
|
775
|
+
|
|
776
|
+
- **`BeatDetector`** — Stage 1+2 beat tracker via AudioWorkletNode.
|
|
777
|
+
Causal DSP pipeline: log-mel spectral flux → 6-second sliding
|
|
778
|
+
tempogram → top-K tempo candidates with octave-error hysteresis →
|
|
779
|
+
phase tracker with novelty-snap correction → HMM-lite bar-position
|
|
780
|
+
posterior. ~500 LOC of inlined worklet source, all in plain JS, no
|
|
781
|
+
dependencies. Polymorphic `source` setter accepts `AudioBus`,
|
|
782
|
+
`Sound`, `Music`, `MediaStream`, raw `AudioNode`, or `null`.
|
|
783
|
+
- **BeatDetector live state**:
|
|
784
|
+
- Stage 1: `tempo`, `beatPhase`, `nextBeatTime`, `confidence`,
|
|
785
|
+
`gridStability`, `tempoCandidates`, `rms`, `onsetStrength`,
|
|
786
|
+
`bandEnergy`
|
|
787
|
+
- Stage 2: `barPosition` (1..N within bar), `barLength`,
|
|
788
|
+
`timeSignature` (currently always 4/4 in V1), `nextDownbeatTime`,
|
|
789
|
+
`lookahead` (next 8 beats projected with audio-time precision)
|
|
790
|
+
- **BeatDetector signals**:
|
|
791
|
+
- Stage 1: `onBeat`, `onTempoChange`
|
|
792
|
+
- Stage 2: `onDownbeat` (the "1" of each bar), `onBarStart`,
|
|
793
|
+
`onBeatPredicted` (when lookahead updates)
|
|
794
|
+
- **`BeatDetectorOptions`** — `minBpm` (default 50), `maxBpm` (default
|
|
795
|
+
250), `fftSize` (default 2048), `hopSize` (default 512),
|
|
796
|
+
`tempoWindowSec` (default 6), `settlingMs` (default 1500), `melBands`
|
|
797
|
+
(default 24).
|
|
798
|
+
- **Settling period** — first `settlingMs` ms after worklet starts,
|
|
799
|
+
beats are suppressed and `confidence` is `0`. Prevents spurious early
|
|
800
|
+
beat firings before the tempogram has stabilized.
|
|
801
|
+
- **Anti-half/double-tempo hysteresis** — top-K candidates retain
|
|
802
|
+
octave-related tempos; switch only with 1.5× score margin to resist
|
|
803
|
+
the classic 60↔120↔240 BPM flipping.
|
|
804
|
+
- **DSP utilities** in `@/audio/dsp` — pure-function exports for
|
|
805
|
+
`fft`, `mel`, `tempogram`. Used internally by the worklet (inlined
|
|
806
|
+
as JS strings) but also testable in isolation. Also usable directly
|
|
807
|
+
by advanced users for custom analysis.
|
|
808
|
+
- **`AudioAnalyser` rewrite** — polymorphic `source` setter (same 5
|
|
809
|
+
source types as BeatDetector). Lazy-init pattern (works before
|
|
810
|
+
AudioContext is unlocked).
|
|
811
|
+
- **`AudioAnalyser` data getters**: `getSpectrum(into?)`,
|
|
812
|
+
`getSpectrumFloat(into?)`, `getWaveform(into?)`,
|
|
813
|
+
`getWaveformFloat(into?)` — all support a user-provided buffer for
|
|
814
|
+
zero-allocation reads.
|
|
815
|
+
- **`AudioAnalyser` convenience**: `getBandEnergy(fromHz, toHz)`,
|
|
816
|
+
`getLowMidHigh()`, `getRms()` — high-level helpers for visualizers
|
|
817
|
+
and reactive UI.
|
|
818
|
+
|
|
819
|
+
### Changed (BREAKING)
|
|
820
|
+
|
|
821
|
+
- **`AudioAnalyser` constructor signature changed.** Old:
|
|
822
|
+
`new AudioAnalyser(media, options)`. New:
|
|
823
|
+
`new AudioAnalyser(options?); analyser.source = media`.
|
|
824
|
+
- **`AudioAnalyser` data properties replaced with methods.** Old
|
|
825
|
+
getters `timeDomainData`, `frequencyData`, `preciseTimeDomainData`,
|
|
826
|
+
`preciseFrequencyData` are removed. Use `getWaveform()`,
|
|
827
|
+
`getSpectrum()`, `getWaveformFloat()`, `getSpectrumFloat()`
|
|
828
|
+
respectively. The new methods accept an optional `into` buffer
|
|
829
|
+
argument for zero-allocation reuse.
|
|
830
|
+
- **`AudioAnalyser.connect()` removed.** Connection is now automatic
|
|
831
|
+
on `source` assignment.
|
|
832
|
+
|
|
833
|
+
### Migration
|
|
834
|
+
|
|
835
|
+
```ts
|
|
836
|
+
// Before:
|
|
837
|
+
const analyser = new AudioAnalyser(music, { fftSize: 1024 });
|
|
838
|
+
analyser.connect();
|
|
839
|
+
const spectrum = analyser.frequencyData;
|
|
840
|
+
const waveform = analyser.timeDomainData;
|
|
841
|
+
|
|
842
|
+
// After:
|
|
843
|
+
const analyser = new AudioAnalyser({ fftSize: 1024 });
|
|
844
|
+
analyser.source = music;
|
|
845
|
+
const spectrum = analyser.getSpectrum();
|
|
846
|
+
const waveform = analyser.getWaveform();
|
|
847
|
+
|
|
848
|
+
// Now also possible:
|
|
849
|
+
analyser.source = mediaStream; // Mic input
|
|
850
|
+
analyser.source = app.audio.master; // Whole mix
|
|
851
|
+
analyser.getBandEnergy(20, 200); // Bass energy 0..1
|
|
852
|
+
analyser.getLowMidHigh(); // {low, mid, high}
|
|
853
|
+
```
|
|
854
|
+
|
|
855
|
+
```ts
|
|
856
|
+
// New: BeatDetector
|
|
857
|
+
const detector = new BeatDetector();
|
|
858
|
+
detector.source = music;
|
|
859
|
+
await detector.ready;
|
|
860
|
+
|
|
861
|
+
detector.onBeat.add(({ audioTime, tempo, isDownbeat, energy }) => {
|
|
862
|
+
sprite.scale.set(1.5);
|
|
863
|
+
new Tween().target(sprite.scale).to({x: 1, y: 1}).duration(200).start();
|
|
864
|
+
});
|
|
865
|
+
|
|
866
|
+
detector.onDownbeat.add(() => {
|
|
867
|
+
boss.attack(); // syncs exactly to "the 1" of each bar
|
|
868
|
+
});
|
|
869
|
+
```
|
|
870
|
+
|
|
871
|
+
### Notes
|
|
872
|
+
|
|
873
|
+
- BeatDetector is calibrated for percussive, metrically stable music
|
|
874
|
+
(Pop, EDM, Dance, Hip-Hop). Expect ~85-92% beat F1 in that range.
|
|
875
|
+
Performance on Jazz, Classical, and Ambient is weaker (50-65%) —
|
|
876
|
+
Stage 3 (CRNN-based activations) would address that and is deferred.
|
|
877
|
+
- Time-signature detection is hardcoded to 4/4 in V1. Bar-position
|
|
878
|
+
tracking still works (HMM-lite over 4 beats); 3/4 detection comes
|
|
879
|
+
later if needed.
|
|
880
|
+
- Lookahead returns 8 beats projected at current tempo. Game-event
|
|
881
|
+
scheduling can use `audioContext.currentTime` differences for
|
|
882
|
+
sample-accurate alignment.
|
|
883
|
+
- The DSP runs entirely in the audio thread via AudioWorklet — no
|
|
884
|
+
main-thread CPU pressure, no jitter from GC or task scheduling. The
|
|
885
|
+
worklet source is embedded as a JS string in BeatDetector.ts (no
|
|
886
|
+
separate asset shipped).
|
|
887
|
+
|
|
888
|
+
## [0.7.1] - 2026-05-04
|
|
889
|
+
|
|
890
|
+
Adds an AudioWorklet foundation and migrates `DuckingFilter` from
|
|
891
|
+
CPU-thread `setInterval(60Hz)` polling to sample-accurate audio-thread
|
|
892
|
+
DSP. Establishes the architecture for future custom-DSP filters
|
|
893
|
+
(Chorus, Pitch-Shift, Vocoder, etc.) without shipping any new effect
|
|
894
|
+
filters in this release.
|
|
895
|
+
|
|
896
|
+
### Added
|
|
897
|
+
|
|
898
|
+
- **`registerWorkletProcessor(audioContext, name, source)`** — Blob-URL
|
|
899
|
+
based helper for registering AudioWorkletProcessors at runtime from a
|
|
900
|
+
source string. No build-tooling changes required: worklet code lives
|
|
901
|
+
as a JavaScript string inside the TypeScript file, gets converted to
|
|
902
|
+
a Blob URL on first registration, and is cached per-AudioContext.
|
|
903
|
+
Concurrent registrations are deduplicated via shared in-flight
|
|
904
|
+
Promises.
|
|
905
|
+
- **`WorkletFilter`** — abstract base class extending `AudioFilter` for
|
|
906
|
+
filters implemented as AudioWorklet processors. Subclasses declare
|
|
907
|
+
`_workletName`, `_workletSource`, and (optionally) `_workletOptions`
|
|
908
|
+
/ `_onWorkletReady`. The base handles:
|
|
909
|
+
- Async worklet loading lifecycle
|
|
910
|
+
- Stable `inputNode` / `outputNode` GainNodes that exist immediately
|
|
911
|
+
(audio passes through directly while the worklet loads, then
|
|
912
|
+
re-routes through the worklet once ready — no destruction or
|
|
913
|
+
re-wiring on the bus side)
|
|
914
|
+
- `_setAudioParam(name, value)` helper for smooth parameter updates
|
|
915
|
+
- Safe destruction during async load
|
|
916
|
+
- **`AudioFilter.ready: Promise<void>`** — resolves when the filter is
|
|
917
|
+
fully initialized. Sync filters (BiquadFilter-backed, etc.) return
|
|
918
|
+
an already-resolved Promise. Async filters (WorkletFilter
|
|
919
|
+
subclasses) return a Promise that resolves once the worklet has
|
|
920
|
+
loaded. Useful when user code wants to `await` a parameter setup
|
|
921
|
+
that depends on the underlying node existing.
|
|
922
|
+
|
|
923
|
+
### Changed
|
|
924
|
+
|
|
925
|
+
- **`DuckingFilter` is now AudioWorklet-backed.** The setInterval-based
|
|
926
|
+
envelope follower has been replaced with a sample-accurate worklet
|
|
927
|
+
processor. Public API is unchanged: same constructor options
|
|
928
|
+
(`sidechain`, `threshold`, `ratio`, `attackMs`, `releaseMs`), same
|
|
929
|
+
property setters. Behaviorally:
|
|
930
|
+
- Detection runs at full sample-rate (typically 48 kHz) instead of
|
|
931
|
+
60 Hz polling
|
|
932
|
+
- Audio-thread isolated — no jitter from main-thread garbage
|
|
933
|
+
collection or task pressure
|
|
934
|
+
- Functions correctly when the page tab is inactive (audio thread
|
|
935
|
+
keeps running while CPU thread is throttled)
|
|
936
|
+
- Initial use has a one-time ~10–50 ms async load cost as the
|
|
937
|
+
worklet code registers; during that window the filter passes
|
|
938
|
+
audio through unmodified
|
|
939
|
+
|
|
940
|
+
### Notes
|
|
941
|
+
|
|
942
|
+
- AudioWorklet is supported in all browsers since 2020 (Chrome 66+,
|
|
943
|
+
Firefox 76+, Safari 14.1+). No fallback to the old setInterval
|
|
944
|
+
approach — environments without worklet support will throw on
|
|
945
|
+
DuckingFilter construction.
|
|
946
|
+
- The shared infrastructure (`registerWorkletProcessor` +
|
|
947
|
+
`WorkletFilter`) is the foundation for future custom-DSP filters.
|
|
948
|
+
Concrete filter additions (Chorus, Pitch-Shift, Vocoder, Granular,
|
|
949
|
+
etc.) come in subsequent releases.
|
|
950
|
+
- BeatDetector / AudioAnalyser hook revamp is deferred — that's the
|
|
951
|
+
next focused topic.
|
|
952
|
+
|
|
953
|
+
## [0.7.0] - 2026-05-04
|
|
954
|
+
|
|
955
|
+
Audio modernization. Introduces a routing manager with hierarchical buses,
|
|
956
|
+
a filter API consistent with the rendering side, 2D spatial audio, and
|
|
957
|
+
unifies `Sound.play()` into a multi-instance default. Pure-additive on
|
|
958
|
+
the bus / filter / spatial side; the `Sound.play()` semantics are a
|
|
959
|
+
breaking change.
|
|
960
|
+
|
|
961
|
+
### Added
|
|
962
|
+
|
|
963
|
+
- **`AudioManager`** — routing mixer accessible via `app.audio` (lazy
|
|
964
|
+
module-level singleton, also reachable via `getAudioManager()`).
|
|
965
|
+
Built-in buses `master`, `music`, `sound` with hierarchy
|
|
966
|
+
(`music` and `sound` are children of `master`).
|
|
967
|
+
- **`AudioBus`** — class with `name` (positional constructor arg),
|
|
968
|
+
`parent`, `volume`, `muted`, `pan`, `addFilter`, `removeFilter`,
|
|
969
|
+
`fadeIn`, `fadeOut`, `destroy`. Internal node chain is
|
|
970
|
+
`inputNode → [filters...] → panNode → outputNode → parent.input`.
|
|
971
|
+
- **Mixer API**: `app.audio.registerBus(bus)`, `getBus(name)`,
|
|
972
|
+
`hasBus(name)`, `unregisterBus(bus)`. Built-ins cannot be
|
|
973
|
+
unregistered.
|
|
974
|
+
- **Default routing**: `Sound` → `app.audio.sound`, `Music` →
|
|
975
|
+
`app.audio.music`, `Video` → `app.audio.master`. Override by
|
|
976
|
+
setting `media.bus = customBus`.
|
|
977
|
+
- **`AudioManager.muteOnHidden: boolean`** — when true, master is
|
|
978
|
+
muted while `document.visibilityState !== 'visible'`. Wired
|
|
979
|
+
through the `app.onVisibilityChange` signal added in 0.6.20.
|
|
980
|
+
- **`AudioFilter`** — abstract base with `inputNode`, `outputNode`,
|
|
981
|
+
`destroy()`. Buses chain filter `inputNode → outputNode` in the
|
|
982
|
+
order they were added.
|
|
983
|
+
- **Filter implementations**: `LowpassFilter`, `HighpassFilter`,
|
|
984
|
+
`CompressorFilter`, `DelayFilter`, `ReverbFilter` (algorithmic
|
|
985
|
+
impulse-response, no IR assets shipped), `EqualizerFilter`
|
|
986
|
+
(3-band low-shelf / peaking / high-shelf), `DuckingFilter`
|
|
987
|
+
(sidechain-driven gain reduction via `AnalyserNode` polled at
|
|
988
|
+
~60 Hz; takes a `sidechain: AudioBus` option).
|
|
989
|
+
- **`AudioListener`** — accessible at `app.audio.listener`. Has
|
|
990
|
+
`position: Vector`, `velocity: Vector`, and a polymorphic
|
|
991
|
+
`target: SceneNode | View | { x, y } | null` that auto-feeds
|
|
992
|
+
the WebAudio listener position each frame.
|
|
993
|
+
- **`Sound.position: Vector | null`** — when non-null, the sound
|
|
994
|
+
becomes spatial: routes through a `PannerNode`
|
|
995
|
+
(`panningModel: 'equalpower'`, `distanceModel: 'linear'`) and
|
|
996
|
+
ticks per-frame from `AudioManager.update()`. Setting back to null
|
|
997
|
+
tears down the panner and restores non-spatial routing.
|
|
998
|
+
- **`Sound.velocity: Vector | null`** — tracked for future Doppler
|
|
999
|
+
use (modern WebAudio infers Doppler implicitly from positional
|
|
1000
|
+
change between frames; we don't pipe velocity to the panner
|
|
1001
|
+
directly).
|
|
1002
|
+
- **`SoundPoolStrategy` enum** — `FirstInFirstOut`,
|
|
1003
|
+
`LeastRecentlyUsed`, `LowestPriority`. Selects the eviction
|
|
1004
|
+
policy when pool capacity is reached.
|
|
1005
|
+
- **`Sound.priority: number`** — used by the `LowestPriority`
|
|
1006
|
+
strategy. Default 0.
|
|
1007
|
+
- **`AudioManager.update()`** — public per-frame tick called from
|
|
1008
|
+
`Application.update()` between `interaction.update()` and
|
|
1009
|
+
`tweens.update()`. Updates listener position from target,
|
|
1010
|
+
ticks each registered spatial sound's panner.
|
|
1011
|
+
|
|
1012
|
+
### Changed (BREAKING)
|
|
1013
|
+
|
|
1014
|
+
- **`Sound.play()` is now multi-instance by default.** Each call
|
|
1015
|
+
creates a new pooled instance up to `poolSize`. The previous
|
|
1016
|
+
singleton-replace behavior is opt-in via
|
|
1017
|
+
`play({ replace: true })`.
|
|
1018
|
+
- **`Sound.playPooled()` removed.** Use `play()` (which is now the
|
|
1019
|
+
pooled multi-instance path).
|
|
1020
|
+
- **`Sound.poolSize` default raised from 1 to 8.** Closer to typical
|
|
1021
|
+
SFX needs without manual configuration.
|
|
1022
|
+
- **`Sound._sourceNode` (the previous primary singleton source) is
|
|
1023
|
+
removed.** With pooled play unified, all sources go through
|
|
1024
|
+
`_pooledSources`. As a consequence, `Sound.getTime()` and
|
|
1025
|
+
`Sound.setTime()` no longer track per-source playback position
|
|
1026
|
+
— they're effectively no-ops on Sound now. For precise timing
|
|
1027
|
+
use `Music` (HTMLMediaElement-backed singleton).
|
|
1028
|
+
- **`AbstractMedia.bus` property added.** Subclasses (Sound, Music)
|
|
1029
|
+
override `_defaultBus()`, `_connectToBus()`, `_disconnectFromBus()`
|
|
1030
|
+
to integrate with the mixer.
|
|
1031
|
+
|
|
1032
|
+
### Migration
|
|
1033
|
+
|
|
1034
|
+
```ts
|
|
1035
|
+
// Before:
|
|
1036
|
+
sound.play(); // singleton — second call replaces first
|
|
1037
|
+
sound.playPooled(); // multi-instance — concurrent plays
|
|
1038
|
+
|
|
1039
|
+
// After:
|
|
1040
|
+
sound.play(); // multi-instance — concurrent plays (default!)
|
|
1041
|
+
sound.play({ replace: true }); // singleton — equivalent of old play()
|
|
1042
|
+
```
|
|
1043
|
+
|
|
1044
|
+
```ts
|
|
1045
|
+
// Before — direct destination routing was implicit:
|
|
1046
|
+
const sound = new Sound(buffer);
|
|
1047
|
+
sound.play(); // → audioContext.destination
|
|
1048
|
+
|
|
1049
|
+
// After — routes through the soundBus by default:
|
|
1050
|
+
const sound = new Sound(buffer);
|
|
1051
|
+
sound.play(); // → app.audio.sound → app.audio.master → destination
|
|
1052
|
+
|
|
1053
|
+
// Override to a custom bus:
|
|
1054
|
+
const dialogueBus = new AudioBus('dialogue', { parent: app.audio.master });
|
|
1055
|
+
app.audio.registerBus(dialogueBus);
|
|
1056
|
+
sound.bus = dialogueBus;
|
|
1057
|
+
```
|
|
1058
|
+
|
|
1059
|
+
```ts
|
|
1060
|
+
// Spatial audio:
|
|
1061
|
+
const explosion = new Sound(buffer);
|
|
1062
|
+
explosion.position = { x: 200, y: 100 }; // becomes spatial
|
|
1063
|
+
app.audio.listener.target = playerSprite; // ears follow player
|
|
1064
|
+
|
|
1065
|
+
explosion.play();
|
|
1066
|
+
// → routes through equalpower panner with distance falloff
|
|
1067
|
+
```
|
|
1068
|
+
|
|
1069
|
+
### Notes
|
|
1070
|
+
|
|
1071
|
+
- `DuckingFilter` uses its own internal `setInterval(60Hz)` for
|
|
1072
|
+
per-frame envelope-following rather than hooking into
|
|
1073
|
+
`AudioManager.update()`. This keeps audio-side filters
|
|
1074
|
+
self-contained and avoids cross-cutting changes to the mixer
|
|
1075
|
+
contract. May be revisited.
|
|
1076
|
+
- `LowestPriority` pool strategy degenerates to FIFO within a
|
|
1077
|
+
single Sound instance because all pooled sources share the same
|
|
1078
|
+
`priority` value. The strategy becomes meaningful when the
|
|
1079
|
+
engine later adds cross-Sound voice management.
|
|
1080
|
+
- Spatial sounds share a single `PannerNode` per Sound instance —
|
|
1081
|
+
all simultaneous pooled plays of one sound emit from the same
|
|
1082
|
+
world-space point. Per-instance positions would require an
|
|
1083
|
+
API extension and are deferred.
|
|
1084
|
+
- BeatDetector / `AudioAnalyser.onBeat` hooks are deferred to
|
|
1085
|
+
0.7.1 — this release focuses on the mixer / filter / spatial
|
|
1086
|
+
foundation.
|
|
1087
|
+
|
|
1088
|
+
## [0.6.20] - 2026-05-02
|
|
1089
|
+
|
|
1090
|
+
Adds `view.follow(SceneNode)`, audio fade helpers, and focus / visibility
|
|
1091
|
+
infrastructure. Pure additive — no behavior changes for existing code.
|
|
1092
|
+
|
|
1093
|
+
### Added
|
|
1094
|
+
|
|
1095
|
+
- **`view.follow()` accepts `SceneNode`** in addition to `{x, y}`
|
|
1096
|
+
targets. When the target is a SceneNode, the follow tracks its
|
|
1097
|
+
**world-space position** via `getGlobalTransform()`, so following a
|
|
1098
|
+
Sprite nested under a translated/rotated Container works correctly.
|
|
1099
|
+
New exported type `ViewFollowTarget = SceneNode | { x: number; y:
|
|
1100
|
+
number } | null`.
|
|
1101
|
+
- **Audio fade helpers on `AbstractMedia`** — both `Sound` and `Music`
|
|
1102
|
+
inherit:
|
|
1103
|
+
- `fadeIn(durationMs): this` — ramps gain from 0 to current volume.
|
|
1104
|
+
Auto-plays if paused. Cancels any in-flight fade.
|
|
1105
|
+
- `fadeOut(durationMs, options?: { stopAfter?: boolean }): this` —
|
|
1106
|
+
ramps gain to 0. By default calls `pause()` after the fade
|
|
1107
|
+
completes; pass `{ stopAfter: false }` to keep playing at zero
|
|
1108
|
+
volume.
|
|
1109
|
+
- Both return `this` for chaining and use Web Audio's
|
|
1110
|
+
`linearRampToValueAtTime` for sample-accurate fades.
|
|
1111
|
+
- **`Application.canvasFocused: boolean`** — passthrough getter for the
|
|
1112
|
+
InputManager's existing canvas focus state.
|
|
1113
|
+
- **`Application.documentVisible: boolean`** — tracks
|
|
1114
|
+
`document.visibilityState`, updated on `visibilitychange`.
|
|
1115
|
+
- **`Application.onCanvasFocusChange: Signal<[focused: boolean]>`** —
|
|
1116
|
+
fires when the canvas gains or loses focus (canvas blur,
|
|
1117
|
+
click-outside, alt-tab from canvas-focused state).
|
|
1118
|
+
- **`Application.onVisibilityChange: Signal<[visible: boolean]>`** —
|
|
1119
|
+
fires when the page tab becomes hidden or visible (minimize, switch
|
|
1120
|
+
tab, etc.).
|
|
1121
|
+
- **`Application.pauseOnHidden: boolean`** (default `false`) — when
|
|
1122
|
+
`true`, `app.update()` skips the entire frame body while
|
|
1123
|
+
`documentVisible` is `false`. `requestAnimationFrame` keeps
|
|
1124
|
+
ticking (already throttled by the browser when hidden) so the loop
|
|
1125
|
+
resumes seamlessly when the page becomes visible again.
|
|
1126
|
+
- **`InputManager.onCanvasFocusChange`** — same signal also exposed
|
|
1127
|
+
here for users who only need input-side focus tracking without
|
|
1128
|
+
reaching for the Application.
|
|
1129
|
+
|
|
1130
|
+
### Notes
|
|
1131
|
+
|
|
1132
|
+
- Window-level `blur` / `focus` events are intentionally not exposed as
|
|
1133
|
+
separate signals — `document.visibilitychange` is the better-defined
|
|
1134
|
+
API and covers the common cases.
|
|
1135
|
+
- `crossFade()` as a top-level helper was deferred — compose
|
|
1136
|
+
`a.fadeOut(ms)` + `b.fadeIn(ms)` manually until the AudioManager lands.
|
|
1137
|
+
- `view.follow()` continues to use lerp-based smoothing for continuous
|
|
1138
|
+
tracking. Scripted one-shot camera moves (zoom-to-room,
|
|
1139
|
+
pan-to-cutscene) should use the existing Tween system on
|
|
1140
|
+
`view.center` for full easing-curve support.
|
|
1141
|
+
|
|
1142
|
+
## [0.6.19] - 2026-05-02
|
|
1143
|
+
|
|
1144
|
+
Caches global transforms, world-space bounds, sprite vertices, and
|
|
1145
|
+
sprite normals via dirty flags. Closes four hot-path recomputation
|
|
1146
|
+
gaps that the audit identified — `getGlobalTransform()` and
|
|
1147
|
+
`getBounds()` were O(depth) per call, called many times per frame
|
|
1148
|
+
from sprite rendering, hit-testing, frustum culling, and collision
|
|
1149
|
+
detection. Pure performance change — no public API surface changes.
|
|
1150
|
+
|
|
1151
|
+
### Performance
|
|
1152
|
+
|
|
1153
|
+
- **`SceneNode.getGlobalTransform()`** is now cache-hit-O(1) instead
|
|
1154
|
+
of O(depth). The cached `_globalTransform` is invalidated on
|
|
1155
|
+
position / rotation / scale / origin change, on parent change
|
|
1156
|
+
(add/remove from a Container), and propagated to all descendants
|
|
1157
|
+
on parent transform changes.
|
|
1158
|
+
- **`SceneNode.getBounds()`** is now cache-hit-O(1). Invalidated
|
|
1159
|
+
alongside global transform, plus on local-bounds mutations
|
|
1160
|
+
(`Sprite.setTextureFrame`, `Mesh.recomputeLocalBounds`,
|
|
1161
|
+
`ParticleSystem.setTextureFrame`). Local-bounds changes also
|
|
1162
|
+
cascade up to ancestor Containers' bounds.
|
|
1163
|
+
- **`Sprite.vertices`** getter caches the eight world-space vertex
|
|
1164
|
+
components. Recomputes only when the sprite's transform or local
|
|
1165
|
+
bounds change. Previously had a `// todo cache this` comment.
|
|
1166
|
+
- **`Sprite.getNormals()`** returns a stable `[Vector, Vector,
|
|
1167
|
+
Vector, Vector]` array. The four `Vector` instances are reused
|
|
1168
|
+
across calls; previously each call allocated four new `Vector`s.
|
|
1169
|
+
Recomputes only when vertices change. Reduces GC pressure in
|
|
1170
|
+
collision-detection hot paths.
|
|
1171
|
+
|
|
1172
|
+
### Notes
|
|
1173
|
+
|
|
1174
|
+
- `Sprite.getNormals()` now returns the **same array reference** on
|
|
1175
|
+
every call. Callers that previously stored the result and expected
|
|
1176
|
+
it to remain stable across mutations must re-read after any
|
|
1177
|
+
transform change. This is a behavior refinement; no caller in the
|
|
1178
|
+
codebase relied on the prior allocation pattern.
|
|
1179
|
+
- Invalidation propagation walks the scene subtree on position /
|
|
1180
|
+
rotation / scale / origin changes. For very large UI trees
|
|
1181
|
+
(thousands of nested children), this is O(descendants) per setter
|
|
1182
|
+
call. Setters are typically called on a small number of nodes per
|
|
1183
|
+
frame, so the cumulative cost is dominated by the savings on
|
|
1184
|
+
the read path. Generation-counter invalidation is a possible
|
|
1185
|
+
future optimization if profiling shows the walk dominates.
|
|
1186
|
+
- New flag bits: `SceneNodeTransformFlags.GlobalTransform` (1<<8),
|
|
1187
|
+
`SceneNodeTransformFlags.BoundsRect` (1<<9),
|
|
1188
|
+
`SpriteFlags.Vertices` (0x400), `SpriteFlags.Normals` (0x800).
|
|
1189
|
+
Non-overlapping with existing flags so they share the same
|
|
1190
|
+
`Flags<T>` instance.
|
|
1191
|
+
|
|
1192
|
+
## [0.6.18] - 2026-05-02
|
|
1193
|
+
|
|
1194
|
+
Fixes a long-standing audio volume-ramp bug.
|
|
1195
|
+
|
|
1196
|
+
### Fixed
|
|
1197
|
+
|
|
1198
|
+
- **Audio volume / mute changes are now near-instant**. The third
|
|
1199
|
+
argument to `GainNode.setTargetAtTime` is a time constant in
|
|
1200
|
+
**seconds** — `Sound`, `Music`, and the `Video` audio path were
|
|
1201
|
+
passing `10`, which made every volume update take ~30 seconds to
|
|
1202
|
+
reach 95% of its target value. Calling `sound.setVolume(0.5)` would
|
|
1203
|
+
fade over half a minute instead of taking effect immediately.
|
|
1204
|
+
Replaced with `0.01` (10 ms) — fast enough to feel instant, slow
|
|
1205
|
+
enough to avoid the audible click of a snapped value. Standard
|
|
1206
|
+
practice in `pixi-sound`, Howler, and other Web Audio libraries.
|
|
1207
|
+
Affects: `Sound.setVolume`, `Sound.setMuted`, `Sound` audio-context
|
|
1208
|
+
setup, and the equivalent paths on `Music` and `Video`. Bug was
|
|
1209
|
+
present since the initial commit; not caught by tests because the
|
|
1210
|
+
jsdom mock stubs `setTargetAtTime` as a no-op.
|
|
1211
|
+
|
|
1212
|
+
## [0.6.17] - 2026-05-02
|
|
1213
|
+
|
|
1214
|
+
Rewrites the debug overlay as a canvas-native, tree-shake-able module.
|
|
1215
|
+
Replaces the DOM-based 0.6.15 implementation. Also adds a generic
|
|
1216
|
+
per-frame application hook.
|
|
1217
|
+
|
|
1218
|
+
### Added
|
|
1219
|
+
|
|
1220
|
+
- **`Application.onFrame: Signal<[Time]>`** — generic per-frame hook
|
|
1221
|
+
fired between `sceneManager.update()` and `backend.flush()`. Useful
|
|
1222
|
+
for any external tool that wants per-frame ticks without writing a
|
|
1223
|
+
Scene (debug overlays, profilers, custom HUDs).
|
|
1224
|
+
- **`@codexo/exojs/debug` subpath export** — DebugOverlay and friends
|
|
1225
|
+
now live behind a separate import path. Apps that don't import it
|
|
1226
|
+
pay zero bundle cost. The root `@codexo/exojs` no longer references
|
|
1227
|
+
any debug code.
|
|
1228
|
+
- **Canvas-native `DebugOverlay`** — instantiate manually:
|
|
1229
|
+
```ts
|
|
1230
|
+
import { DebugOverlay } from '@codexo/exojs/debug';
|
|
1231
|
+
const debug = new DebugOverlay(app);
|
|
1232
|
+
debug.layers.performance.visible = true; // or press F1
|
|
1233
|
+
```
|
|
1234
|
+
Subscribes to `app.onFrame` for ticking, `inputManager.onKeyDown`
|
|
1235
|
+
for F1 binding, and `app.onResize` for screen-space view sync.
|
|
1236
|
+
Renders into its own screen-space view between scene render and
|
|
1237
|
+
backend flush.
|
|
1238
|
+
- **`PerformanceLayer`** (V1's only layer) — FPS, frame-time
|
|
1239
|
+
sparkline, draw calls, node count, culled nodes. Top-left fixed
|
|
1240
|
+
position. Toggle via `F1` or `debug.layers.performance.visible`.
|
|
1241
|
+
- **`DebugLayer` abstract base** — exported so future layer types
|
|
1242
|
+
(BoundingBoxes, HitTest, PointerStack) plug in cleanly. V1 ships
|
|
1243
|
+
only PerformanceLayer; more arrive in subsequent patches.
|
|
1244
|
+
|
|
1245
|
+
### Changed
|
|
1246
|
+
|
|
1247
|
+
- **`Application.debug` removed** — was added in 0.6.15. Apps that
|
|
1248
|
+
used `app.debug.show()` must migrate to `import { DebugOverlay }
|
|
1249
|
+
from '@codexo/exojs/debug'` and instantiate manually. **Breaking
|
|
1250
|
+
change**, but the affected window is one day (0.6.15 → 0.6.17).
|
|
1251
|
+
|
|
1252
|
+
### Notes
|
|
1253
|
+
|
|
1254
|
+
- The new architecture decouples DebugOverlay from Application so
|
|
1255
|
+
the root bundle tree-shakes the debug code away when unused. This
|
|
1256
|
+
is the same pattern projects use for optional dev-tools modules.
|
|
1257
|
+
- F1 binding is hardcoded for V1. Opt-out (`{ keybindings: false }`
|
|
1258
|
+
constructor option) and additional keybindings come with the
|
|
1259
|
+
next layers.
|
|
1260
|
+
- F-keys only fire while the canvas has focus — engine convention,
|
|
1261
|
+
not a debug-specific quirk.
|
|
1262
|
+
|
|
1263
|
+
## [0.6.16] - 2026-05-02
|
|
1264
|
+
|
|
1265
|
+
Adds an opt-in spatial index for hit-testing and replaces the dead
|
|
1266
|
+
`core/Quadtree` class with a generic `math/Quadtree<T>`.
|
|
1267
|
+
|
|
1268
|
+
### Added
|
|
1269
|
+
|
|
1270
|
+
- **`Quadtree<T>`** in `@/math/Quadtree` — generic spatial index with
|
|
1271
|
+
`insert(item)`, `queryPoint(x, y, results?)`, `queryRect(rect, results?)`,
|
|
1272
|
+
`clear()`, and `destroy()`. Items carry their `bounds: Rectangle` and
|
|
1273
|
+
arbitrary `payload: T` separately, so a single tree can index any
|
|
1274
|
+
spatial domain. The `results` array is reused across queries for
|
|
1275
|
+
zero-allocation hot paths.
|
|
1276
|
+
- **`InteractionManager.useSpatialIndex: boolean`** (default `false`) —
|
|
1277
|
+
opt-in flag. When enabled, the manager rebuilds a quadtree of all
|
|
1278
|
+
visible interactive nodes once per `update()` tick and uses it for
|
|
1279
|
+
hit-testing instead of the recursive scene-tree walk. Z-order is
|
|
1280
|
+
preserved via insertion-order tags. Captured pointers (active drags)
|
|
1281
|
+
bypass the index — same as the recursive fallback.
|
|
1282
|
+
|
|
1283
|
+
### Changed
|
|
1284
|
+
|
|
1285
|
+
- **`core/Quadtree`** removed — was dead code, exposed publicly via the
|
|
1286
|
+
`core` barrel but never imported anywhere internally. The new
|
|
1287
|
+
`math/Quadtree<T>` covers the same conceptual ground with a cleaner
|
|
1288
|
+
API and broader applicability. **This is a breaking change for any
|
|
1289
|
+
external code that imported `Quadtree` from `@codexo/exojs`** and
|
|
1290
|
+
relied on the SceneNode-specialized `addSceneNode` /
|
|
1291
|
+
`getRelatedChildren` methods. Replacement: use `Quadtree<RenderNode>`
|
|
1292
|
+
from `@/math/Quadtree` with `insert({ bounds, payload })` and
|
|
1293
|
+
`queryPoint` / `queryRect`.
|
|
1294
|
+
|
|
1295
|
+
### Notes
|
|
1296
|
+
|
|
1297
|
+
- Default behavior is unchanged: `useSpatialIndex` is off, so the
|
|
1298
|
+
recursive walk remains the hit-test path. Turn it on for scenes
|
|
1299
|
+
with many interactive nodes — the per-frame rebuild + log-time
|
|
1300
|
+
query pays off when the linear walk becomes a bottleneck.
|
|
1301
|
+
- Per-frame rebuild is intentional in v1. Smarter invalidation
|
|
1302
|
+
(rebuild only when the scene tree mutates) is a follow-up.
|
|
1303
|
+
- The new tree does not redistribute items already-stored in a parent
|
|
1304
|
+
when subdivision happens — fine for the rebuild-each-frame model
|
|
1305
|
+
since items don't accumulate across frames. If item-stable trees
|
|
1306
|
+
become a use case later, redistribution is ~20 LOC to add.
|
|
1307
|
+
|
|
1308
|
+
## [0.6.15] - 2026-05-02
|
|
1309
|
+
|
|
1310
|
+
Adds a built-in debug HUD for runtime stats. Opt-in HTML overlay that
|
|
1311
|
+
shows FPS, frame time, draw calls, node count, active pointers, and
|
|
1312
|
+
the currently hovered interactive node — handy during development,
|
|
1313
|
+
zero cost when not shown.
|
|
1314
|
+
|
|
1315
|
+
### Added
|
|
1316
|
+
|
|
1317
|
+
- **`Application.debug`** — auto-instantiated `DebugOverlay` instance.
|
|
1318
|
+
DOM is created lazily on first `show()`, so the panel costs nothing
|
|
1319
|
+
until opt-in. Position-fixed over the canvas, recomputed each frame
|
|
1320
|
+
from `canvas.getBoundingClientRect()` so it tracks if the canvas
|
|
1321
|
+
moves.
|
|
1322
|
+
- **`DebugOverlay.show() / hide() / toggle()`** — visibility control.
|
|
1323
|
+
`show()` returns `this` for chaining. Bind to a key in your code if
|
|
1324
|
+
you want a hotkey toggle.
|
|
1325
|
+
- **Stats displayed**: FPS (60-sample rolling average), frame time
|
|
1326
|
+
(ms), draw calls, culled nodes, total scene-tree node count, active
|
|
1327
|
+
pointers, hovered node class + cursor coords.
|
|
1328
|
+
- **`InteractionManager.getHoveredNode(pointerId?)`** — returns the
|
|
1329
|
+
RenderNode currently hovered by the given pointer (or the first one
|
|
1330
|
+
in iteration order when omitted). Used by the debug panel; also
|
|
1331
|
+
useful for custom HUDs.
|
|
1332
|
+
|
|
1333
|
+
### Notes
|
|
1334
|
+
|
|
1335
|
+
- The overlay is a styled `<div>` appended to `document.body`. It uses
|
|
1336
|
+
`pointer-events: none` so clicks pass through to the canvas.
|
|
1337
|
+
- No keyboard shortcut is wired up — bind `app.debug.toggle()` to
|
|
1338
|
+
whatever key you want.
|
|
1339
|
+
- Hit-test box visualization is not in this release — coming when
|
|
1340
|
+
the spatial-index work lands.
|
|
1341
|
+
|
|
1342
|
+
## [0.6.14] - 2026-05-02
|
|
1343
|
+
|
|
1344
|
+
Reshapes the interaction system around a per-frame tick and adds an
|
|
1345
|
+
opt-in drag-and-drop helper. The public per-node signal API from 0.6.13
|
|
1346
|
+
is unchanged; only event *cadence* and a new `draggable` flag.
|
|
1347
|
+
|
|
1348
|
+
### Added
|
|
1349
|
+
|
|
1350
|
+
- **`RenderNode.draggable: boolean`** (default `false`) — when set on
|
|
1351
|
+
an interactive node, a `pointerdown` over the node starts a drag:
|
|
1352
|
+
the framework auto-positions the node by tracking pointer movement
|
|
1353
|
+
while preserving the grab offset, and routes all subsequent pointer
|
|
1354
|
+
events for that pointer ID to the dragged node regardless of where
|
|
1355
|
+
the pointer is. Drag bypasses hit-testing until release.
|
|
1356
|
+
- **Three drag signals on `RenderNode`**: `onDragStart`, `onDrag`,
|
|
1357
|
+
`onDragEnd` — all `Signal<[InteractionEvent]>`. Drag events use new
|
|
1358
|
+
event types `'dragstart' | 'drag' | 'dragend'` and dispatch directly
|
|
1359
|
+
on the node (no bubble — parent containers don't receive child drag
|
|
1360
|
+
events).
|
|
1361
|
+
- **`InteractionManager.update()`** — public per-frame tick called
|
|
1362
|
+
automatically from `Application.update()` between `inputManager.update()`
|
|
1363
|
+
and `tweens.update()`. Drains a per-pointer queue filled by signal
|
|
1364
|
+
handlers; no-op when nothing happened that frame.
|
|
1365
|
+
|
|
1366
|
+
### Changed
|
|
1367
|
+
|
|
1368
|
+
- **InteractionManager moved from event-driven to tick-driven.**
|
|
1369
|
+
Signal handlers now only enqueue flags into a per-pointer bitfield
|
|
1370
|
+
and set a dirty flag; the actual hit-test + dispatch happens once
|
|
1371
|
+
per frame in `update()`. Same observable behavior, but decoupled
|
|
1372
|
+
from `InputManager` signal cadence — paves the way for spatial-index
|
|
1373
|
+
integration.
|
|
1374
|
+
|
|
1375
|
+
### Notes
|
|
1376
|
+
|
|
1377
|
+
- **Drag uses native `setPointerCapture`** so movement keeps tracking
|
|
1378
|
+
even when the pointer leaves canvas bounds. `pointercancel` /
|
|
1379
|
+
`pointerleave` during a drag fires `onDragEnd` (no separate
|
|
1380
|
+
cancellation flag in v1; check the event type if needed).
|
|
1381
|
+
- **Drag offset is in canvas-space.** Nodes whose parent containers
|
|
1382
|
+
have non-identity transforms may feel off — v1 assumes top-level
|
|
1383
|
+
draggable elements (UI panels, inventory items). True
|
|
1384
|
+
parent-aware drag is a follow-up.
|
|
1385
|
+
- **`pointerover` / `pointerout` are suppressed during a drag** —
|
|
1386
|
+
the dragged node stays "hovered" by definition.
|
|
1387
|
+
|
|
1388
|
+
## [0.6.13] - 2026-05-02
|
|
1389
|
+
|
|
1390
|
+
Adds object-level pointer events. Scene-graph nodes are now first-class
|
|
1391
|
+
event targets — opt in with `node.interactive = true` and listen on
|
|
1392
|
+
per-node signals. Pure addition; existing global pointer signals on
|
|
1393
|
+
`InputManager` are unchanged.
|
|
1394
|
+
|
|
1395
|
+
### Added
|
|
1396
|
+
|
|
1397
|
+
- **`RenderNode.interactive: boolean`** (default `false`) — opt-in flag
|
|
1398
|
+
enabling hit-testing for the node. Hit-test reuses the existing
|
|
1399
|
+
`RenderNode.contains(x, y)` (AABB in world space).
|
|
1400
|
+
- **`RenderNode.cursor: string | null`** (default `null`) — CSS cursor
|
|
1401
|
+
string applied to `canvas.style.cursor` while the pointer is over the
|
|
1402
|
+
node. Walks up the ancestor chain; first non-null wins.
|
|
1403
|
+
- **Six per-node signals**: `onPointerDown`, `onPointerUp`,
|
|
1404
|
+
`onPointerMove`, `onPointerOver`, `onPointerOut`, `onPointerTap` —
|
|
1405
|
+
all `Signal<[InteractionEvent]>`.
|
|
1406
|
+
- **`InteractionEvent`** — `type`, `target` (the originally-hit node,
|
|
1407
|
+
stable across bubble), `currentTarget` (changes per bubble step),
|
|
1408
|
+
`pointer`, `worldX`, `worldY`, `stopPropagation()`,
|
|
1409
|
+
`propagationStopped`.
|
|
1410
|
+
- **`InteractionManager`** — wired automatically as
|
|
1411
|
+
`Application.interaction`. Subscribes to existing `InputManager`
|
|
1412
|
+
signals (no extra DOM listeners), hit-tests the active scene's root
|
|
1413
|
+
in reverse z-order, dispatches with bubble propagation, and updates
|
|
1414
|
+
the canvas cursor.
|
|
1415
|
+
|
|
1416
|
+
### Notes
|
|
1417
|
+
|
|
1418
|
+
- **Bubble-only, no capture phase.** Bubble walks `parentNode` and
|
|
1419
|
+
stops at the first non-interactive ancestor — parents must opt in
|
|
1420
|
+
to receive bubbled events. `event.stopPropagation()` halts the walk.
|
|
1421
|
+
- **Touch has no hover phase.** `pointerover` / `pointerout` for touch
|
|
1422
|
+
fire only at down/up boundaries (a finger doesn't exist on the
|
|
1423
|
+
surface between presses). Don't rely on hover effects for touch UX.
|
|
1424
|
+
- **AABB hit-test only in v1.** Precise (polygon / alpha) hit-testing
|
|
1425
|
+
is deferred. Override `contains(x, y)` for custom shapes.
|
|
1426
|
+
- **Cursor is CSS-only.** For animated or texture-based custom cursors,
|
|
1427
|
+
set `canvas.style.cursor = 'none'` and render a sprite that follows
|
|
1428
|
+
pointer position. CSS gives OS-level latency and survives game-loop
|
|
1429
|
+
stutter; engine-rendered cursors don't.
|
|
1430
|
+
|
|
7
1431
|
## [0.6.12] - 2026-05-02
|
|
8
1432
|
|
|
9
1433
|
Adds swept (continuous) collision detection. Pure-math addition —
|