@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.
Files changed (512) hide show
  1. package/CHANGELOG.md +1424 -0
  2. package/dist/esm/animation/Tween.d.ts +60 -1
  3. package/dist/esm/animation/Tween.js +60 -1
  4. package/dist/esm/animation/Tween.js.map +1 -1
  5. package/dist/esm/animation/TweenManager.d.ts +10 -0
  6. package/dist/esm/animation/TweenManager.js +10 -0
  7. package/dist/esm/animation/TweenManager.js.map +1 -1
  8. package/dist/esm/animation/types.d.ts +15 -0
  9. package/dist/esm/animation/types.js +6 -0
  10. package/dist/esm/animation/types.js.map +1 -1
  11. package/dist/esm/audio/AbstractMedia.d.ts +29 -0
  12. package/dist/esm/audio/AbstractMedia.js +76 -0
  13. package/dist/esm/audio/AbstractMedia.js.map +1 -1
  14. package/dist/esm/audio/AudioAnalyser.d.ts +83 -23
  15. package/dist/esm/audio/AudioAnalyser.js +277 -57
  16. package/dist/esm/audio/AudioAnalyser.js.map +1 -1
  17. package/dist/esm/audio/AudioBus.d.ts +82 -0
  18. package/dist/esm/audio/AudioBus.js +255 -0
  19. package/dist/esm/audio/AudioBus.js.map +1 -0
  20. package/dist/esm/audio/AudioFilter.d.ts +16 -0
  21. package/dist/esm/audio/AudioFilter.js +13 -0
  22. package/dist/esm/audio/AudioFilter.js.map +1 -0
  23. package/dist/esm/audio/AudioListener.d.ts +39 -0
  24. package/dist/esm/audio/AudioListener.js +97 -0
  25. package/dist/esm/audio/AudioListener.js.map +1 -0
  26. package/dist/esm/audio/AudioManager.d.ts +68 -0
  27. package/dist/esm/audio/AudioManager.js +139 -0
  28. package/dist/esm/audio/AudioManager.js.map +1 -0
  29. package/dist/esm/audio/BeatDetector.d.ts +142 -0
  30. package/dist/esm/audio/BeatDetector.js +957 -0
  31. package/dist/esm/audio/BeatDetector.js.map +1 -0
  32. package/dist/esm/audio/Envelope.d.ts +44 -0
  33. package/dist/esm/audio/Envelope.js +60 -0
  34. package/dist/esm/audio/Envelope.js.map +1 -0
  35. package/dist/esm/audio/Media.d.ts +11 -0
  36. package/dist/esm/audio/Music.d.ts +20 -0
  37. package/dist/esm/audio/Music.js +45 -4
  38. package/dist/esm/audio/Music.js.map +1 -1
  39. package/dist/esm/audio/OscillatorSound.d.ts +98 -0
  40. package/dist/esm/audio/OscillatorSound.js +342 -0
  41. package/dist/esm/audio/OscillatorSound.js.map +1 -0
  42. package/dist/esm/audio/Sound.d.ts +119 -9
  43. package/dist/esm/audio/Sound.js +301 -117
  44. package/dist/esm/audio/Sound.js.map +1 -1
  45. package/dist/esm/audio/audio-context.d.ts +48 -0
  46. package/dist/esm/audio/audio-context.js +48 -5
  47. package/dist/esm/audio/audio-context.js.map +1 -1
  48. package/dist/esm/audio/crossFade.d.ts +19 -0
  49. package/dist/esm/audio/crossFade.js +26 -0
  50. package/dist/esm/audio/crossFade.js.map +1 -0
  51. package/dist/esm/audio/dsp/fft.d.ts +22 -0
  52. package/dist/esm/audio/dsp/mel.d.ts +43 -0
  53. package/dist/esm/audio/dsp/tempogram.d.ts +51 -0
  54. package/dist/esm/audio/filters/ChorusFilter.d.ts +52 -0
  55. package/dist/esm/audio/filters/ChorusFilter.js +143 -0
  56. package/dist/esm/audio/filters/ChorusFilter.js.map +1 -0
  57. package/dist/esm/audio/filters/CompressorFilter.d.ts +43 -0
  58. package/dist/esm/audio/filters/CompressorFilter.js +108 -0
  59. package/dist/esm/audio/filters/CompressorFilter.js.map +1 -0
  60. package/dist/esm/audio/filters/DelayFilter.d.ts +34 -0
  61. package/dist/esm/audio/filters/DelayFilter.js +110 -0
  62. package/dist/esm/audio/filters/DelayFilter.js.map +1 -0
  63. package/dist/esm/audio/filters/DuckingFilter.d.ts +44 -0
  64. package/dist/esm/audio/filters/DuckingFilter.js +164 -0
  65. package/dist/esm/audio/filters/DuckingFilter.js.map +1 -0
  66. package/dist/esm/audio/filters/EqualizerFilter.d.ts +49 -0
  67. package/dist/esm/audio/filters/EqualizerFilter.js +134 -0
  68. package/dist/esm/audio/filters/EqualizerFilter.js.map +1 -0
  69. package/dist/esm/audio/filters/GranularFilter.d.ts +62 -0
  70. package/dist/esm/audio/filters/GranularFilter.js +176 -0
  71. package/dist/esm/audio/filters/GranularFilter.js.map +1 -0
  72. package/dist/esm/audio/filters/HighpassFilter.d.ts +29 -0
  73. package/dist/esm/audio/filters/HighpassFilter.js +71 -0
  74. package/dist/esm/audio/filters/HighpassFilter.js.map +1 -0
  75. package/dist/esm/audio/filters/LowpassFilter.d.ts +29 -0
  76. package/dist/esm/audio/filters/LowpassFilter.js +71 -0
  77. package/dist/esm/audio/filters/LowpassFilter.js.map +1 -0
  78. package/dist/esm/audio/filters/PitchShiftFilter.d.ts +44 -0
  79. package/dist/esm/audio/filters/PitchShiftFilter.js +132 -0
  80. package/dist/esm/audio/filters/PitchShiftFilter.js.map +1 -0
  81. package/dist/esm/audio/filters/ReverbFilter.d.ts +36 -0
  82. package/dist/esm/audio/filters/ReverbFilter.js +118 -0
  83. package/dist/esm/audio/filters/ReverbFilter.js.map +1 -0
  84. package/dist/esm/audio/filters/VocoderFilter.d.ts +49 -0
  85. package/dist/esm/audio/filters/VocoderFilter.js +174 -0
  86. package/dist/esm/audio/filters/VocoderFilter.js.map +1 -0
  87. package/dist/esm/audio/filters/WorkletFilter.d.ts +51 -0
  88. package/dist/esm/audio/filters/WorkletFilter.js +106 -0
  89. package/dist/esm/audio/filters/WorkletFilter.js.map +1 -0
  90. package/dist/esm/audio/filters/index.d.ts +12 -0
  91. package/dist/esm/audio/index.d.ts +15 -1
  92. package/dist/esm/audio/worklet/registerWorklet.d.ts +10 -0
  93. package/dist/esm/audio/worklet/registerWorklet.js +44 -0
  94. package/dist/esm/audio/worklet/registerWorklet.js.map +1 -0
  95. package/dist/esm/core/Application.d.ts +82 -0
  96. package/dist/esm/core/Application.js +137 -2
  97. package/dist/esm/core/Application.js.map +1 -1
  98. package/dist/esm/core/Bounds.d.ts +23 -0
  99. package/dist/esm/core/Bounds.js +23 -0
  100. package/dist/esm/core/Bounds.js.map +1 -1
  101. package/dist/esm/core/Clock.d.ts +21 -0
  102. package/dist/esm/core/Clock.js +21 -0
  103. package/dist/esm/core/Clock.js.map +1 -1
  104. package/dist/esm/core/Color.d.ts +35 -0
  105. package/dist/esm/core/Color.js +35 -0
  106. package/dist/esm/core/Color.js.map +1 -1
  107. package/dist/esm/core/Scene.d.ts +46 -0
  108. package/dist/esm/core/Scene.js +27 -0
  109. package/dist/esm/core/Scene.js.map +1 -1
  110. package/dist/esm/core/SceneManager.d.ts +62 -0
  111. package/dist/esm/core/SceneManager.js +49 -0
  112. package/dist/esm/core/SceneManager.js.map +1 -1
  113. package/dist/esm/core/SceneNode.d.ts +40 -1
  114. package/dist/esm/core/SceneNode.js +75 -6
  115. package/dist/esm/core/SceneNode.js.map +1 -1
  116. package/dist/esm/core/Signal.d.ts +44 -0
  117. package/dist/esm/core/Signal.js +39 -0
  118. package/dist/esm/core/Signal.js.map +1 -1
  119. package/dist/esm/core/Time.d.ts +21 -0
  120. package/dist/esm/core/Time.js +22 -1
  121. package/dist/esm/core/Time.js.map +1 -1
  122. package/dist/esm/core/Timer.d.ts +7 -0
  123. package/dist/esm/core/Timer.js +7 -0
  124. package/dist/esm/core/Timer.js.map +1 -1
  125. package/dist/esm/core/capabilities.d.ts +12 -0
  126. package/dist/esm/core/capabilities.js +12 -0
  127. package/dist/esm/core/capabilities.js.map +1 -1
  128. package/dist/esm/core/index.d.ts +0 -1
  129. package/dist/esm/core/types.d.ts +37 -0
  130. package/dist/esm/core/utils.d.ts +48 -0
  131. package/dist/esm/core/utils.js +48 -0
  132. package/dist/esm/core/utils.js.map +1 -1
  133. package/dist/esm/debug/BoundingBoxesLayer.d.ts +26 -0
  134. package/dist/esm/debug/BoundingBoxesLayer.js +136 -0
  135. package/dist/esm/debug/BoundingBoxesLayer.js.map +1 -0
  136. package/dist/esm/debug/DebugLayer.d.ts +42 -0
  137. package/dist/esm/debug/DebugLayer.js +34 -0
  138. package/dist/esm/debug/DebugLayer.js.map +1 -0
  139. package/dist/esm/debug/DebugOverlay.d.ts +59 -0
  140. package/dist/esm/debug/DebugOverlay.js +123 -0
  141. package/dist/esm/debug/DebugOverlay.js.map +1 -0
  142. package/dist/esm/debug/HitTestLayer.d.ts +32 -0
  143. package/dist/esm/debug/HitTestLayer.js +118 -0
  144. package/dist/esm/debug/HitTestLayer.js.map +1 -0
  145. package/dist/esm/debug/PerformanceLayer.d.ts +37 -0
  146. package/dist/esm/debug/PerformanceLayer.js +193 -0
  147. package/dist/esm/debug/PerformanceLayer.js.map +1 -0
  148. package/dist/esm/debug/PointerStackLayer.d.ts +31 -0
  149. package/dist/esm/debug/PointerStackLayer.js +160 -0
  150. package/dist/esm/debug/PointerStackLayer.js.map +1 -0
  151. package/dist/esm/debug/index.d.ts +6 -0
  152. package/dist/esm/debug/index.js +7 -0
  153. package/dist/esm/debug/index.js.map +1 -0
  154. package/dist/esm/index.js +30 -4
  155. package/dist/esm/index.js.map +1 -1
  156. package/dist/esm/input/ArcadeStickGamepadMapping.d.ts +7 -0
  157. package/dist/esm/input/ArcadeStickGamepadMapping.js +7 -0
  158. package/dist/esm/input/ArcadeStickGamepadMapping.js.map +1 -1
  159. package/dist/esm/input/GameCubeGamepadMapping.d.ts +9 -0
  160. package/dist/esm/input/GameCubeGamepadMapping.js +9 -0
  161. package/dist/esm/input/GameCubeGamepadMapping.js.map +1 -1
  162. package/dist/esm/input/Gamepad.d.ts +48 -0
  163. package/dist/esm/input/Gamepad.js +43 -0
  164. package/dist/esm/input/Gamepad.js.map +1 -1
  165. package/dist/esm/input/GamepadChannels.d.ts +8 -0
  166. package/dist/esm/input/GamepadChannels.js +8 -0
  167. package/dist/esm/input/GamepadChannels.js.map +1 -1
  168. package/dist/esm/input/GamepadControl.d.ts +18 -0
  169. package/dist/esm/input/GamepadControl.js +13 -0
  170. package/dist/esm/input/GamepadControl.js.map +1 -1
  171. package/dist/esm/input/GamepadDefinitions.d.ts +64 -1
  172. package/dist/esm/input/GamepadDefinitions.js +40 -1
  173. package/dist/esm/input/GamepadDefinitions.js.map +1 -1
  174. package/dist/esm/input/GamepadMapping.d.ts +32 -2
  175. package/dist/esm/input/GamepadMapping.js +24 -0
  176. package/dist/esm/input/GamepadMapping.js.map +1 -1
  177. package/dist/esm/input/GamepadPromptLayouts.d.ts +32 -1
  178. package/dist/esm/input/GamepadPromptLayouts.js +25 -1
  179. package/dist/esm/input/GamepadPromptLayouts.js.map +1 -1
  180. package/dist/esm/input/GenericDualAnalogGamepadMapping.d.ts +10 -0
  181. package/dist/esm/input/GenericDualAnalogGamepadMapping.js +10 -0
  182. package/dist/esm/input/GenericDualAnalogGamepadMapping.js.map +1 -1
  183. package/dist/esm/input/GestureRecognizer.d.ts +11 -2
  184. package/dist/esm/input/GestureRecognizer.js +11 -2
  185. package/dist/esm/input/GestureRecognizer.js.map +1 -1
  186. package/dist/esm/input/Input.d.ts +24 -0
  187. package/dist/esm/input/Input.js +22 -0
  188. package/dist/esm/input/Input.js.map +1 -1
  189. package/dist/esm/input/InputManager.d.ts +43 -0
  190. package/dist/esm/input/InputManager.js +68 -5
  191. package/dist/esm/input/InputManager.js.map +1 -1
  192. package/dist/esm/input/InteractionEvent.d.ts +31 -0
  193. package/dist/esm/input/InteractionEvent.js +37 -0
  194. package/dist/esm/input/InteractionEvent.js.map +1 -0
  195. package/dist/esm/input/InteractionManager.d.ts +163 -0
  196. package/dist/esm/input/InteractionManager.js +575 -0
  197. package/dist/esm/input/InteractionManager.js.map +1 -0
  198. package/dist/esm/input/JoyConLeftGamepadMapping.d.ts +9 -0
  199. package/dist/esm/input/JoyConLeftGamepadMapping.js +9 -0
  200. package/dist/esm/input/JoyConLeftGamepadMapping.js.map +1 -1
  201. package/dist/esm/input/JoyConRightGamepadMapping.d.ts +9 -0
  202. package/dist/esm/input/JoyConRightGamepadMapping.js +9 -0
  203. package/dist/esm/input/JoyConRightGamepadMapping.js.map +1 -1
  204. package/dist/esm/input/PlayStationGamepadMapping.d.ts +8 -0
  205. package/dist/esm/input/PlayStationGamepadMapping.js +8 -0
  206. package/dist/esm/input/PlayStationGamepadMapping.js.map +1 -1
  207. package/dist/esm/input/Pointer.d.ts +22 -0
  208. package/dist/esm/input/Pointer.js +22 -0
  209. package/dist/esm/input/Pointer.js.map +1 -1
  210. package/dist/esm/input/SteamControllerGamepadMapping.d.ts +8 -0
  211. package/dist/esm/input/SteamControllerGamepadMapping.js +8 -0
  212. package/dist/esm/input/SteamControllerGamepadMapping.js.map +1 -1
  213. package/dist/esm/input/SwitchProGamepadMapping.d.ts +9 -0
  214. package/dist/esm/input/SwitchProGamepadMapping.js +9 -0
  215. package/dist/esm/input/SwitchProGamepadMapping.js.map +1 -1
  216. package/dist/esm/input/XboxGamepadMapping.d.ts +8 -0
  217. package/dist/esm/input/XboxGamepadMapping.js +8 -0
  218. package/dist/esm/input/XboxGamepadMapping.js.map +1 -1
  219. package/dist/esm/input/index.d.ts +2 -0
  220. package/dist/esm/input/interaction-hooks.d.ts +34 -0
  221. package/dist/esm/input/interaction-hooks.js +35 -0
  222. package/dist/esm/input/interaction-hooks.js.map +1 -0
  223. package/dist/esm/input/types.d.ts +20 -0
  224. package/dist/esm/input/types.js +20 -0
  225. package/dist/esm/input/types.js.map +1 -1
  226. package/dist/esm/math/AbstractVector.d.ts +83 -0
  227. package/dist/esm/math/AbstractVector.js +83 -0
  228. package/dist/esm/math/AbstractVector.js.map +1 -1
  229. package/dist/esm/math/Circle.d.ts +44 -2
  230. package/dist/esm/math/Circle.js +116 -16
  231. package/dist/esm/math/Circle.js.map +1 -1
  232. package/dist/esm/math/CircleLike.d.ts +1 -0
  233. package/dist/esm/math/Collision.d.ts +39 -0
  234. package/dist/esm/math/Collision.js +5 -0
  235. package/dist/esm/math/Collision.js.map +1 -1
  236. package/dist/esm/math/Ellipse.d.ts +11 -0
  237. package/dist/esm/math/Ellipse.js +18 -2
  238. package/dist/esm/math/Ellipse.js.map +1 -1
  239. package/dist/esm/math/EllipseLike.d.ts +3 -0
  240. package/dist/esm/math/Flags.d.ts +31 -0
  241. package/dist/esm/math/Flags.js +31 -0
  242. package/dist/esm/math/Flags.js.map +1 -1
  243. package/dist/esm/math/Interval.d.ts +15 -0
  244. package/dist/esm/math/Interval.js +16 -1
  245. package/dist/esm/math/Interval.js.map +1 -1
  246. package/dist/esm/math/Line.d.ts +17 -1
  247. package/dist/esm/math/Line.js +17 -1
  248. package/dist/esm/math/Line.js.map +1 -1
  249. package/dist/esm/math/LineLike.d.ts +1 -0
  250. package/dist/esm/math/Matrix.d.ts +44 -3
  251. package/dist/esm/math/Matrix.js +44 -3
  252. package/dist/esm/math/Matrix.js.map +1 -1
  253. package/dist/esm/math/ObservableSize.d.ts +7 -0
  254. package/dist/esm/math/ObservableSize.js +7 -0
  255. package/dist/esm/math/ObservableSize.js.map +1 -1
  256. package/dist/esm/math/ObservableVector.d.ts +11 -2
  257. package/dist/esm/math/ObservableVector.js +13 -2
  258. package/dist/esm/math/ObservableVector.js.map +1 -1
  259. package/dist/esm/math/PointLike.d.ts +1 -0
  260. package/dist/esm/math/PolarVector.d.ts +15 -0
  261. package/dist/esm/math/PolarVector.js +16 -1
  262. package/dist/esm/math/PolarVector.js.map +1 -1
  263. package/dist/esm/math/Polygon.d.ts +35 -1
  264. package/dist/esm/math/Polygon.js +78 -6
  265. package/dist/esm/math/Polygon.js.map +1 -1
  266. package/dist/esm/math/PolygonLike.d.ts +4 -0
  267. package/dist/esm/math/Quadtree.d.ts +84 -0
  268. package/dist/esm/math/Quadtree.js +204 -0
  269. package/dist/esm/math/Quadtree.js.map +1 -0
  270. package/dist/esm/math/Random.d.ts +25 -0
  271. package/dist/esm/math/Random.js +26 -1
  272. package/dist/esm/math/Random.js.map +1 -1
  273. package/dist/esm/math/Rectangle.d.ts +15 -0
  274. package/dist/esm/math/Rectangle.js +17 -2
  275. package/dist/esm/math/Rectangle.js.map +1 -1
  276. package/dist/esm/math/RectangleLike.d.ts +1 -0
  277. package/dist/esm/math/Segment.d.ts +7 -0
  278. package/dist/esm/math/Segment.js +7 -0
  279. package/dist/esm/math/Segment.js.map +1 -1
  280. package/dist/esm/math/ShapeLike.d.ts +6 -0
  281. package/dist/esm/math/Size.d.ts +9 -0
  282. package/dist/esm/math/Size.js +10 -1
  283. package/dist/esm/math/Size.js.map +1 -1
  284. package/dist/esm/math/Vector.d.ts +16 -0
  285. package/dist/esm/math/Vector.js +17 -1
  286. package/dist/esm/math/Vector.js.map +1 -1
  287. package/dist/esm/math/collision-detection.d.ts +47 -1
  288. package/dist/esm/math/collision-detection.js +231 -17
  289. package/dist/esm/math/collision-detection.js.map +1 -1
  290. package/dist/esm/math/collision-primitives.d.ts +23 -0
  291. package/dist/esm/math/collision-primitives.js +23 -0
  292. package/dist/esm/math/collision-primitives.js.map +1 -1
  293. package/dist/esm/math/geometry.d.ts +42 -0
  294. package/dist/esm/math/geometry.js +37 -0
  295. package/dist/esm/math/geometry.js.map +1 -1
  296. package/dist/esm/math/index.d.ts +1 -0
  297. package/dist/esm/math/utils.d.ts +41 -0
  298. package/dist/esm/math/utils.js +41 -0
  299. package/dist/esm/math/utils.js.map +1 -1
  300. package/dist/esm/particles/Particle.d.ts +37 -0
  301. package/dist/esm/particles/Particle.js +37 -0
  302. package/dist/esm/particles/Particle.js.map +1 -1
  303. package/dist/esm/particles/ParticleProperties.d.ts +15 -0
  304. package/dist/esm/particles/ParticleSystem.d.ts +69 -0
  305. package/dist/esm/particles/ParticleSystem.js +70 -0
  306. package/dist/esm/particles/ParticleSystem.js.map +1 -1
  307. package/dist/esm/particles/affectors/ColorAffector.d.ts +13 -0
  308. package/dist/esm/particles/affectors/ColorAffector.js +13 -0
  309. package/dist/esm/particles/affectors/ColorAffector.js.map +1 -1
  310. package/dist/esm/particles/affectors/ForceAffector.d.ts +11 -0
  311. package/dist/esm/particles/affectors/ForceAffector.js +11 -0
  312. package/dist/esm/particles/affectors/ForceAffector.js.map +1 -1
  313. package/dist/esm/particles/affectors/ParticleAffector.d.ts +13 -0
  314. package/dist/esm/particles/affectors/ScaleAffector.d.ts +10 -0
  315. package/dist/esm/particles/affectors/ScaleAffector.js +10 -0
  316. package/dist/esm/particles/affectors/ScaleAffector.js.map +1 -1
  317. package/dist/esm/particles/affectors/TorqueAffector.d.ts +11 -0
  318. package/dist/esm/particles/affectors/TorqueAffector.js +12 -1
  319. package/dist/esm/particles/affectors/TorqueAffector.js.map +1 -1
  320. package/dist/esm/particles/emitters/ParticleEmitter.d.ts +13 -0
  321. package/dist/esm/particles/emitters/ParticleOptions.d.ts +16 -0
  322. package/dist/esm/particles/emitters/ParticleOptions.js +16 -0
  323. package/dist/esm/particles/emitters/ParticleOptions.js.map +1 -1
  324. package/dist/esm/particles/emitters/UniversalEmitter.d.ts +23 -0
  325. package/dist/esm/particles/emitters/UniversalEmitter.js +23 -0
  326. package/dist/esm/particles/emitters/UniversalEmitter.js.map +1 -1
  327. package/dist/esm/rendering/CallbackRenderPass.d.ts +12 -0
  328. package/dist/esm/rendering/CallbackRenderPass.js +12 -0
  329. package/dist/esm/rendering/CallbackRenderPass.js.map +1 -1
  330. package/dist/esm/rendering/Container.d.ts +44 -0
  331. package/dist/esm/rendering/Container.js +62 -0
  332. package/dist/esm/rendering/Container.js.map +1 -1
  333. package/dist/esm/rendering/Drawable.d.ts +22 -0
  334. package/dist/esm/rendering/Drawable.js +22 -0
  335. package/dist/esm/rendering/Drawable.js.map +1 -1
  336. package/dist/esm/rendering/RenderBackend.d.ts +13 -0
  337. package/dist/esm/rendering/RenderBackendType.d.ts +4 -0
  338. package/dist/esm/rendering/RenderBackendType.js +4 -0
  339. package/dist/esm/rendering/RenderBackendType.js.map +1 -1
  340. package/dist/esm/rendering/RenderNode.d.ts +45 -0
  341. package/dist/esm/rendering/RenderNode.js +62 -0
  342. package/dist/esm/rendering/RenderNode.js.map +1 -1
  343. package/dist/esm/rendering/RenderPass.d.ts +8 -0
  344. package/dist/esm/rendering/RenderStats.d.ts +19 -0
  345. package/dist/esm/rendering/RenderStats.js +7 -0
  346. package/dist/esm/rendering/RenderStats.js.map +1 -1
  347. package/dist/esm/rendering/RenderTarget.d.ts +17 -0
  348. package/dist/esm/rendering/RenderTarget.js +17 -0
  349. package/dist/esm/rendering/RenderTarget.js.map +1 -1
  350. package/dist/esm/rendering/RenderTargetPass.d.ts +11 -0
  351. package/dist/esm/rendering/RenderTargetPass.js +7 -0
  352. package/dist/esm/rendering/RenderTargetPass.js.map +1 -1
  353. package/dist/esm/rendering/Renderer.d.ts +15 -0
  354. package/dist/esm/rendering/RendererRegistry.d.ts +7 -0
  355. package/dist/esm/rendering/RendererRegistry.js +7 -0
  356. package/dist/esm/rendering/RendererRegistry.js.map +1 -1
  357. package/dist/esm/rendering/View.d.ts +73 -6
  358. package/dist/esm/rendering/View.js +69 -2
  359. package/dist/esm/rendering/View.js.map +1 -1
  360. package/dist/esm/rendering/filters/BlurFilter.d.ts +9 -0
  361. package/dist/esm/rendering/filters/BlurFilter.js +8 -0
  362. package/dist/esm/rendering/filters/BlurFilter.js.map +1 -1
  363. package/dist/esm/rendering/filters/ColorFilter.d.ts +7 -0
  364. package/dist/esm/rendering/filters/ColorFilter.js +7 -0
  365. package/dist/esm/rendering/filters/ColorFilter.js.map +1 -1
  366. package/dist/esm/rendering/filters/Filter.d.ts +23 -0
  367. package/dist/esm/rendering/filters/Filter.js +20 -0
  368. package/dist/esm/rendering/filters/Filter.js.map +1 -1
  369. package/dist/esm/rendering/filters/WebGl2ShaderFilter.d.ts +114 -0
  370. package/dist/esm/rendering/filters/WebGl2ShaderFilter.js +273 -0
  371. package/dist/esm/rendering/filters/WebGl2ShaderFilter.js.map +1 -0
  372. package/dist/esm/rendering/filters/WebGpuShaderFilter.d.ts +116 -0
  373. package/dist/esm/rendering/filters/WebGpuShaderFilter.js +402 -0
  374. package/dist/esm/rendering/filters/WebGpuShaderFilter.js.map +1 -0
  375. package/dist/esm/rendering/index.d.ts +3 -0
  376. package/dist/esm/rendering/mesh/Mesh.d.ts +2 -0
  377. package/dist/esm/rendering/mesh/Mesh.js +3 -0
  378. package/dist/esm/rendering/mesh/Mesh.js.map +1 -1
  379. package/dist/esm/rendering/primitives/Graphics.d.ts +34 -0
  380. package/dist/esm/rendering/primitives/Graphics.js +34 -0
  381. package/dist/esm/rendering/primitives/Graphics.js.map +1 -1
  382. package/dist/esm/rendering/shader/Shader.d.ts +36 -0
  383. package/dist/esm/rendering/shader/Shader.js +26 -0
  384. package/dist/esm/rendering/shader/Shader.js.map +1 -1
  385. package/dist/esm/rendering/shader/ShaderAttribute.d.ts +13 -0
  386. package/dist/esm/rendering/shader/ShaderAttribute.js +13 -0
  387. package/dist/esm/rendering/shader/ShaderAttribute.js.map +1 -1
  388. package/dist/esm/rendering/shader/ShaderUniform.d.ts +27 -0
  389. package/dist/esm/rendering/shader/ShaderUniform.js +28 -1
  390. package/dist/esm/rendering/shader/ShaderUniform.js.map +1 -1
  391. package/dist/esm/rendering/shader/upgradeFragmentShaderToGl300.d.ts +34 -0
  392. package/dist/esm/rendering/shader/upgradeFragmentShaderToGl300.js +60 -0
  393. package/dist/esm/rendering/shader/upgradeFragmentShaderToGl300.js.map +1 -0
  394. package/dist/esm/rendering/sprite/AnimatedSprite.d.ts +38 -0
  395. package/dist/esm/rendering/sprite/AnimatedSprite.js +36 -0
  396. package/dist/esm/rendering/sprite/AnimatedSprite.js.map +1 -1
  397. package/dist/esm/rendering/sprite/Sprite.d.ts +62 -1
  398. package/dist/esm/rendering/sprite/Sprite.js +97 -19
  399. package/dist/esm/rendering/sprite/Sprite.js.map +1 -1
  400. package/dist/esm/rendering/sprite/Spritesheet.d.ts +25 -0
  401. package/dist/esm/rendering/sprite/Spritesheet.js +20 -0
  402. package/dist/esm/rendering/sprite/Spritesheet.js.map +1 -1
  403. package/dist/esm/rendering/text/Text.d.ts +2 -0
  404. package/dist/esm/rendering/text/Text.js +2 -0
  405. package/dist/esm/rendering/text/Text.js.map +1 -1
  406. package/dist/esm/rendering/text/TextStyle.d.ts +29 -0
  407. package/dist/esm/rendering/text/TextStyle.js +24 -0
  408. package/dist/esm/rendering/text/TextStyle.js.map +1 -1
  409. package/dist/esm/rendering/text/types.d.ts +1 -0
  410. package/dist/esm/rendering/texture/RenderTexture.d.ts +16 -0
  411. package/dist/esm/rendering/texture/RenderTexture.js +16 -0
  412. package/dist/esm/rendering/texture/RenderTexture.js.map +1 -1
  413. package/dist/esm/rendering/texture/Sampler.d.ts +23 -0
  414. package/dist/esm/rendering/texture/Sampler.js +13 -0
  415. package/dist/esm/rendering/texture/Sampler.js.map +1 -1
  416. package/dist/esm/rendering/texture/Texture.d.ts +30 -0
  417. package/dist/esm/rendering/texture/Texture.js +30 -0
  418. package/dist/esm/rendering/texture/Texture.js.map +1 -1
  419. package/dist/esm/rendering/types.d.ts +29 -0
  420. package/dist/esm/rendering/types.js +29 -0
  421. package/dist/esm/rendering/types.js.map +1 -1
  422. package/dist/esm/rendering/utils.d.ts +20 -0
  423. package/dist/esm/rendering/utils.js +10 -0
  424. package/dist/esm/rendering/utils.js.map +1 -1
  425. package/dist/esm/rendering/video/Video.d.ts +39 -1
  426. package/dist/esm/rendering/video/Video.js +68 -6
  427. package/dist/esm/rendering/video/Video.js.map +1 -1
  428. package/dist/esm/rendering/webgl2/AbstractWebGl2BatchedRenderer.d.ts +7 -0
  429. package/dist/esm/rendering/webgl2/AbstractWebGl2BatchedRenderer.js +7 -0
  430. package/dist/esm/rendering/webgl2/AbstractWebGl2BatchedRenderer.js.map +1 -1
  431. package/dist/esm/rendering/webgl2/WebGl2Backend.d.ts +17 -4
  432. package/dist/esm/rendering/webgl2/WebGl2Backend.js +20 -16
  433. package/dist/esm/rendering/webgl2/WebGl2Backend.js.map +1 -1
  434. package/dist/esm/rendering/webgl2/WebGl2RenderBuffer.d.ts +8 -0
  435. package/dist/esm/rendering/webgl2/WebGl2RenderBuffer.js +8 -0
  436. package/dist/esm/rendering/webgl2/WebGl2RenderBuffer.js.map +1 -1
  437. package/dist/esm/rendering/webgpu/WebGpuBackend.d.ts +45 -8
  438. package/dist/esm/rendering/webgpu/WebGpuBackend.js +160 -40
  439. package/dist/esm/rendering/webgpu/WebGpuBackend.js.map +1 -1
  440. package/dist/esm/resources/AbstractAssetFactory.d.ts +20 -0
  441. package/dist/esm/resources/AbstractAssetFactory.js +20 -0
  442. package/dist/esm/resources/AbstractAssetFactory.js.map +1 -1
  443. package/dist/esm/resources/AssetFactory.d.ts +26 -0
  444. package/dist/esm/resources/AssetManifest.d.ts +45 -0
  445. package/dist/esm/resources/AssetManifest.js +25 -0
  446. package/dist/esm/resources/AssetManifest.js.map +1 -1
  447. package/dist/esm/resources/CacheFirstStrategy.d.ts +12 -0
  448. package/dist/esm/resources/CacheFirstStrategy.js +12 -0
  449. package/dist/esm/resources/CacheFirstStrategy.js.map +1 -1
  450. package/dist/esm/resources/CacheStore.d.ts +23 -0
  451. package/dist/esm/resources/CacheStrategy.d.ts +20 -0
  452. package/dist/esm/resources/Database.d.ts +45 -0
  453. package/dist/esm/resources/FactoryRegistry.d.ts +28 -0
  454. package/dist/esm/resources/FactoryRegistry.js +24 -0
  455. package/dist/esm/resources/FactoryRegistry.js.map +1 -1
  456. package/dist/esm/resources/IndexedDbDatabase.d.ts +22 -1
  457. package/dist/esm/resources/IndexedDbDatabase.js +21 -0
  458. package/dist/esm/resources/IndexedDbDatabase.js.map +1 -1
  459. package/dist/esm/resources/IndexedDbStore.d.ts +20 -0
  460. package/dist/esm/resources/IndexedDbStore.js +12 -0
  461. package/dist/esm/resources/IndexedDbStore.js.map +1 -1
  462. package/dist/esm/resources/Loader.d.ts +167 -0
  463. package/dist/esm/resources/Loader.js +110 -0
  464. package/dist/esm/resources/Loader.js.map +1 -1
  465. package/dist/esm/resources/NetworkOnlyStrategy.d.ts +8 -0
  466. package/dist/esm/resources/NetworkOnlyStrategy.js +8 -0
  467. package/dist/esm/resources/NetworkOnlyStrategy.js.map +1 -1
  468. package/dist/esm/resources/factories/BinaryFactory.d.ts +13 -0
  469. package/dist/esm/resources/factories/BinaryFactory.js +13 -0
  470. package/dist/esm/resources/factories/BinaryFactory.js.map +1 -1
  471. package/dist/esm/resources/factories/FontFactory.d.ts +37 -0
  472. package/dist/esm/resources/factories/FontFactory.js +26 -0
  473. package/dist/esm/resources/factories/FontFactory.js.map +1 -1
  474. package/dist/esm/resources/factories/ImageFactory.d.ts +24 -0
  475. package/dist/esm/resources/factories/ImageFactory.js +19 -0
  476. package/dist/esm/resources/factories/ImageFactory.js.map +1 -1
  477. package/dist/esm/resources/factories/JsonFactory.d.ts +13 -0
  478. package/dist/esm/resources/factories/JsonFactory.js +13 -0
  479. package/dist/esm/resources/factories/JsonFactory.js.map +1 -1
  480. package/dist/esm/resources/factories/MusicFactory.d.ts +36 -0
  481. package/dist/esm/resources/factories/MusicFactory.js +44 -4
  482. package/dist/esm/resources/factories/MusicFactory.js.map +1 -1
  483. package/dist/esm/resources/factories/SoundFactory.d.ts +29 -0
  484. package/dist/esm/resources/factories/SoundFactory.js +18 -0
  485. package/dist/esm/resources/factories/SoundFactory.js.map +1 -1
  486. package/dist/esm/resources/factories/SvgFactory.d.ts +19 -0
  487. package/dist/esm/resources/factories/SvgFactory.js +19 -0
  488. package/dist/esm/resources/factories/SvgFactory.js.map +1 -1
  489. package/dist/esm/resources/factories/TextFactory.d.ts +11 -0
  490. package/dist/esm/resources/factories/TextFactory.js +11 -0
  491. package/dist/esm/resources/factories/TextFactory.js.map +1 -1
  492. package/dist/esm/resources/factories/TextureFactory.d.ts +25 -0
  493. package/dist/esm/resources/factories/TextureFactory.js +20 -0
  494. package/dist/esm/resources/factories/TextureFactory.js.map +1 -1
  495. package/dist/esm/resources/factories/VideoFactory.d.ts +37 -0
  496. package/dist/esm/resources/factories/VideoFactory.js +48 -5
  497. package/dist/esm/resources/factories/VideoFactory.js.map +1 -1
  498. package/dist/esm/resources/factories/VttFactory.d.ts +18 -0
  499. package/dist/esm/resources/factories/VttFactory.js +24 -0
  500. package/dist/esm/resources/factories/VttFactory.js.map +1 -1
  501. package/dist/esm/resources/factories/WasmFactory.d.ts +16 -0
  502. package/dist/esm/resources/factories/WasmFactory.js +16 -0
  503. package/dist/esm/resources/factories/WasmFactory.js.map +1 -1
  504. package/dist/esm/resources/utils.d.ts +10 -0
  505. package/dist/esm/resources/utils.js +10 -0
  506. package/dist/esm/resources/utils.js.map +1 -1
  507. package/dist/exo.esm.js +11182 -2538
  508. package/dist/exo.esm.js.map +1 -1
  509. package/package.json +20 -9
  510. package/dist/esm/core/Quadtree.d.ts +0 -20
  511. package/dist/esm/core/Quadtree.js +0 -86
  512. 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 —