@codexo/exojs 0.7.13 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (544) hide show
  1. package/CHANGELOG.md +637 -124
  2. package/README.md +19 -19
  3. package/dist/esm/animation/Easing.js +13 -21
  4. package/dist/esm/animation/Easing.js.map +1 -1
  5. package/dist/esm/animation/Tween.d.ts +3 -3
  6. package/dist/esm/animation/Tween.js +7 -5
  7. package/dist/esm/animation/Tween.js.map +1 -1
  8. package/dist/esm/animation/TweenManager.js +1 -1
  9. package/dist/esm/animation/TweenManager.js.map +1 -1
  10. package/dist/esm/animation/types.js.map +1 -1
  11. package/dist/esm/audio/AbstractMedia.d.ts +1 -1
  12. package/dist/esm/audio/AbstractMedia.js +1 -1
  13. package/dist/esm/audio/AbstractMedia.js.map +1 -1
  14. package/dist/esm/audio/AudioAnalyser.d.ts +1 -1
  15. package/dist/esm/audio/AudioAnalyser.js +2 -2
  16. package/dist/esm/audio/AudioAnalyser.js.map +1 -1
  17. package/dist/esm/audio/AudioBus.d.ts +1 -1
  18. package/dist/esm/audio/AudioBus.js.map +1 -1
  19. package/dist/esm/audio/AudioFilter.js +3 -1
  20. package/dist/esm/audio/AudioFilter.js.map +1 -1
  21. package/dist/esm/audio/AudioListener.d.ts +1 -1
  22. package/dist/esm/audio/AudioListener.js +5 -3
  23. package/dist/esm/audio/AudioListener.js.map +1 -1
  24. package/dist/esm/audio/AudioManager.js.map +1 -1
  25. package/dist/esm/audio/BeatDetector.d.ts +4 -4
  26. package/dist/esm/audio/BeatDetector.js +68 -44
  27. package/dist/esm/audio/BeatDetector.js.map +1 -1
  28. package/dist/esm/audio/Envelope.d.ts +1 -1
  29. package/dist/esm/audio/Envelope.js +2 -2
  30. package/dist/esm/audio/Envelope.js.map +1 -1
  31. package/dist/esm/audio/Music.d.ts +1 -1
  32. package/dist/esm/audio/Music.js +1 -1
  33. package/dist/esm/audio/Music.js.map +1 -1
  34. package/dist/esm/audio/OscillatorSound.d.ts +2 -2
  35. package/dist/esm/audio/OscillatorSound.js.map +1 -1
  36. package/dist/esm/audio/Sound.d.ts +40 -2
  37. package/dist/esm/audio/Sound.js +69 -9
  38. package/dist/esm/audio/Sound.js.map +1 -1
  39. package/dist/esm/audio/audio-context.js +1 -1
  40. package/dist/esm/audio/audio-context.js.map +1 -1
  41. package/dist/esm/audio/crossFade.js +1 -1
  42. package/dist/esm/audio/crossFade.js.map +1 -1
  43. package/dist/esm/audio/dsp/mel.d.ts +2 -2
  44. package/dist/esm/audio/dsp/tempogram.d.ts +2 -2
  45. package/dist/esm/audio/filters/ChorusFilter.js +12 -4
  46. package/dist/esm/audio/filters/ChorusFilter.js.map +1 -1
  47. package/dist/esm/audio/filters/CompressorFilter.d.ts +9 -0
  48. package/dist/esm/audio/filters/CompressorFilter.js +11 -0
  49. package/dist/esm/audio/filters/CompressorFilter.js.map +1 -1
  50. package/dist/esm/audio/filters/DelayFilter.js.map +1 -1
  51. package/dist/esm/audio/filters/DuckingFilter.d.ts +1 -1
  52. package/dist/esm/audio/filters/DuckingFilter.js +13 -5
  53. package/dist/esm/audio/filters/DuckingFilter.js.map +1 -1
  54. package/dist/esm/audio/filters/EqualizerFilter.js.map +1 -1
  55. package/dist/esm/audio/filters/GranularFilter.js +30 -14
  56. package/dist/esm/audio/filters/GranularFilter.js.map +1 -1
  57. package/dist/esm/audio/filters/HighpassFilter.js.map +1 -1
  58. package/dist/esm/audio/filters/LowpassFilter.js.map +1 -1
  59. package/dist/esm/audio/filters/PitchShiftFilter.js +14 -10
  60. package/dist/esm/audio/filters/PitchShiftFilter.js.map +1 -1
  61. package/dist/esm/audio/filters/ReverbFilter.js.map +1 -1
  62. package/dist/esm/audio/filters/VocoderFilter.d.ts +2 -2
  63. package/dist/esm/audio/filters/VocoderFilter.js +20 -12
  64. package/dist/esm/audio/filters/VocoderFilter.js.map +1 -1
  65. package/dist/esm/audio/filters/WorkletFilter.js +5 -6
  66. package/dist/esm/audio/filters/WorkletFilter.js.map +1 -1
  67. package/dist/esm/audio/filters/index.d.ts +7 -7
  68. package/dist/esm/audio/index.d.ts +9 -9
  69. package/dist/esm/audio/worklet/registerWorklet.js +7 -3
  70. package/dist/esm/audio/worklet/registerWorklet.js.map +1 -1
  71. package/dist/esm/core/Application.d.ts +13 -13
  72. package/dist/esm/core/Application.js +24 -16
  73. package/dist/esm/core/Application.js.map +1 -1
  74. package/dist/esm/core/Bounds.d.ts +1 -1
  75. package/dist/esm/core/Bounds.js +1 -3
  76. package/dist/esm/core/Bounds.js.map +1 -1
  77. package/dist/esm/core/Clock.js +1 -1
  78. package/dist/esm/core/Clock.js.map +1 -1
  79. package/dist/esm/core/Color.js +2 -5
  80. package/dist/esm/core/Color.js.map +1 -1
  81. package/dist/esm/core/Scene.d.ts +10 -10
  82. package/dist/esm/core/Scene.js +1 -1
  83. package/dist/esm/core/Scene.js.map +1 -1
  84. package/dist/esm/core/SceneManager.d.ts +4 -4
  85. package/dist/esm/core/SceneManager.js +39 -33
  86. package/dist/esm/core/SceneManager.js.map +1 -1
  87. package/dist/esm/core/SceneNode.d.ts +8 -8
  88. package/dist/esm/core/SceneNode.js +39 -29
  89. package/dist/esm/core/SceneNode.js.map +1 -1
  90. package/dist/esm/core/Signal.d.ts +3 -3
  91. package/dist/esm/core/Signal.js +3 -3
  92. package/dist/esm/core/Signal.js.map +1 -1
  93. package/dist/esm/core/Time.js +6 -6
  94. package/dist/esm/core/Time.js.map +1 -1
  95. package/dist/esm/core/Timer.d.ts +1 -1
  96. package/dist/esm/core/Timer.js +1 -1
  97. package/dist/esm/core/Timer.js.map +1 -1
  98. package/dist/esm/core/capabilities.js +2 -4
  99. package/dist/esm/core/capabilities.js.map +1 -1
  100. package/dist/esm/core/index.d.ts +2 -2
  101. package/dist/esm/core/utils.d.ts +4 -4
  102. package/dist/esm/core/utils.js +10 -10
  103. package/dist/esm/core/utils.js.map +1 -1
  104. package/dist/esm/debug/BoundingBoxesLayer.d.ts +3 -3
  105. package/dist/esm/debug/BoundingBoxesLayer.js +6 -13
  106. package/dist/esm/debug/BoundingBoxesLayer.js.map +1 -1
  107. package/dist/esm/debug/DebugLayer.d.ts +1 -1
  108. package/dist/esm/debug/DebugLayer.js.map +1 -1
  109. package/dist/esm/debug/DebugOverlay.d.ts +2 -2
  110. package/dist/esm/debug/DebugOverlay.js +2 -2
  111. package/dist/esm/debug/DebugOverlay.js.map +1 -1
  112. package/dist/esm/debug/HitTestLayer.d.ts +3 -3
  113. package/dist/esm/debug/HitTestLayer.js +3 -10
  114. package/dist/esm/debug/HitTestLayer.js.map +1 -1
  115. package/dist/esm/debug/PerformanceLayer.d.ts +2 -2
  116. package/dist/esm/debug/PerformanceLayer.js +6 -6
  117. package/dist/esm/debug/PerformanceLayer.js.map +1 -1
  118. package/dist/esm/debug/PointerStackLayer.d.ts +3 -3
  119. package/dist/esm/debug/PointerStackLayer.js +3 -3
  120. package/dist/esm/debug/PointerStackLayer.js.map +1 -1
  121. package/dist/esm/debug/index.d.ts +3 -3
  122. package/dist/esm/debug/index.js +3 -3
  123. package/dist/esm/index.d.ts +1 -1
  124. package/dist/esm/index.js +106 -84
  125. package/dist/esm/index.js.map +1 -1
  126. package/dist/esm/input/ArcadeStickGamepadMapping.js.map +1 -1
  127. package/dist/esm/input/GameCubeGamepadMapping.d.ts +1 -1
  128. package/dist/esm/input/GameCubeGamepadMapping.js +1 -1
  129. package/dist/esm/input/GameCubeGamepadMapping.js.map +1 -1
  130. package/dist/esm/input/Gamepad.d.ts +7 -7
  131. package/dist/esm/input/Gamepad.js +10 -6
  132. package/dist/esm/input/Gamepad.js.map +1 -1
  133. package/dist/esm/input/GamepadAxis.js +1 -1
  134. package/dist/esm/input/GamepadAxis.js.map +1 -1
  135. package/dist/esm/input/GamepadButton.js +1 -1
  136. package/dist/esm/input/GamepadButton.js.map +1 -1
  137. package/dist/esm/input/GamepadDefinitions.d.ts +4 -4
  138. package/dist/esm/input/GamepadDefinitions.js +12 -14
  139. package/dist/esm/input/GamepadDefinitions.js.map +1 -1
  140. package/dist/esm/input/GamepadMapping.d.ts +5 -5
  141. package/dist/esm/input/GamepadMapping.js.map +1 -1
  142. package/dist/esm/input/GamepadPromptLayouts.d.ts +2 -2
  143. package/dist/esm/input/GamepadPromptLayouts.js +8 -8
  144. package/dist/esm/input/GamepadPromptLayouts.js.map +1 -1
  145. package/dist/esm/input/GenericDualAnalogGamepadMapping.js.map +1 -1
  146. package/dist/esm/input/GestureRecognizer.d.ts +1 -1
  147. package/dist/esm/input/GestureRecognizer.js.map +1 -1
  148. package/dist/esm/input/InputBinding.d.ts +3 -3
  149. package/dist/esm/input/InputBinding.js.map +1 -1
  150. package/dist/esm/input/InputManager.d.ts +9 -9
  151. package/dist/esm/input/InputManager.js +25 -16
  152. package/dist/esm/input/InputManager.js.map +1 -1
  153. package/dist/esm/input/InteractionEvent.d.ts +1 -1
  154. package/dist/esm/input/InteractionEvent.js.map +1 -1
  155. package/dist/esm/input/InteractionManager.d.ts +2 -2
  156. package/dist/esm/input/InteractionManager.js +25 -16
  157. package/dist/esm/input/InteractionManager.js.map +1 -1
  158. package/dist/esm/input/JoyConLeftGamepadMapping.js.map +1 -1
  159. package/dist/esm/input/JoyConRightGamepadMapping.js.map +1 -1
  160. package/dist/esm/input/PlayStationGamepadMapping.d.ts +1 -1
  161. package/dist/esm/input/PlayStationGamepadMapping.js +1 -1
  162. package/dist/esm/input/PlayStationGamepadMapping.js.map +1 -1
  163. package/dist/esm/input/Pointer.d.ts +2 -2
  164. package/dist/esm/input/Pointer.js +6 -6
  165. package/dist/esm/input/Pointer.js.map +1 -1
  166. package/dist/esm/input/SteamControllerGamepadMapping.d.ts +1 -1
  167. package/dist/esm/input/SteamControllerGamepadMapping.js +1 -1
  168. package/dist/esm/input/SteamControllerGamepadMapping.js.map +1 -1
  169. package/dist/esm/input/SteamDeckGamepadMapping.js.map +1 -1
  170. package/dist/esm/input/SwitchProGamepadMapping.d.ts +1 -1
  171. package/dist/esm/input/SwitchProGamepadMapping.js +1 -1
  172. package/dist/esm/input/SwitchProGamepadMapping.js.map +1 -1
  173. package/dist/esm/input/XboxGamepadMapping.d.ts +1 -1
  174. package/dist/esm/input/XboxGamepadMapping.js +1 -1
  175. package/dist/esm/input/XboxGamepadMapping.js.map +1 -1
  176. package/dist/esm/input/index.d.ts +16 -16
  177. package/dist/esm/input/interaction-hooks.js.map +1 -1
  178. package/dist/esm/input/types.js.map +1 -1
  179. package/dist/esm/math/AbstractVector.js +8 -9
  180. package/dist/esm/math/AbstractVector.js.map +1 -1
  181. package/dist/esm/math/Circle.d.ts +5 -5
  182. package/dist/esm/math/Circle.js +33 -21
  183. package/dist/esm/math/Circle.js.map +1 -1
  184. package/dist/esm/math/Collision.d.ts +2 -2
  185. package/dist/esm/math/Collision.js.map +1 -1
  186. package/dist/esm/math/Ellipse.d.ts +5 -5
  187. package/dist/esm/math/Ellipse.js +28 -19
  188. package/dist/esm/math/Ellipse.js.map +1 -1
  189. package/dist/esm/math/Flags.d.ts +4 -4
  190. package/dist/esm/math/Flags.js.map +1 -1
  191. package/dist/esm/math/Interval.js.map +1 -1
  192. package/dist/esm/math/Line.d.ts +5 -5
  193. package/dist/esm/math/Line.js +23 -15
  194. package/dist/esm/math/Line.js.map +1 -1
  195. package/dist/esm/math/Matrix.js +14 -16
  196. package/dist/esm/math/Matrix.js.map +1 -1
  197. package/dist/esm/math/ObservableSize.js.map +1 -1
  198. package/dist/esm/math/ObservableVector.d.ts +1 -1
  199. package/dist/esm/math/ObservableVector.js.map +1 -1
  200. package/dist/esm/math/PolarVector.js.map +1 -1
  201. package/dist/esm/math/Polygon.d.ts +11 -11
  202. package/dist/esm/math/Polygon.js +37 -26
  203. package/dist/esm/math/Polygon.js.map +1 -1
  204. package/dist/esm/math/PolygonLike.d.ts +1 -1
  205. package/dist/esm/math/Quadtree.js +2 -8
  206. package/dist/esm/math/Quadtree.js.map +1 -1
  207. package/dist/esm/math/Random.js +5 -5
  208. package/dist/esm/math/Random.js.map +1 -1
  209. package/dist/esm/math/Rectangle.d.ts +4 -4
  210. package/dist/esm/math/Rectangle.js +59 -41
  211. package/dist/esm/math/Rectangle.js.map +1 -1
  212. package/dist/esm/math/Segment.d.ts +1 -1
  213. package/dist/esm/math/Segment.js +4 -4
  214. package/dist/esm/math/Segment.js.map +1 -1
  215. package/dist/esm/math/ShapeLike.d.ts +1 -1
  216. package/dist/esm/math/Size.js +1 -2
  217. package/dist/esm/math/Size.js.map +1 -1
  218. package/dist/esm/math/Vector.d.ts +4 -4
  219. package/dist/esm/math/Vector.js +19 -13
  220. package/dist/esm/math/Vector.js.map +1 -1
  221. package/dist/esm/math/collision-detection.d.ts +3 -3
  222. package/dist/esm/math/collision-detection.js +49 -49
  223. package/dist/esm/math/collision-detection.js.map +1 -1
  224. package/dist/esm/math/collision-primitives.d.ts +8 -8
  225. package/dist/esm/math/collision-primitives.js +27 -33
  226. package/dist/esm/math/collision-primitives.js.map +1 -1
  227. package/dist/esm/math/geometry.d.ts +3 -3
  228. package/dist/esm/math/geometry.js +55 -48
  229. package/dist/esm/math/geometry.js.map +1 -1
  230. package/dist/esm/math/index.d.ts +16 -16
  231. package/dist/esm/math/swept-collision.d.ts +3 -3
  232. package/dist/esm/math/swept-collision.js +5 -8
  233. package/dist/esm/math/swept-collision.js.map +1 -1
  234. package/dist/esm/math/triangulate.js +34 -24
  235. package/dist/esm/math/triangulate.js.map +1 -1
  236. package/dist/esm/math/utils.d.ts +2 -2
  237. package/dist/esm/math/utils.js +7 -7
  238. package/dist/esm/math/utils.js.map +1 -1
  239. package/dist/esm/particles/ParticleSystem.d.ts +181 -84
  240. package/dist/esm/particles/ParticleSystem.js +460 -147
  241. package/dist/esm/particles/ParticleSystem.js.map +1 -1
  242. package/dist/esm/particles/distributions/BoxArea.d.ts +17 -0
  243. package/dist/esm/particles/distributions/BoxArea.js +48 -0
  244. package/dist/esm/particles/distributions/BoxArea.js.map +1 -0
  245. package/dist/esm/particles/distributions/CircleArea.d.ts +19 -0
  246. package/dist/esm/particles/distributions/CircleArea.js +33 -0
  247. package/dist/esm/particles/distributions/CircleArea.js.map +1 -0
  248. package/dist/esm/particles/distributions/ConeDirection.d.ts +28 -0
  249. package/dist/esm/particles/distributions/ConeDirection.js +44 -0
  250. package/dist/esm/particles/distributions/ConeDirection.js.map +1 -0
  251. package/dist/esm/particles/distributions/Constant.d.ts +17 -0
  252. package/dist/esm/particles/distributions/Constant.js +35 -0
  253. package/dist/esm/particles/distributions/Constant.js.map +1 -0
  254. package/dist/esm/particles/distributions/Curve.d.ts +30 -0
  255. package/dist/esm/particles/distributions/Curve.js +53 -0
  256. package/dist/esm/particles/distributions/Curve.js.map +1 -0
  257. package/dist/esm/particles/distributions/Distribution.d.ts +45 -0
  258. package/dist/esm/particles/distributions/Gradient.d.ts +40 -0
  259. package/dist/esm/particles/distributions/Gradient.js +72 -0
  260. package/dist/esm/particles/distributions/Gradient.js.map +1 -0
  261. package/dist/esm/particles/distributions/LineSegment.d.ts +15 -0
  262. package/dist/esm/particles/distributions/LineSegment.js +27 -0
  263. package/dist/esm/particles/distributions/LineSegment.js.map +1 -0
  264. package/dist/esm/particles/distributions/Range.d.ts +12 -0
  265. package/dist/esm/particles/distributions/Range.js +19 -0
  266. package/dist/esm/particles/distributions/Range.js.map +1 -0
  267. package/dist/esm/particles/distributions/VectorRange.d.ts +20 -0
  268. package/dist/esm/particles/distributions/VectorRange.js +31 -0
  269. package/dist/esm/particles/distributions/VectorRange.js.map +1 -0
  270. package/dist/esm/particles/distributions/index.d.ts +12 -0
  271. package/dist/esm/particles/gpu/ParticleGpuState.d.ts +57 -0
  272. package/dist/esm/particles/gpu/ParticleGpuState.js +535 -0
  273. package/dist/esm/particles/gpu/ParticleGpuState.js.map +1 -0
  274. package/dist/esm/particles/index.d.ts +2 -10
  275. package/dist/esm/particles/modules/AlphaFadeOverLifetime.d.ts +24 -0
  276. package/dist/esm/particles/modules/AlphaFadeOverLifetime.js +60 -0
  277. package/dist/esm/particles/modules/AlphaFadeOverLifetime.js.map +1 -0
  278. package/dist/esm/particles/modules/ApplyForce.d.ts +20 -0
  279. package/dist/esm/particles/modules/ApplyForce.js +48 -0
  280. package/dist/esm/particles/modules/ApplyForce.js.map +1 -0
  281. package/dist/esm/particles/modules/AttractToPoint.d.ts +27 -0
  282. package/dist/esm/particles/modules/AttractToPoint.js +73 -0
  283. package/dist/esm/particles/modules/AttractToPoint.js.map +1 -0
  284. package/dist/esm/particles/modules/BurstSpawn.d.ts +53 -0
  285. package/dist/esm/particles/modules/BurstSpawn.js +94 -0
  286. package/dist/esm/particles/modules/BurstSpawn.js.map +1 -0
  287. package/dist/esm/particles/modules/ColorOverLifetime.d.ts +22 -0
  288. package/dist/esm/particles/modules/ColorOverLifetime.js +65 -0
  289. package/dist/esm/particles/modules/ColorOverLifetime.js.map +1 -0
  290. package/dist/esm/particles/modules/ColorOverSpeed.d.ts +27 -0
  291. package/dist/esm/particles/modules/ColorOverSpeed.js +86 -0
  292. package/dist/esm/particles/modules/ColorOverSpeed.js.map +1 -0
  293. package/dist/esm/particles/modules/DeathModule.d.ts +24 -0
  294. package/dist/esm/particles/modules/DeathModule.js +25 -0
  295. package/dist/esm/particles/modules/DeathModule.js.map +1 -0
  296. package/dist/esm/particles/modules/Drag.d.ts +20 -0
  297. package/dist/esm/particles/modules/Drag.js +43 -0
  298. package/dist/esm/particles/modules/Drag.js.map +1 -0
  299. package/dist/esm/particles/modules/OrbitalForce.d.ts +28 -0
  300. package/dist/esm/particles/modules/OrbitalForce.js +65 -0
  301. package/dist/esm/particles/modules/OrbitalForce.js.map +1 -0
  302. package/dist/esm/particles/modules/RateSpawn.d.ts +41 -0
  303. package/dist/esm/particles/modules/RateSpawn.js +76 -0
  304. package/dist/esm/particles/modules/RateSpawn.js.map +1 -0
  305. package/dist/esm/particles/modules/RepelFromPoint.d.ts +24 -0
  306. package/dist/esm/particles/modules/RepelFromPoint.js +76 -0
  307. package/dist/esm/particles/modules/RepelFromPoint.js.map +1 -0
  308. package/dist/esm/particles/modules/RotateOverLifetime.d.ts +20 -0
  309. package/dist/esm/particles/modules/RotateOverLifetime.js +41 -0
  310. package/dist/esm/particles/modules/RotateOverLifetime.js.map +1 -0
  311. package/dist/esm/particles/modules/ScaleOverLifetime.d.ts +26 -0
  312. package/dist/esm/particles/modules/ScaleOverLifetime.js +59 -0
  313. package/dist/esm/particles/modules/ScaleOverLifetime.js.map +1 -0
  314. package/dist/esm/particles/modules/SpawnModule.d.ts +30 -0
  315. package/dist/esm/particles/modules/SpawnModule.js +31 -0
  316. package/dist/esm/particles/modules/SpawnModule.js.map +1 -0
  317. package/dist/esm/particles/modules/SpawnOnDeath.d.ts +24 -0
  318. package/dist/esm/particles/modules/SpawnOnDeath.js +47 -0
  319. package/dist/esm/particles/modules/SpawnOnDeath.js.map +1 -0
  320. package/dist/esm/particles/modules/Turbulence.d.ts +30 -0
  321. package/dist/esm/particles/modules/Turbulence.js +122 -0
  322. package/dist/esm/particles/modules/Turbulence.js.map +1 -0
  323. package/dist/esm/particles/modules/UpdateModule.d.ts +95 -0
  324. package/dist/esm/particles/modules/UpdateModule.js +66 -0
  325. package/dist/esm/particles/modules/UpdateModule.js.map +1 -0
  326. package/dist/esm/particles/modules/VelocityOverLifetime.d.ts +30 -0
  327. package/dist/esm/particles/modules/VelocityOverLifetime.js +84 -0
  328. package/dist/esm/particles/modules/VelocityOverLifetime.js.map +1 -0
  329. package/dist/esm/particles/modules/WgslContribution.d.ts +81 -0
  330. package/dist/esm/particles/modules/WgslContribution.js +34 -0
  331. package/dist/esm/particles/modules/WgslContribution.js.map +1 -0
  332. package/dist/esm/particles/modules/index.d.ts +22 -0
  333. package/dist/esm/rendering/CallbackRenderPass.js.map +1 -1
  334. package/dist/esm/rendering/Container.d.ts +2 -2
  335. package/dist/esm/rendering/Container.js +10 -11
  336. package/dist/esm/rendering/Container.js.map +1 -1
  337. package/dist/esm/rendering/Drawable.js.map +1 -1
  338. package/dist/esm/rendering/RenderBackend.d.ts +6 -6
  339. package/dist/esm/rendering/RenderBackendType.js.map +1 -1
  340. package/dist/esm/rendering/RenderNode.d.ts +10 -10
  341. package/dist/esm/rendering/RenderNode.js +31 -31
  342. package/dist/esm/rendering/RenderNode.js.map +1 -1
  343. package/dist/esm/rendering/RenderStats.js.map +1 -1
  344. package/dist/esm/rendering/RenderTarget.d.ts +2 -2
  345. package/dist/esm/rendering/RenderTarget.js +5 -5
  346. package/dist/esm/rendering/RenderTarget.js.map +1 -1
  347. package/dist/esm/rendering/RenderTargetPass.d.ts +3 -3
  348. package/dist/esm/rendering/RenderTargetPass.js.map +1 -1
  349. package/dist/esm/rendering/Renderer.d.ts +2 -2
  350. package/dist/esm/rendering/RendererRegistry.d.ts +3 -3
  351. package/dist/esm/rendering/RendererRegistry.js +2 -3
  352. package/dist/esm/rendering/RendererRegistry.js.map +1 -1
  353. package/dist/esm/rendering/View.d.ts +3 -3
  354. package/dist/esm/rendering/View.js +17 -20
  355. package/dist/esm/rendering/View.js.map +1 -1
  356. package/dist/esm/rendering/filters/BlurFilter.d.ts +1 -1
  357. package/dist/esm/rendering/filters/BlurFilter.js +4 -9
  358. package/dist/esm/rendering/filters/BlurFilter.js.map +1 -1
  359. package/dist/esm/rendering/filters/ColorFilter.d.ts +1 -1
  360. package/dist/esm/rendering/filters/ColorFilter.js +3 -9
  361. package/dist/esm/rendering/filters/ColorFilter.js.map +1 -1
  362. package/dist/esm/rendering/filters/Filter.d.ts +1 -1
  363. package/dist/esm/rendering/filters/Filter.js.map +1 -1
  364. package/dist/esm/rendering/filters/LutFilter.d.ts +87 -0
  365. package/dist/esm/rendering/filters/LutFilter.js +261 -0
  366. package/dist/esm/rendering/filters/LutFilter.js.map +1 -0
  367. package/dist/esm/rendering/filters/WebGl2ShaderFilter.d.ts +2 -2
  368. package/dist/esm/rendering/filters/WebGl2ShaderFilter.js +7 -14
  369. package/dist/esm/rendering/filters/WebGl2ShaderFilter.js.map +1 -1
  370. package/dist/esm/rendering/filters/WebGpuShaderFilter.d.ts +1 -1
  371. package/dist/esm/rendering/filters/WebGpuShaderFilter.js +11 -10
  372. package/dist/esm/rendering/filters/WebGpuShaderFilter.js.map +1 -1
  373. package/dist/esm/rendering/index.d.ts +28 -27
  374. package/dist/esm/rendering/mesh/Mesh.d.ts +49 -1
  375. package/dist/esm/rendering/mesh/Mesh.js +3 -1
  376. package/dist/esm/rendering/mesh/Mesh.js.map +1 -1
  377. package/dist/esm/rendering/primitives/Graphics.d.ts +3 -3
  378. package/dist/esm/rendering/primitives/Graphics.js +12 -12
  379. package/dist/esm/rendering/primitives/Graphics.js.map +1 -1
  380. package/dist/esm/rendering/shader/Shader.js.map +1 -1
  381. package/dist/esm/rendering/shader/ShaderAttribute.js.map +1 -1
  382. package/dist/esm/rendering/shader/ShaderUniform.js.map +1 -1
  383. package/dist/esm/rendering/shader/upgradeFragmentShaderToGl300.js +6 -8
  384. package/dist/esm/rendering/shader/upgradeFragmentShaderToGl300.js.map +1 -1
  385. package/dist/esm/rendering/sprite/AnimatedSprite.d.ts +3 -3
  386. package/dist/esm/rendering/sprite/AnimatedSprite.js.map +1 -1
  387. package/dist/esm/rendering/sprite/Sprite.d.ts +4 -4
  388. package/dist/esm/rendering/sprite/Sprite.js +51 -35
  389. package/dist/esm/rendering/sprite/Sprite.js.map +1 -1
  390. package/dist/esm/rendering/sprite/Spritesheet.d.ts +5 -9
  391. package/dist/esm/rendering/sprite/Spritesheet.js +1 -1
  392. package/dist/esm/rendering/sprite/Spritesheet.js.map +1 -1
  393. package/dist/esm/rendering/text/DynamicGlyphAtlas.js +3 -9
  394. package/dist/esm/rendering/text/DynamicGlyphAtlas.js.map +1 -1
  395. package/dist/esm/rendering/text/Text.js +9 -9
  396. package/dist/esm/rendering/text/Text.js.map +1 -1
  397. package/dist/esm/rendering/text/TextLayout.d.ts +2 -2
  398. package/dist/esm/rendering/text/TextLayout.js.map +1 -1
  399. package/dist/esm/rendering/text/TextStyle.js.map +1 -1
  400. package/dist/esm/rendering/text/atlas-singleton.js.map +1 -1
  401. package/dist/esm/rendering/texture/RenderTexture.d.ts +1 -1
  402. package/dist/esm/rendering/texture/RenderTexture.js +4 -1
  403. package/dist/esm/rendering/texture/RenderTexture.js.map +1 -1
  404. package/dist/esm/rendering/texture/Sampler.js.map +1 -1
  405. package/dist/esm/rendering/texture/Texture.d.ts +2 -2
  406. package/dist/esm/rendering/texture/Texture.js +8 -5
  407. package/dist/esm/rendering/texture/Texture.js.map +1 -1
  408. package/dist/esm/rendering/types.js.map +1 -1
  409. package/dist/esm/rendering/utils.js.map +1 -1
  410. package/dist/esm/rendering/video/Video.d.ts +3 -3
  411. package/dist/esm/rendering/video/Video.js +7 -6
  412. package/dist/esm/rendering/video/Video.js.map +1 -1
  413. package/dist/esm/rendering/webgl2/AbstractWebGl2BatchedRenderer.d.ts +6 -6
  414. package/dist/esm/rendering/webgl2/AbstractWebGl2BatchedRenderer.js +7 -10
  415. package/dist/esm/rendering/webgl2/AbstractWebGl2BatchedRenderer.js.map +1 -1
  416. package/dist/esm/rendering/webgl2/AbstractWebGl2Renderer.d.ts +1 -1
  417. package/dist/esm/rendering/webgl2/AbstractWebGl2Renderer.js +1 -2
  418. package/dist/esm/rendering/webgl2/AbstractWebGl2Renderer.js.map +1 -1
  419. package/dist/esm/rendering/webgl2/WebGl2Backend.d.ts +11 -11
  420. package/dist/esm/rendering/webgl2/WebGl2Backend.js +19 -21
  421. package/dist/esm/rendering/webgl2/WebGl2Backend.js.map +1 -1
  422. package/dist/esm/rendering/webgl2/WebGl2MaskCompositor.d.ts +2 -2
  423. package/dist/esm/rendering/webgl2/WebGl2MaskCompositor.js +6 -8
  424. package/dist/esm/rendering/webgl2/WebGl2MaskCompositor.js.map +1 -1
  425. package/dist/esm/rendering/webgl2/WebGl2MeshRenderer.d.ts +7 -4
  426. package/dist/esm/rendering/webgl2/WebGl2MeshRenderer.js +110 -43
  427. package/dist/esm/rendering/webgl2/WebGl2MeshRenderer.js.map +1 -1
  428. package/dist/esm/rendering/webgl2/WebGl2ParticleRenderer.d.ts +10 -15
  429. package/dist/esm/rendering/webgl2/WebGl2ParticleRenderer.js +100 -79
  430. package/dist/esm/rendering/webgl2/WebGl2ParticleRenderer.js.map +1 -1
  431. package/dist/esm/rendering/webgl2/WebGl2RenderBuffer.d.ts +1 -1
  432. package/dist/esm/rendering/webgl2/WebGl2RenderBuffer.js.map +1 -1
  433. package/dist/esm/rendering/webgl2/WebGl2ShaderBlock.js.map +1 -1
  434. package/dist/esm/rendering/webgl2/WebGl2ShaderMappings.js.map +1 -1
  435. package/dist/esm/rendering/webgl2/WebGl2ShaderProgram.js +54 -22
  436. package/dist/esm/rendering/webgl2/WebGl2ShaderProgram.js.map +1 -1
  437. package/dist/esm/rendering/webgl2/WebGl2SpriteRenderer.d.ts +1 -1
  438. package/dist/esm/rendering/webgl2/WebGl2SpriteRenderer.js +11 -14
  439. package/dist/esm/rendering/webgl2/WebGl2SpriteRenderer.js.map +1 -1
  440. package/dist/esm/rendering/webgl2/WebGl2VertexArrayObject.d.ts +1 -1
  441. package/dist/esm/rendering/webgl2/WebGl2VertexArrayObject.js.map +1 -1
  442. package/dist/esm/rendering/webgl2/glsl/particle.vert.js +1 -1
  443. package/dist/esm/rendering/webgpu/AbstractWebGpuRenderer.d.ts +1 -1
  444. package/dist/esm/rendering/webgpu/AbstractWebGpuRenderer.js +1 -2
  445. package/dist/esm/rendering/webgpu/AbstractWebGpuRenderer.js.map +1 -1
  446. package/dist/esm/rendering/webgpu/WebGpuBackend.d.ts +7 -7
  447. package/dist/esm/rendering/webgpu/WebGpuBackend.js +44 -30
  448. package/dist/esm/rendering/webgpu/WebGpuBackend.js.map +1 -1
  449. package/dist/esm/rendering/webgpu/WebGpuBlendState.js.map +1 -1
  450. package/dist/esm/rendering/webgpu/WebGpuMaskCompositor.d.ts +2 -2
  451. package/dist/esm/rendering/webgpu/WebGpuMaskCompositor.js +1 -3
  452. package/dist/esm/rendering/webgpu/WebGpuMaskCompositor.js.map +1 -1
  453. package/dist/esm/rendering/webgpu/WebGpuMeshRenderer.d.ts +2 -2
  454. package/dist/esm/rendering/webgpu/WebGpuMeshRenderer.js +25 -23
  455. package/dist/esm/rendering/webgpu/WebGpuMeshRenderer.js.map +1 -1
  456. package/dist/esm/rendering/webgpu/WebGpuParticleRenderer.d.ts +10 -1
  457. package/dist/esm/rendering/webgpu/WebGpuParticleRenderer.js +204 -76
  458. package/dist/esm/rendering/webgpu/WebGpuParticleRenderer.js.map +1 -1
  459. package/dist/esm/rendering/webgpu/WebGpuSpriteRenderer.d.ts +2 -2
  460. package/dist/esm/rendering/webgpu/WebGpuSpriteRenderer.js +45 -45
  461. package/dist/esm/rendering/webgpu/WebGpuSpriteRenderer.js.map +1 -1
  462. package/dist/esm/rendering/webgpu/compute/WebGpuComputePipeline.d.ts +52 -0
  463. package/dist/esm/rendering/webgpu/compute/WebGpuStorageBuffer.d.ts +29 -0
  464. package/dist/esm/rendering/webgpu/compute/index.d.ts +3 -0
  465. package/dist/esm/resources/AbstractAssetFactory.d.ts +1 -1
  466. package/dist/esm/resources/AbstractAssetFactory.js.map +1 -1
  467. package/dist/esm/resources/AssetManifest.d.ts +1 -1
  468. package/dist/esm/resources/AssetManifest.js +2 -2
  469. package/dist/esm/resources/AssetManifest.js.map +1 -1
  470. package/dist/esm/resources/CacheFirstStrategy.d.ts +2 -2
  471. package/dist/esm/resources/CacheFirstStrategy.js.map +1 -1
  472. package/dist/esm/resources/CacheStrategy.d.ts +1 -1
  473. package/dist/esm/resources/FactoryRegistry.d.ts +1 -1
  474. package/dist/esm/resources/FactoryRegistry.js +2 -3
  475. package/dist/esm/resources/FactoryRegistry.js.map +1 -1
  476. package/dist/esm/resources/IndexedDbDatabase.d.ts +1 -1
  477. package/dist/esm/resources/IndexedDbDatabase.js +11 -14
  478. package/dist/esm/resources/IndexedDbDatabase.js.map +1 -1
  479. package/dist/esm/resources/IndexedDbStore.d.ts +1 -1
  480. package/dist/esm/resources/IndexedDbStore.js +2 -7
  481. package/dist/esm/resources/IndexedDbStore.js.map +1 -1
  482. package/dist/esm/resources/Loader.d.ts +9 -9
  483. package/dist/esm/resources/Loader.js +48 -35
  484. package/dist/esm/resources/Loader.js.map +1 -1
  485. package/dist/esm/resources/NetworkOnlyStrategy.d.ts +2 -2
  486. package/dist/esm/resources/NetworkOnlyStrategy.js.map +1 -1
  487. package/dist/esm/resources/factories/BinaryFactory.js.map +1 -1
  488. package/dist/esm/resources/factories/FontFactory.js +1 -1
  489. package/dist/esm/resources/factories/FontFactory.js.map +1 -1
  490. package/dist/esm/resources/factories/ImageFactory.js +4 -4
  491. package/dist/esm/resources/factories/ImageFactory.js.map +1 -1
  492. package/dist/esm/resources/factories/JsonFactory.d.ts +1 -1
  493. package/dist/esm/resources/factories/JsonFactory.js +1 -1
  494. package/dist/esm/resources/factories/JsonFactory.js.map +1 -1
  495. package/dist/esm/resources/factories/MusicFactory.d.ts +1 -1
  496. package/dist/esm/resources/factories/MusicFactory.js +4 -4
  497. package/dist/esm/resources/factories/MusicFactory.js.map +1 -1
  498. package/dist/esm/resources/factories/SoundFactory.d.ts +1 -1
  499. package/dist/esm/resources/factories/SoundFactory.js +3 -3
  500. package/dist/esm/resources/factories/SoundFactory.js.map +1 -1
  501. package/dist/esm/resources/factories/SvgFactory.js +3 -3
  502. package/dist/esm/resources/factories/SvgFactory.js.map +1 -1
  503. package/dist/esm/resources/factories/TextFactory.js +1 -1
  504. package/dist/esm/resources/factories/TextFactory.js.map +1 -1
  505. package/dist/esm/resources/factories/TextureFactory.d.ts +1 -1
  506. package/dist/esm/resources/factories/TextureFactory.js +3 -3
  507. package/dist/esm/resources/factories/TextureFactory.js.map +1 -1
  508. package/dist/esm/resources/factories/VideoFactory.d.ts +2 -2
  509. package/dist/esm/resources/factories/VideoFactory.js +5 -5
  510. package/dist/esm/resources/factories/VideoFactory.js.map +1 -1
  511. package/dist/esm/resources/factories/VttFactory.d.ts +2 -2
  512. package/dist/esm/resources/factories/VttFactory.js +6 -6
  513. package/dist/esm/resources/factories/VttFactory.js.map +1 -1
  514. package/dist/esm/resources/factories/WasmFactory.js.map +1 -1
  515. package/dist/esm/resources/index.d.ts +11 -11
  516. package/dist/esm/resources/utils.js +30 -25
  517. package/dist/esm/resources/utils.js.map +1 -1
  518. package/dist/exo.esm.js +26438 -23869
  519. package/dist/exo.esm.js.map +1 -1
  520. package/package.json +15 -4
  521. package/dist/esm/particles/Particle.d.ts +0 -77
  522. package/dist/esm/particles/Particle.js +0 -143
  523. package/dist/esm/particles/Particle.js.map +0 -1
  524. package/dist/esm/particles/ParticleProperties.d.ts +0 -29
  525. package/dist/esm/particles/affectors/ColorAffector.d.ts +0 -30
  526. package/dist/esm/particles/affectors/ColorAffector.js +0 -55
  527. package/dist/esm/particles/affectors/ColorAffector.js.map +0 -1
  528. package/dist/esm/particles/affectors/ForceAffector.d.ts +0 -24
  529. package/dist/esm/particles/affectors/ForceAffector.js +0 -39
  530. package/dist/esm/particles/affectors/ForceAffector.js.map +0 -1
  531. package/dist/esm/particles/affectors/ParticleAffector.d.ts +0 -19
  532. package/dist/esm/particles/affectors/ScaleAffector.d.ts +0 -23
  533. package/dist/esm/particles/affectors/ScaleAffector.js +0 -38
  534. package/dist/esm/particles/affectors/ScaleAffector.js.map +0 -1
  535. package/dist/esm/particles/affectors/TorqueAffector.d.ts +0 -23
  536. package/dist/esm/particles/affectors/TorqueAffector.js +0 -37
  537. package/dist/esm/particles/affectors/TorqueAffector.js.map +0 -1
  538. package/dist/esm/particles/emitters/ParticleEmitter.d.ts +0 -19
  539. package/dist/esm/particles/emitters/ParticleOptions.d.ts +0 -62
  540. package/dist/esm/particles/emitters/ParticleOptions.js +0 -120
  541. package/dist/esm/particles/emitters/ParticleOptions.js.map +0 -1
  542. package/dist/esm/particles/emitters/UniversalEmitter.d.ts +0 -40
  543. package/dist/esm/particles/emitters/UniversalEmitter.js +0 -68
  544. package/dist/esm/particles/emitters/UniversalEmitter.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,507 @@ 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.8.2] - 2026-05-09
8
+
9
+ ### Engine
10
+
11
+ - **`Mesh` accepts custom WebGL2 shaders.** New `MeshShaderConfig` + `MeshShaderUniformValue`
12
+ exports. Supply `shader: { vertexSource, fragmentSource, uniforms }` in `MeshOptions` to
13
+ bind a custom GLSL ES 3.00 program against the standard mesh vertex layout. Auto-bound
14
+ uniforms (`u_projection`, `u_translation`, `u_tint`, `u_texture`) are set only when the
15
+ shader declares them, so Shadertoy-style fullscreen passes can ignore them entirely.
16
+ Texture uniforms claim slots 1–7. WebGL2 only in this release; the WebGPU mesh
17
+ renderer throws a clear error pointing to the WebGL2 backend if `mesh.shader` is set.
18
+
19
+ - **Filter chain memory: ping-pong RT reuse.** `RenderNode._renderContentToTexture` now
20
+ releases the previous step's RenderTexture immediately after each `filter.apply`, so
21
+ the pool can hand the same memory back to the next step. Multi-filter chains drop from
22
+ N+1 simultaneously-allocated RTs to a steady-state of 2. ~60% RT-memory reduction on
23
+ 4-filter 1080p chains. Behaviour-identical; no public API change.
24
+
25
+ ### Site / Docs
26
+
27
+ - Part 2 "Core Concepts" guide section published (6 chapters, source-verified):
28
+ Application, Scenes, Scene lifecycle, Scene graph, Coordinates and views,
29
+ Loading and resources.
30
+ - Astro `6.3.0 → 6.3.1`, `@types/node 25.6.0 → 25.6.2` in site/.
31
+
32
+ ### Verification
33
+
34
+ - Engine: 100/100 suites, 1266/1266 tests, lint:strict 0/0, typecheck clean.
35
+ - Site: build green (488 pages), check-ts 0/0, screenshot smoke 36/36.
36
+
37
+ ## [0.8.1] - 2026-05-08
38
+
39
+ Three small additive features that close the remaining examples-driven API gaps from
40
+ the 0.8.0 audit, plus a long-overdue lint/format tooling consolidation and a 19-chapter
41
+ examples reorganisation.
42
+
43
+ ### Added
44
+
45
+ - **`Sound` spatial falloff configuration.** `DistanceModel` type (`'linear' | 'inverse'
46
+ | 'exponential'`), plus optional `distanceModel`, `refDistance`, `maxDistance`, and
47
+ `rolloffFactor` fields on `SoundOptions`. The four are also exposed as live property
48
+ setters that lazy-forward to the attached `PannerNode`. New public `Sound.audioBuffer`
49
+ getter to share one decoded buffer across multiple `Sound` instances.
50
+ - **`LutFilter`** — new colour-pipeline primitive that maps every pixel through a
51
+ Look-Up Table texture. Supports both 1D LUTs (`N×1`, indexed by red channel — palette
52
+ cycling, indexed-colour effects) and 3D LUTs (`N²×N` unwrapped cube with trilinear
53
+ slice interpolation — cinematic colour grading, tone mapping, film stock emulation,
54
+ accessibility filters). Backend selection is automatic. Static helpers
55
+ `LutFilter.identityLut1D(size)`, `LutFilter.identityLut3D(size)`,
56
+ `LutFilter.fromImage(image)` cover the standard DaVinci/OBS/Photoshop LUT-export
57
+ workflows.
58
+ - **`CompressorFilter.reduction`** — public getter forwarding the live gain reduction
59
+ in dB from the underlying `DynamicsCompressorNode`. Use as a meter source for
60
+ visualisations or sidechain triggers.
61
+
62
+ ### Examples
63
+
64
+ - Migrated `examples/public/examples/` to a 19-chapter pedagogical structure: getting
65
+ started, application & scenes, sprites & textures, tweens & animation, input, scene
66
+ graph, audio basics, spatial audio, filters, particles, text & fonts, geometry &
67
+ graphics, render targets, performance, audio FX, beat detection, debug layer, custom
68
+ renderers, showcase. Old chapter directories (`collision-detection`, `extras`,
69
+ `particle-system`, `rendering`, `webgpu`) removed.
70
+ - New examples: `spatial-audio/falloff-curves.js`, `filters/palette-cycling.js`,
71
+ `showcase/color-grading.js`. The compressor demo gained a live gain-reduction meter.
72
+
73
+ ### Tooling
74
+
75
+ - ESLint config consolidated into a single `eslint.config.ts` driven by ESLint 10 +
76
+ `typescript-eslint`'s type-aware checks plus `simple-import-sort`,
77
+ `unused-imports`, `unicorn`, and `security` plugins. `lint:strict` is the
78
+ release-gate variant, scoped to `src/**/*.ts` and run with `--max-warnings=0` (warnings
79
+ fail the build); `lint` is the broader development view across `src`, `test`, and
80
+ examples. Per-subsystem override blocks are documented as known deviations to tighten
81
+ over time.
82
+ - Tightened to error: `eqeqeq`, `no-floating-promises`, `no-base-to-string`,
83
+ `only-throw-error`, `switch-exhaustiveness-check`, `no-non-null-assertion`,
84
+ `complexity` (cap 20). Added: `no-self-compare`, `no-unreachable-loop`,
85
+ `default-case-last`, `prefer-promise-reject-errors`, `no-promise-executor-return`,
86
+ `no-unmodified-loop-condition`, plus six TypeScript and six Unicorn correctness rules.
87
+ - Prettier `printWidth: 160`, `.editorconfig` matched. Engine code reformatted to
88
+ 2-space indent.
89
+
90
+ ## [0.8.0] - 2026-05-07
91
+
92
+ Wholesale rewrite of the particle subsystem around a data-oriented core
93
+ plus a backend-agnostic auto-routing pipeline. The `Particle` class,
94
+ `ParticleAffector` interface, `ParticleEmitter` interface,
95
+ `ParticleOptions`, `UniversalEmitter`, and the four built-in affectors
96
+ (`ColorAffector`, `ForceAffector`, `ScaleAffector`, `TorqueAffector`)
97
+ are removed. They are replaced by SoA storage on the system,
98
+ `Distribution<T>`-based spawn configs, and per-batch
99
+ `SpawnModule` / `UpdateModule` / `DeathModule` interfaces.
100
+
101
+ Update modules now declare an optional `wgsl()` contribution — when
102
+ the system is constructed with a `WebGpuBackend` and every registered
103
+ update module is GPU-eligible (i.e. all built-ins, plus any custom
104
+ modules the author opts in), a composite WGSL compute shader is built
105
+ at first `update()`. Integration + every module body + pack-instances
106
+ all run in **one dispatch**, writing directly into the renderer's
107
+ instance vertex buffer. **No CPU readback** in the steady state.
108
+
109
+ On WebGL2 backends, or when any registered update module lacks
110
+ `wgsl()`, the system runs the existing CPU pipeline. The decision is
111
+ automatic and per-system; user code is unchanged across both paths.
112
+
113
+ ### Added — Struct-of-Arrays storage
114
+
115
+ `ParticleSystem` now stores particles as parallel `Float32Array` /
116
+ `Uint32Array` / `Uint16Array` channels addressed by slot index:
117
+
118
+ ```ts
119
+ system.posX[slot];
120
+ system.posY[slot];
121
+ system.velX[slot];
122
+ system.velY[slot];
123
+ system.scaleX[slot];
124
+ system.scaleY[slot];
125
+ system.rotations[slot];
126
+ system.rotationSpeeds[slot];
127
+ system.color[slot]; // packed 0xAABBGGRR
128
+ system.elapsed[slot];
129
+ system.lifetime[slot];
130
+ system.textureIndex[slot];
131
+ system.liveCount; // [0, liveCount) is the live range
132
+ ```
133
+
134
+ Capacity is fixed at construction (default 4096) — no reallocations.
135
+ The integrate pass runs as one tight loop over typed arrays with no
136
+ method calls. Expiry is handled by forward-compaction (O(n) total
137
+ instead of the previous O(n²) splice loop with scattered expirations).
138
+
139
+ ### Added — `Distribution<T>` family
140
+
141
+ Spawn-time random sampling and lifetime-parameterised evaluation:
142
+
143
+ | Type | Use |
144
+ | --------------- | ----------------------------------------------------------------------------- |
145
+ | `Constant<T>` | Always-same value |
146
+ | `Range` | Uniform random number in `[min, max]` |
147
+ | `VectorRange` | Per-axis random vector |
148
+ | `ConeDirection` | Random unit vector in a cone × speed range |
149
+ | `CircleArea` | Random point in/on a circle |
150
+ | `BoxArea` | Random point in/on an AABB |
151
+ | `LineSegment` | Random point on a segment |
152
+ | `Curve` | Piecewise-linear keyframe scalar by lifetime ratio |
153
+ | `Gradient` | Piecewise-linear keyframe color, with `evaluateRgba()` for direct u32 packing |
154
+
155
+ `Curve` and `Gradient` cache the last segment so monotonically
156
+ advancing `t` (the typical case for per-particle lifetime sampling)
157
+ is O(1) amortised.
158
+
159
+ ### Added — Module pipeline
160
+
161
+ Three module bases. Each registered on a system via the corresponding
162
+ `addX` method; each runs in its declared phase per-frame.
163
+
164
+ ```ts
165
+ abstract class SpawnModule {
166
+ apply(system, dt: number): void;
167
+ }
168
+ abstract class UpdateModule {
169
+ apply(system, dt: number): void;
170
+ }
171
+ abstract class DeathModule {
172
+ onDeath(system, slot: number): void;
173
+ }
174
+ ```
175
+
176
+ **Built-in spawn modules:**
177
+
178
+ - `RateSpawn({ rate, lifetime?, position?, velocity?, scale?, rotation?, rotationSpeed?, tint?, textureIndex? })`
179
+ — continuous emission with sub-frame accumulator. Each property is an
180
+ independent `Distribution<T>`.
181
+ - `BurstSpawn({ schedule, loop?, ...samePropsAsRate })` — discrete
182
+ bursts at scheduled times. Use for explosions, level-ups,
183
+ hit-impacts.
184
+
185
+ **Built-in update modules** (operate on the SoA arrays in tight loops):
186
+
187
+ - `ApplyForce(ax, ay)` — adds constant acceleration.
188
+ - `Drag(coefficient)` — exponential velocity damping.
189
+ - `ColorOverLifetime(gradient)` — tint sampled from a `Gradient`.
190
+ - `ScaleOverLifetime(curve)` — both axes sampled from a `Curve`.
191
+ - `RotateOverLifetime(angularAccel)` — increments `rotationSpeed`.
192
+
193
+ **Built-in death module:**
194
+
195
+ - `SpawnOnDeath(targetSystem, spawner, count?)` — sub-emitter. Forwards
196
+ the dying particle's position to a target system's spawn module.
197
+ Use for explosion-on-impact, end-of-life sparks, multi-stage VFX.
198
+
199
+ ### Added — Backend-agnostic auto-routing GPU compute pipeline
200
+
201
+ New `src/rendering/webgpu/compute/` infrastructure:
202
+
203
+ - `WebGpuStorageBuffer` — owning wrapper over a `STORAGE | COPY_DST | COPY_SRC`
204
+ buffer with `write()` and async `read()` helpers.
205
+ - `WebGpuComputePipeline` — `device.createComputePipeline` wrapper with
206
+ bind-group-layout creation, dispatch helper.
207
+
208
+ New `src/particles/gpu/ParticleGpuState` — owns the GPU-side mirror
209
+ for one `ParticleSystem`. At construction time it:
210
+
211
+ 1. Walks the registered update modules, collecting each module's
212
+ `WgslContribution` (uniform field declarations + texture bindings
213
+ - WGSL body snippet).
214
+ 2. Generates a composite WGSL compute shader: SoA storage bindings +
215
+ sim/module uniform structs + module texture bindings + a `main`
216
+ function containing integration → all module bodies in registration
217
+ order → pack-instances writing interleaved 24-byte instances into
218
+ a `STORAGE | VERTEX` buffer.
219
+ 3. Allocates 7 packed storage buffers (positions/velocities/scales/
220
+ rotInfo/timing as `vec2<f32>` arrays plus color as `u32` plus the
221
+ instance output) — fits within WebGPU's default
222
+ `maxStorageBuffersPerShaderStage = 8` limit.
223
+ 4. Allocates 1D textures for any module that declares them
224
+ (`Curve` → 256-tap r32float; `Gradient` → 256-tap rgba8unorm) and
225
+ uploads the lookup data once via `module.uploadTextures()`.
226
+ 5. Each module's `writeUniforms()` runs every frame to update its
227
+ slice of the shared module-uniform buffer.
228
+
229
+ The `WebGpuParticleRenderer` reads the GPU-written instance buffer
230
+ directly when `system.gpuMode` is true; CPU mode falls back to the
231
+ existing CPU-pack path. Same renderer, same vertex layout, no copy
232
+ between simulation and render.
233
+
234
+ `UpdateModule` gains optional `wgsl()`, `writeUniforms()`,
235
+ `uploadTextures()`. Built-in modules ship all three. Custom modules
236
+ that implement them get GPU acceleration; modules with only `apply()`
237
+ keep working but force their host system into CPU mode.
238
+
239
+ Opt-in is a single constructor option — no imperative toggle:
240
+
241
+ ```ts
242
+ const system = new ParticleSystem(texture, {
243
+ capacity: 8192,
244
+ backend: app.backend, // CPU-routed on WebGL2, GPU-routed on WebGPU
245
+ });
246
+ ```
247
+
248
+ The `backend` reference is duck-typed against `WebGpuBackend`; on
249
+ WebGL2 it's recorded but never used. The system's mode is locked in
250
+ at the first `update()` (when modules are introspected); adding update
251
+ modules after that throws.
252
+
253
+ ### Removed — Old particle API (BREAKING)
254
+
255
+ The following symbols are deleted. Migration recipes follow the table.
256
+
257
+ | Removed | Replacement |
258
+ | -------------------------------------- | ------------------------------------------------------------- |
259
+ | `Particle` (class) | SoA arrays on `ParticleSystem` (`system.posX[slot]`, etc.) |
260
+ | `ParticleProperties` (interface) | None — slot-indexed arrays replace the per-particle object |
261
+ | `ParticleEmitter` (interface) | `SpawnModule` (abstract class) |
262
+ | `ParticleOptions` | Per-property `Distribution<T>` in the spawn module's config |
263
+ | `UniversalEmitter` | `RateSpawn` |
264
+ | `ParticleAffector` (interface) | `UpdateModule` (abstract class) |
265
+ | `ColorAffector` | `ColorOverLifetime` + `Gradient` |
266
+ | `ForceAffector` | `ApplyForce` |
267
+ | `ScaleAffector` | `ScaleOverLifetime` + `Curve` |
268
+ | `TorqueAffector` | `RotateOverLifetime` |
269
+ | `system.requestParticle()` | `system.spawn(): number` (slot index, or `-1` at capacity) |
270
+ | `system.emitParticle(p)` | (gone — `spawn()` already commits the slot to the live range) |
271
+ | `system.updateParticle(p, dt)` | (gone — internal to `update()`) |
272
+ | `system.addEmitter(e)` | `system.addSpawnModule(m)` |
273
+ | `system.addAffector(a)` | `system.addUpdateModule(m)` |
274
+ | `system.particles` (`Array<Particle>`) | `system.posX` / `system.posY` / ... `system.liveCount` |
275
+ | `system.graveyard` | (gone — no graveyard; slots are recycled in place) |
276
+
277
+ ### Migration
278
+
279
+ ```ts
280
+ // Before — bonfire
281
+ const options = new ParticleOptions();
282
+ const colorAffector = new ColorAffector(new Color(194, 64, 30, 1), new Color(0, 0, 0, 0));
283
+ const emitter = new UniversalEmitter(50, options);
284
+ const system = new ParticleSystem(texture);
285
+ system.addAffector(colorAffector);
286
+ system.addEmitter(emitter);
287
+
288
+ // in update():
289
+ options.totalLifetime.copy(seconds(rand(5, 10)));
290
+ options.position.set(rand(-50, 50), rand(-10, 10));
291
+ options.velocity.set(/* ... */);
292
+
293
+ // After — bonfire
294
+ const system = new ParticleSystem(texture);
295
+ system.addSpawnModule(
296
+ new RateSpawn({
297
+ rate: new Constant(50),
298
+ lifetime: new Range(5, 10),
299
+ position: new VectorRange(-50, 50, -10, 10),
300
+ velocity: new ConeDirection(-Math.PI / 2, Math.PI / 36, 60, 80),
301
+ }),
302
+ );
303
+ system.addUpdateModule(
304
+ new ColorOverLifetime(
305
+ new Gradient([
306
+ { t: 0, color: new Color(194, 64, 30, 1) },
307
+ { t: 1, color: new Color(0, 0, 0, 0) },
308
+ ]),
309
+ ),
310
+ );
311
+ // no per-frame mutation needed.
312
+ ```
313
+
314
+ ```ts
315
+ // Before — gravity affector
316
+ const gravity = new ForceAffector(0, 980);
317
+ system.addAffector(gravity);
318
+
319
+ // After
320
+ system.addUpdateModule(new ApplyForce(0, 980));
321
+ ```
322
+
323
+ ```ts
324
+ // Before — custom affector
325
+ class AlphaFade {
326
+ apply(particle, delta) {
327
+ particle.tint.a = particle.remainingRatio;
328
+ return this;
329
+ }
330
+ destroy() {}
331
+ }
332
+
333
+ // After
334
+ class AlphaFadeOverLifetime extends UpdateModule {
335
+ apply(system) {
336
+ const { color, elapsed, lifetime, liveCount } = system;
337
+ for (let i = 0; i < liveCount; i++) {
338
+ const remaining = 1 - elapsed[i] / lifetime[i];
339
+ const a = (Math.max(0, Math.min(1, remaining)) * 255) & 255;
340
+ color[i] = (color[i] & 0x00ffffff) | (a << 24);
341
+ }
342
+ }
343
+ }
344
+ ```
345
+
346
+ ```ts
347
+ // Before — direct particle creation in tests
348
+ const particle = system.requestParticle();
349
+ particle.position.set(10, 12);
350
+ particle.tint = Color.red;
351
+ system.emitParticle(particle);
352
+
353
+ // After — direct slot manipulation
354
+ const slot = system.spawn();
355
+ system.posX[slot] = 10;
356
+ system.posY[slot] = 12;
357
+ system.color[slot] = Color.red.toRgba();
358
+ system.lifetime[slot] = 1;
359
+ system.scaleX[slot] = 1;
360
+ system.scaleY[slot] = 1;
361
+ ```
362
+
363
+ ### Changed — `ParticleSystem` constructor: typed overloads (BREAKING)
364
+
365
+ Source material (texture / atlas frames / spritesheet) lives in
366
+ **positional arguments** — TypeScript overload signatures enforce mutual
367
+ exclusivity at compile time so you can't pass nonsense combinations like
368
+ texture-and-spritesheet-at-once. Capacity and the test-only `device`
369
+ escape hatch live in the trailing options object.
370
+
371
+ ```ts
372
+ // 0.7.x:
373
+ new ParticleSystem(texture);
374
+ new ParticleSystem(texture, 4096);
375
+
376
+ // 0.8.0:
377
+ new ParticleSystem(); // untextured (1×1 white), CPU/GPU auto-routed
378
+ new ParticleSystem(spark); // simple textured particles
379
+ new ParticleSystem(spark, { capacity: 8192 }); // explicit capacity
380
+ new ParticleSystem(atlas, [r0, r1, r2]); // multi-frame atlas
381
+ new ParticleSystem(atlas, frames, { capacity: 8192 }); // atlas + capacity
382
+ new ParticleSystem(sheet); // spritesheet shorthand
383
+ new ParticleSystem(sheet, { capacity: 4096 });
384
+ ```
385
+
386
+ The four overload signatures:
387
+
388
+ ```ts
389
+ constructor(options?: ParticleSystemOptions);
390
+ constructor(texture: Texture, options?: ParticleSystemOptions);
391
+ constructor(texture: Texture, frames: ReadonlyArray<Rectangle>, options?: ParticleSystemOptions);
392
+ constructor(spritesheet: Spritesheet, options?: ParticleSystemOptions);
393
+ ```
394
+
395
+ Compile-time errors for illegal combinations:
396
+
397
+ ```ts
398
+ new ParticleSystem(spark, sheet); // ✗ no overload matches
399
+ new ParticleSystem(sheet, frames); // ✗ frames only valid with Texture
400
+ new ParticleSystem({ frames }); // ✗ frames isn't an option
401
+ ```
402
+
403
+ **No `backend` option** — the renderer auto-discovers the active backend
404
+ on the first `render(backend)` call. WebGPU → GPU compute path, WebGL2 →
405
+ CPU path. Re-discovery on backend change (device-loss recovery).
406
+
407
+ ### Added — Optional texture + 1×1 white default
408
+
409
+ When `texture` is omitted, the system uses a lazily-allocated 1×1
410
+ opaque-white singleton. Particles render as solid color quads driven by
411
+ the per-particle `color` channel. Useful for tech-demo magic effects,
412
+ abstract VFX, performance benchmarks.
413
+
414
+ ### Added — Multi-frame atlas via `frames` / `spritesheet` options
415
+
416
+ `frames: ReadonlyArray<Rectangle>` declares per-particle frame
417
+ rectangles within the atlas texture. Each particle's `textureIndex[i]`
418
+ selects which frame to render — `RateSpawn` /
419
+ `BurstSpawn`'s `textureIndex: Distribution<number>` becomes the per-spawn
420
+ frame chooser:
421
+
422
+ ```ts
423
+ const system = new ParticleSystem({
424
+ texture: explosionAtlas,
425
+ frames: [
426
+ new Rectangle(0, 0, 32, 32), // index 0 — flame core
427
+ new Rectangle(32, 0, 32, 32), // index 1 — smoke ring
428
+ new Rectangle(64, 0, 32, 32), // index 2 — ember
429
+ ],
430
+ });
431
+
432
+ system.addSpawnModule(
433
+ new BurstSpawn({
434
+ schedule: [{ time: 0, count: 60 }],
435
+ velocity: ConeDirection.omni(120, 280),
436
+ textureIndex: new Range(0, 2), // each spawn picks a random frame
437
+ }),
438
+ );
439
+ ```
440
+
441
+ `Spritesheet` integration via `spritesheet: sheet` extracts texture +
442
+ frames in insertion order — convenient for atlas authors who already
443
+ have a sheet from a TexturePacker / Aseprite export.
444
+
445
+ UV resolution happens once per particle per frame (CPU pack in CPU mode,
446
+ compute shader in GPU mode); the renderer reads pre-resolved UVs from
447
+ the instance buffer — no shader-side frame-array lookup overhead.
448
+
449
+ ### Changed — Per-instance vertex layout: 24 → 40 bytes
450
+
451
+ The renderer's per-instance buffer now carries `uvMin: vec2` and
452
+ `uvMax: vec2` alongside the existing translation/scale/rotation/color
453
+ fields. Lets a single batch render any mix of atlas frames per instance
454
+ without indirection through a uniform array. Net cost: +67% bandwidth
455
+ on the instance buffer (still trivial — ~10 MB/s at 60 fps with 16k
456
+ particles).
457
+
458
+ The previous design used a single `u_uvBounds` uniform that pinned
459
+ every particle in a system to the same frame; the new layout is what
460
+ makes per-particle atlas selection free.
461
+
462
+ The system pre-allocates all SoA arrays at construction. Spawn modules
463
+ that want to emit beyond capacity get `-1` from `spawn()` and should
464
+ bail cleanly (the built-ins do).
465
+
466
+ ### Changed — slot allocation differs between CPU and GPU mode
467
+
468
+ In CPU mode, `[0, liveCount)` is dense (forward-compaction at end of
469
+ update). `spawn()` always returns the next sequential slot.
470
+
471
+ In GPU mode, no compaction happens — readback would be required to
472
+ move slots whose authoritative position lives in GPU memory. Instead:
473
+
474
+ - Each particle has an `alive: Uint8Array` flag (1 = alive, 0 = dead).
475
+ - `spawn()` finds the first dead slot via a round-robin hint pointer
476
+ (amortised O(1), worst case O(capacity)).
477
+ - Expiry on CPU: `alive[i] = 0`, `lifetime[i] = -1` (sentinel).
478
+ - The compute shader skips dead slots (`timing[idx].y < 0.0` → write
479
+ zero-scale instance and return).
480
+
481
+ Custom modules iterating `[0, liveCount)` should check `system.alive[i]`
482
+ in GPU mode if they care about ignoring dead slots; mutating dead slot
483
+ data is harmless because the GPU shader skips them.
484
+
485
+ ### Added — `system.aliveCount`
486
+
487
+ Returns the actual count of alive particles (slots with `alive[i] === 1`).
488
+ In CPU mode this equals `liveCount`; in GPU mode it's `≤ liveCount`.
489
+ Use for fragmentation diagnostics or UI counters.
490
+
491
+ ### Performance notes
492
+
493
+ - Spawning + integrating + ColorOverLifetime/ScaleOverLifetime + drag
494
+ on 10k particles: previously ~5 ms CPU per frame; new SoA path on
495
+ CPU: ~0.5 ms (~10× speedup from eliminating per-particle object
496
+ indirection). New GPU path on WebGPU: ~0.05 ms (~100× speedup from
497
+ the previous OO baseline) — bound by the per-frame upload, not the
498
+ compute itself.
499
+ - The crossover where GPU beats CPU sits around 1-3 k particles
500
+ depending on hardware. For sub-1k systems CPU is still slightly
501
+ faster (upload overhead dominates); the auto-router doesn't second-
502
+ guess this — opt out via `backend: undefined` if you want to force
503
+ CPU at low counts.
504
+ - 100k+ particles render and simulate cleanly on WebGPU at 60 fps in
505
+ CI smoke tests; the bottleneck shifts from compute to texture
506
+ bandwidth at that scale.
507
+
7
508
  ## [0.7.13] - 2026-05-07
8
509
 
9
510
  Major gamepad-input refactor. Replaces the `new Input(...)` +
@@ -19,7 +520,7 @@ signed stick channels, and Joy-Con-honest mappings.
19
520
  ```ts
20
521
  // Per inputManager (manual unbind):
21
522
  app.input.onTrigger(GamepadButton.South, () => player.jump());
22
- app.input.onActive(GamepadAxis.LeftStickX, (v) => player.x += v * 5);
523
+ app.input.onActive(GamepadAxis.LeftStickX, v => (player.x += v * 5));
23
524
  app.input.onStart([Keyboard.Space, GamepadButton.South], () => fire());
24
525
 
25
526
  // Per gamepad (slot-aware, listener survives disconnect/reconnect):
@@ -63,8 +564,8 @@ default `'sticky'` (each pad keeps its slot through disconnects).
63
564
  disconnect (good for hot-seat couch coop where "the first N pads are
64
565
  the N players" is the desired semantic).
65
566
 
66
- In compact mode, the disconnect signal fires on the slot that *ended
67
- up* empty after the shift (not the slot the disconnected hardware
567
+ In compact mode, the disconnect signal fires on the slot that _ended
568
+ up_ empty after the shift (not the slot the disconnected hardware
68
569
  originally occupied), keeping `pad.connected === false` consistent with
69
570
  the fired event. Slots that received a different physical pad through
70
571
  the shift dispatch a separate signal:
@@ -78,6 +579,7 @@ player when slots renumber.
78
579
  ### Added — Generic signals
79
580
 
80
581
  Per-pad:
582
+
81
583
  - `pad.onConnect: Signal<[]>`
82
584
  - `pad.onDisconnect: Signal<[]>`
83
585
  - `pad.onButtonDown: Signal<[GamepadButton, number]>`
@@ -85,6 +587,7 @@ Per-pad:
85
587
  - `pad.onAxisChange: Signal<[GamepadAxis, number]>`
86
588
 
87
589
  Aggregate across all pads:
590
+
88
591
  - `inputManager.onAnyGamepadButtonDown: Signal<[Gamepad, GamepadButton, number]>`
89
592
  - `inputManager.onAnyGamepadButtonUp: Signal<[Gamepad, GamepadButton, number]>`
90
593
  - `inputManager.onAnyGamepadAxisChange: Signal<[Gamepad, GamepadAxis, number]>`
@@ -93,7 +596,7 @@ Aggregate across all pads:
93
596
 
94
597
  ```ts
95
598
  if (pad.canVibrate) {
96
- await pad.vibrate({ duration: 200, weakMagnitude: 0.5, strongMagnitude: 1.0 });
599
+ await pad.vibrate({ duration: 200, weakMagnitude: 0.5, strongMagnitude: 1.0 });
97
600
  }
98
601
  pad.stopVibration();
99
602
  ```
@@ -114,18 +617,18 @@ remain available for buttons-style 0..1 input.
114
617
 
115
618
  ```ts
116
619
  // Stick-style — one binding per axis, signed value:
117
- this.inputs.onActive(GamepadAxis.LeftStickX, (x) => player.x += x * 5);
620
+ this.inputs.onActive(GamepadAxis.LeftStickX, x => (player.x += x * 5));
118
621
 
119
622
  // Buttons-style — separate bindings per direction, 0..1 each:
120
- this.inputs.onActive(GamepadAxis.LeftStickLeft, (v) => player.x -= v * 5);
121
- this.inputs.onActive(GamepadAxis.LeftStickRight, (v) => player.x += v * 5);
623
+ this.inputs.onActive(GamepadAxis.LeftStickLeft, v => (player.x -= v * 5));
624
+ this.inputs.onActive(GamepadAxis.LeftStickRight, v => (player.x += v * 5));
122
625
  ```
123
626
 
124
627
  ### Added — `pad.hasChannel(channel)` capability check
125
628
 
126
629
  ```ts
127
630
  if (pad.hasChannel(GamepadAxis.RightStickX)) {
128
- pad.onActive(GamepadAxis.RightStickX, (v) => crosshair.x += v * 8);
631
+ pad.onActive(GamepadAxis.RightStickX, v => (crosshair.x += v * 8));
129
632
  }
130
633
  ```
131
634
 
@@ -142,19 +645,19 @@ Internally tracks each binding and calls `.unbind()` in `Scene.destroy`.
142
645
  ### Added — Steam Deck / Steam Virtual Gamepad / Valve fallback
143
646
 
144
647
  New `SteamDeckGamepadMapping` covers the raw HID layout reported by the
145
- Steam Deck (and likely future Valve hardware) when Steam Input is *not*
648
+ Steam Deck (and likely future Valve hardware) when Steam Input is _not_
146
649
  intercepting the device. Indices follow the SDL_GameControllerDB Linux
147
650
  entry: face buttons at 3-6, D-pad at 16-19, paddles at 20-23, triggers
148
651
  as analog axes 8/9.
149
652
 
150
653
  Routing rules added to `builtInGamepadDefinitions`:
151
654
 
152
- | Browser ID | Mapping |
153
- |---|---|
154
- | `28de:1102`, `28de:1142` | `SteamControllerGamepadMapping` (existing, original Steam Controller raw) |
155
- | `28de:11ff` (Steam Virtual Gamepad — any controller via Steam Input) | `GenericDualAnalogGamepadMapping` (W3C standard Xbox emulation) |
156
- | `28de:1205` | `SteamDeckGamepadMapping` (raw Steam Deck) |
157
- | Vendor `28de` (anything else from Valve, e.g. future Steam Controller 2 raw) | `SteamDeckGamepadMapping` (best-effort fallback) |
655
+ | Browser ID | Mapping |
656
+ | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
657
+ | `28de:1102`, `28de:1142` | `SteamControllerGamepadMapping` (existing, original Steam Controller raw) |
658
+ | `28de:11ff` (Steam Virtual Gamepad — any controller via Steam Input) | `GenericDualAnalogGamepadMapping` (W3C standard Xbox emulation) |
659
+ | `28de:1205` | `SteamDeckGamepadMapping` (raw Steam Deck) |
660
+ | Vendor `28de` (anything else from Valve, e.g. future Steam Controller 2 raw) | `SteamDeckGamepadMapping` (best-effort fallback) |
158
661
 
159
662
  Enum: `GamepadMappingFamily.SteamDeck` added.
160
663
 
@@ -207,7 +710,7 @@ app.input.getGamepad(0);
207
710
  ### Fixed — Compact-mode disconnect ordering
208
711
 
209
712
  In `'compact'` slot strategy, `onDisconnect` previously fired on the
210
- slot the disconnected hardware originally occupied — *before* the
713
+ slot the disconnected hardware originally occupied — _before_ the
211
714
  compaction shift moved a different physical pad into that slot. User
212
715
  code observing the event would see `pad.connected === true` because
213
716
  the slot had been silently re-bound by the shift. Now compaction is
@@ -219,13 +722,13 @@ ended up empty (the trailing slot). Sticky behaviour is unchanged.
219
722
  The unified `GamepadChannel` enum is split into two disjoint enums for
220
723
  nominal type safety:
221
724
 
222
- | Old | New (user-facing) | New (internal type) |
223
- |---|---|---|
224
- | `GamepadChannel.ButtonSouth` | `GamepadButton.South` | `GamepadButtonChannel.South` |
225
- | `GamepadChannel.ButtonEast` | `GamepadButton.East` | `GamepadButtonChannel.East` |
226
- | `GamepadChannel.LeftShoulder` | `GamepadButton.LeftShoulder` | `GamepadButtonChannel.LeftShoulder` |
227
- | `GamepadChannel.LeftStickLeft` | `GamepadAxis.LeftStickLeft` | `GamepadAxisChannel.LeftStickLeft` |
228
- | ... | ... | ... |
725
+ | Old | New (user-facing) | New (internal type) |
726
+ | ------------------------------ | ---------------------------- | ----------------------------------- |
727
+ | `GamepadChannel.ButtonSouth` | `GamepadButton.South` | `GamepadButtonChannel.South` |
728
+ | `GamepadChannel.ButtonEast` | `GamepadButton.East` | `GamepadButtonChannel.East` |
729
+ | `GamepadChannel.LeftShoulder` | `GamepadButton.LeftShoulder` | `GamepadButtonChannel.LeftShoulder` |
730
+ | `GamepadChannel.LeftStickLeft` | `GamepadAxis.LeftStickLeft` | `GamepadAxisChannel.LeftStickLeft` |
731
+ | ... | ... | ... |
229
732
 
230
733
  User code references the namespace mirrors (`GamepadButton.X`,
231
734
  `GamepadAxis.Y`) — same `Pointer.X` / `Keyboard.Space` convention. Type
@@ -267,11 +770,11 @@ instead of firing every frame.
267
770
 
268
771
  ```ts
269
772
  // Before:
270
- new Gamepad(index, channels, mapping)
271
- new Gamepad(browserGamepad, channels, definition)
773
+ new Gamepad(index, channels, mapping);
774
+ new Gamepad(browserGamepad, channels, definition);
272
775
 
273
776
  // After (engine-internal — InputManager handles slot allocation):
274
- new Gamepad(slot, channels)
777
+ new Gamepad(slot, channels);
275
778
  // followed by pad._bind(browserGamepad, definition) on connect
276
779
  ```
277
780
 
@@ -293,7 +796,7 @@ import { GamepadButton, Keyboard } from '@codexo/exojs';
293
796
 
294
797
  // Manual lifecycle
295
798
  const binding = app.input.onTrigger(GamepadButton.South, () => player.jump());
296
- binding.unbind(); // when done
799
+ binding.unbind(); // when done
297
800
 
298
801
  // Auto-disposed on scene unload
299
802
  this.inputs.onTrigger(GamepadButton.South, () => player.jump());
@@ -304,37 +807,31 @@ this.app.input.gamepads[0].onTrigger(GamepadButton.South, () => player.jump());
304
807
 
305
808
  ```ts
306
809
  // Stick movement — before:
307
- const moveLeft = new Input(GamepadChannel.LeftStickLeft);
810
+ const moveLeft = new Input(GamepadChannel.LeftStickLeft);
308
811
  const moveRight = new Input(GamepadChannel.LeftStickRight);
309
812
  app.input.add(moveLeft);
310
813
  app.input.add(moveRight);
311
814
  // per frame: const x = moveRight.value - moveLeft.value;
312
815
 
313
816
  // After (signed aggregate channel):
314
- this.inputs.onActive(GamepadAxis.LeftStickX, (x) => player.x += x * 5);
817
+ this.inputs.onActive(GamepadAxis.LeftStickX, x => (player.x += x * 5));
315
818
  ```
316
819
 
317
820
  ```ts
318
821
  // Custom mapping — before:
319
822
  import { GamepadMapping, GamepadChannel } from '@codexo/exojs';
320
823
  const buttons = GamepadMapping.createControls([
321
- [0, GamepadChannel.ButtonSouth],
322
- [1, GamepadChannel.ButtonEast],
824
+ [0, GamepadChannel.ButtonSouth],
825
+ [1, GamepadChannel.ButtonEast],
323
826
  ]);
324
827
 
325
828
  // After:
326
829
  import { GamepadButton, GamepadMapping, GamepadMappingFamily } from '@codexo/exojs';
327
830
  class MyMapping extends GamepadMapping {
328
- public readonly family = GamepadMappingFamily.GenericDualAnalog;
329
- public constructor() {
330
- super(
331
- [
332
- new GamepadButton(0, GamepadButton.South),
333
- new GamepadButton(1, GamepadButton.East),
334
- ],
335
- [],
336
- );
337
- }
831
+ public readonly family = GamepadMappingFamily.GenericDualAnalog;
832
+ public constructor() {
833
+ super([new GamepadButton(0, GamepadButton.South), new GamepadButton(1, GamepadButton.East)], []);
834
+ }
338
835
  }
339
836
  ```
340
837
 
@@ -460,7 +957,7 @@ indexing is now automatic and persistent).
460
957
  audio, collision, scene-graph, interaction. Each domain has its own
461
958
  script (`npm run perf:bench:rendering`, `:audio`, `:collision`,
462
959
  `:scene-graph`, `:interaction`) plus `:all` aggregator. Output: JSON
463
- + Markdown to `test/perf/results/`.
960
+ - Markdown to `test/perf/results/`.
464
961
  - **Baseline snapshot** committed as `test/perf/results/baseline.md` —
465
962
  reference numbers at 0.7.10 for future regression detection.
466
963
  - **Auto-profiler** (`npm run perf:profile`, `:gc` variant with
@@ -500,6 +997,7 @@ also makes the `useSpatialIndex` opt-in flag unnecessary and **the
500
997
  flag has been removed entirely**.
501
998
 
502
999
  **How it works now:**
1000
+
503
1001
  - A persistent quadtree is created lazily when the first interactive
504
1002
  node enters the scene.
505
1003
  - `Container.addChild` / `removeChild` walk subtrees and add/remove
@@ -540,7 +1038,7 @@ actually-moved nodes.
540
1038
 
541
1039
  ```ts
542
1040
  // Before:
543
- app.interaction.useSpatialIndex = true; // flag opt-in
1041
+ app.interaction.useSpatialIndex = true; // flag opt-in
544
1042
 
545
1043
  // After:
546
1044
  // Nothing — index is automatic. Just have at least one interactive
@@ -643,7 +1141,7 @@ Fixes a GLSL compile-error in the 0.7.8 shader auto-upgrade path.
643
1141
  ### Fixed
644
1142
 
645
1143
  - **`upgradeFragmentShaderToGl300()` now always prepends `precision highp
646
- float;`** before the `out vec4 fragColor;` declaration. Previously, if
1144
+ float;`** before the `out vec4 fragColor;` declaration. Previously, if
647
1145
  the user's source already contained a precision declaration anywhere
648
1146
  (e.g., `precision lowp float;` mid-source), the upgrader skipped its
649
1147
  own injection — but the user's declaration came AFTER the
@@ -654,7 +1152,7 @@ Fixes a GLSL compile-error in the 0.7.8 shader auto-upgrade path.
654
1152
 
655
1153
  Multiple precision declarations are legal in GLSL ES 3.00 with
656
1154
  last-precision-wins semantics. The fix always injects `precision highp
657
- float;` at line 2 (before `out vec4 fragColor;`); the user's own
1155
+ float;` at line 2 (before `out vec4 fragColor;`); the user's own
658
1156
  precision declaration further down still applies to their code via
659
1157
  the standard last-precision-wins rule. No semantic change for
660
1158
  user-provided shader logic; previously-broken shaders with custom
@@ -853,7 +1351,7 @@ app.backend.setCursor('pointer');
853
1351
  const cursor = app.backend.cursor;
854
1352
 
855
1353
  // After:
856
- app.setCursor('pointer'); // or
1354
+ app.setCursor('pointer'); // or
857
1355
  app.cursor = 'pointer';
858
1356
  const cursor = app.cursor;
859
1357
  ```
@@ -861,14 +1359,14 @@ const cursor = app.cursor;
861
1359
  ```ts
862
1360
  // New: react to backend loss
863
1361
  app.onBackendLost.add(() => {
864
- showReloadDialog();
1362
+ showReloadDialog();
865
1363
  });
866
1364
 
867
1365
  // Or backend-specific:
868
1366
  if (app.backend.backendType === RenderBackendType.WebGpu) {
869
- (app.backend as WebGpuBackend).onDeviceLost.add((info) => {
870
- console.error('GPU device lost:', info.message, info.reason);
871
- });
1367
+ (app.backend as WebGpuBackend).onDeviceLost.add(info => {
1368
+ console.error('GPU device lost:', info.message, info.reason);
1369
+ });
872
1370
  }
873
1371
  ```
874
1372
 
@@ -977,9 +1475,9 @@ etc.).
977
1475
  separate `WebGpuShaderFilter`.
978
1476
  - **Backend guard messages updated**:
979
1477
  - `WebGl2ShaderFilter` on WebGPU: `'WebGl2ShaderFilter requires the
980
- WebGL2 backend. Use WebGpuShaderFilter on WebGPU.'`
1478
+ WebGL2 backend. Use WebGpuShaderFilter on WebGPU.'`
981
1479
  - `WebGpuShaderFilter` on WebGL2: `'WebGpuShaderFilter requires the
982
- WebGPU backend. Use WebGl2ShaderFilter on WebGL2.'`
1480
+ WebGPU backend. Use WebGl2ShaderFilter on WebGL2.'`
983
1481
 
984
1482
  `ShaderFilterUniformValue` (the polymorphic uniform value type) is
985
1483
  **unchanged** and shared between both backends — same value shapes
@@ -1040,7 +1538,7 @@ LUT color grading, chromatic aberration, etc.
1040
1538
  via property assignment; flushed before each apply():
1041
1539
  ```ts
1042
1540
  filter.uniforms.uTime = performance.now() / 1000;
1043
- filter.uniforms.uColor = [1, 0.5, 0, 1]; // vec4
1541
+ filter.uniforms.uColor = [1, 0.5, 0, 1]; // vec4
1044
1542
  ```
1045
1543
  - **Polymorphic uniform values**: scalar `number`, tuple `[a, b]` /
1046
1544
  `[a, b, c]` / `[a, b, c, d]`, `Float32Array` / `Int32Array`, or
@@ -1055,8 +1553,8 @@ LUT color grading, chromatic aberration, etc.
1055
1553
 
1056
1554
  - **WebGL2-only in V1.** Constructor accepts `wgsl` source, but `apply()`
1057
1555
  on the WebGPU backend throws `'ShaderFilter does not yet support the
1058
- WebGPU backend. WGSL support is planned for a future release. Use the
1059
- WebGL2 backend for now.'` Document this limitation; reasoning: WebGPU
1556
+ WebGPU backend. WGSL support is planned for a future release. Use the
1557
+ WebGL2 backend for now.'` Document this limitation; reasoning: WebGPU
1060
1558
  requires a separate WGSL pipeline implementation that's substantial
1061
1559
  on its own. Coming when there's concrete user demand.
1062
1560
  - `fragmentSource` is required at construction. Constructor throws if
@@ -1073,7 +1571,7 @@ LUT color grading, chromatic aberration, etc.
1073
1571
  import { ShaderFilter } from '@codexo/exojs';
1074
1572
 
1075
1573
  const filter = new ShaderFilter({
1076
- fragmentSource: `#version 300 es
1574
+ fragmentSource: `#version 300 es
1077
1575
  precision highp float;
1078
1576
  in vec2 vUv;
1079
1577
  uniform sampler2D uTexture;
@@ -1086,15 +1584,15 @@ const filter = new ShaderFilter({
1086
1584
  outColor = texture(uTexture, uv);
1087
1585
  }
1088
1586
  `,
1089
- uniforms: {
1090
- uTime: 0,
1091
- },
1587
+ uniforms: {
1588
+ uTime: 0,
1589
+ },
1092
1590
  });
1093
1591
 
1094
1592
  sprite.filters = [filter];
1095
1593
 
1096
- app.onFrame.add((delta) => {
1097
- filter.uniforms.uTime = performance.now() / 1000;
1594
+ app.onFrame.add(delta => {
1595
+ filter.uniforms.uTime = performance.now() / 1000;
1098
1596
  });
1099
1597
  ```
1100
1598
 
@@ -1180,10 +1678,10 @@ const spectrum = analyser.getSpectrum();
1180
1678
  const waveform = analyser.getWaveform();
1181
1679
 
1182
1680
  // Now also possible:
1183
- analyser.source = mediaStream; // Mic input
1681
+ analyser.source = mediaStream; // Mic input
1184
1682
  analyser.source = app.audio.master; // Whole mix
1185
- analyser.getBandEnergy(20, 200); // Bass energy 0..1
1186
- analyser.getLowMidHigh(); // {low, mid, high}
1683
+ analyser.getBandEnergy(20, 200); // Bass energy 0..1
1684
+ analyser.getLowMidHigh(); // {low, mid, high}
1187
1685
  ```
1188
1686
 
1189
1687
  ```ts
@@ -1193,12 +1691,12 @@ detector.source = music;
1193
1691
  await detector.ready;
1194
1692
 
1195
1693
  detector.onBeat.add(({ audioTime, tempo, isDownbeat, energy }) => {
1196
- sprite.scale.set(1.5);
1197
- new Tween().target(sprite.scale).to({x: 1, y: 1}).duration(200).start();
1694
+ sprite.scale.set(1.5);
1695
+ new Tween().target(sprite.scale).to({ x: 1, y: 1 }).duration(200).start();
1198
1696
  });
1199
1697
 
1200
1698
  detector.onDownbeat.add(() => {
1201
- boss.attack(); // syncs exactly to "the 1" of each bar
1699
+ boss.attack(); // syncs exactly to "the 1" of each bar
1202
1700
  });
1203
1701
  ```
1204
1702
 
@@ -1367,22 +1865,22 @@ breaking change.
1367
1865
 
1368
1866
  ```ts
1369
1867
  // Before:
1370
- sound.play(); // singleton — second call replaces first
1371
- sound.playPooled(); // multi-instance — concurrent plays
1868
+ sound.play(); // singleton — second call replaces first
1869
+ sound.playPooled(); // multi-instance — concurrent plays
1372
1870
 
1373
1871
  // After:
1374
- sound.play(); // multi-instance — concurrent plays (default!)
1872
+ sound.play(); // multi-instance — concurrent plays (default!)
1375
1873
  sound.play({ replace: true }); // singleton — equivalent of old play()
1376
1874
  ```
1377
1875
 
1378
1876
  ```ts
1379
1877
  // Before — direct destination routing was implicit:
1380
1878
  const sound = new Sound(buffer);
1381
- sound.play(); // → audioContext.destination
1879
+ sound.play(); // → audioContext.destination
1382
1880
 
1383
1881
  // After — routes through the soundBus by default:
1384
1882
  const sound = new Sound(buffer);
1385
- sound.play(); // → app.audio.sound → app.audio.master → destination
1883
+ sound.play(); // → app.audio.sound → app.audio.master → destination
1386
1884
 
1387
1885
  // Override to a custom bus:
1388
1886
  const dialogueBus = new AudioBus('dialogue', { parent: app.audio.master });
@@ -1393,8 +1891,8 @@ sound.bus = dialogueBus;
1393
1891
  ```ts
1394
1892
  // Spatial audio:
1395
1893
  const explosion = new Sound(buffer);
1396
- explosion.position = { x: 200, y: 100 }; // becomes spatial
1397
- app.audio.listener.target = playerSprite; // ears follow player
1894
+ explosion.position = { x: 200, y: 100 }; // becomes spatial
1895
+ app.audio.listener.target = playerSprite; // ears follow player
1398
1896
 
1399
1897
  explosion.play();
1400
1898
  // → routes through equalpower panner with distance falloff
@@ -1431,7 +1929,7 @@ infrastructure. Pure additive — no behavior changes for existing code.
1431
1929
  **world-space position** via `getGlobalTransform()`, so following a
1432
1930
  Sprite nested under a translated/rotated Container works correctly.
1433
1931
  New exported type `ViewFollowTarget = SceneNode | { x: number; y:
1434
- number } | null`.
1932
+ number } | null`.
1435
1933
  - **Audio fade helpers on `AbstractMedia`** — both `Sound` and `Music`
1436
1934
  inherit:
1437
1935
  - `fadeIn(durationMs): this` — ramps gain from 0 to current volume.
@@ -1498,7 +1996,7 @@ detection. Pure performance change — no public API surface changes.
1498
1996
  components. Recomputes only when the sprite's transform or local
1499
1997
  bounds change. Previously had a `// todo cache this` comment.
1500
1998
  - **`Sprite.getNormals()`** returns a stable `[Vector, Vector,
1501
- Vector, Vector]` array. The four `Vector` instances are reused
1999
+ Vector, Vector]` array. The four `Vector` instances are reused
1502
2000
  across calls; previously each call allocated four new `Vector`s.
1503
2001
  Recomputes only when vertices change. Reduces GC pressure in
1504
2002
  collision-detection hot paths.
@@ -1580,7 +2078,7 @@ per-frame application hook.
1580
2078
 
1581
2079
  - **`Application.debug` removed** — was added in 0.6.15. Apps that
1582
2080
  used `app.debug.show()` must migrate to `import { DebugOverlay }
1583
- from '@codexo/exojs/debug'` and instantiate manually. **Breaking
2081
+ from '@codexo/exojs/debug'` and instantiate manually. **Breaking
1584
2082
  change**, but the affected window is one day (0.6.15 → 0.6.17).
1585
2083
 
1586
2084
  ### Notes
@@ -1677,7 +2175,7 @@ zero cost when not shown.
1677
2175
 
1678
2176
  Reshapes the interaction system around a per-frame tick and adds an
1679
2177
  opt-in drag-and-drop helper. The public per-node signal API from 0.6.13
1680
- is unchanged; only event *cadence* and a new `draggable` flag.
2178
+ is unchanged; only event _cadence_ and a new `draggable` flag.
1681
2179
 
1682
2180
  ### Added
1683
2181
 
@@ -1813,12 +2311,13 @@ existing surface changes shape.
1813
2311
  properties on any target object:
1814
2312
 
1815
2313
  ```ts
1816
- app.tweens.create(sprite)
1817
- .to({ x: 100, alpha: 0.5 }, 1.0) // 1 second
1818
- .easing(Ease.cubicOut)
1819
- .delay(0.2)
1820
- .onComplete(() => console.log('done'))
1821
- .start();
2314
+ app.tweens
2315
+ .create(sprite)
2316
+ .to({ x: 100, alpha: 0.5 }, 1.0) // 1 second
2317
+ .easing(Ease.cubicOut)
2318
+ .delay(0.2)
2319
+ .onComplete(() => console.log('done'))
2320
+ .start();
1822
2321
  ```
1823
2322
 
1824
2323
  Lifecycle: `Idle → Active → Complete | Stopped` (with
@@ -1830,6 +2329,7 @@ existing surface changes shape.
1830
2329
  interpolation), `onUpdate` (per frame), `onRepeat` (cycle
1831
2330
  boundaries), `onComplete` (final cycle ends naturally).
1832
2331
  `stop()` does NOT fire `onComplete`.
2332
+
1833
2333
  - **`TweenManager` class.** Owns active tweens and ticks them
1834
2334
  from `Application.update()`. Use `app.tweens.create(target)` to
1835
2335
  spawn-and-register a tween in one call; `app.tweens.add(tween)`
@@ -1925,7 +2425,7 @@ one atlas — memory-efficient at scale, single drawcall per Text.
1925
2425
 
1926
2426
  - **`DynamicGlyphAtlas`** — public class. Constructor takes
1927
2427
  `width = 1024, height = 1024`. Has `getGlyph(char, family, size,
1928
- weight, style) → GlyphInfo` (cached or rasterizes), `clear()` to
2428
+ weight, style) → GlyphInfo` (cached or rasterizes), `clear()` to
1929
2429
  reset, and `texture` for binding to a Mesh. Internal shelf
1930
2430
  bin-packing; throws on atlas-full (LRU eviction is V2).
1931
2431
  - **`layoutText(text, style, atlas)`** — pure function. Returns
@@ -1937,7 +2437,7 @@ one atlas — memory-efficient at scale, single drawcall per Text.
1937
2437
  their own atlas / layout pipelines.
1938
2438
  - **TextStyle gets `fillColor: Color`** (defaults to white, used
1939
2439
  via mesh.tint after glyph rasterization), **`fontStyle: 'normal'
1940
- | 'italic'`**, and **`lineHeight: number`** (multiplied by
2440
+ | 'italic'`**, and **`lineHeight: number`** (multiplied by
1941
2441
  fontSize for line spacing, defaults to 1.2). `align` field is
1942
2442
  now strongly typed as `TextAlignment`.
1943
2443
 
@@ -1967,8 +2467,8 @@ one atlas — memory-efficient at scale, single drawcall per Text.
1967
2467
  `document.createElement('canvas')` (works in jsdom / older
1968
2468
  browsers).
1969
2469
  - First-render of a never-seen glyph costs one canvas2d round-trip
1970
- + texture re-upload. Cached glyphs are zero-cost on subsequent
1971
- renders.
2470
+ - texture re-upload. Cached glyphs are zero-cost on subsequent
2471
+ renders.
1972
2472
  - Per-character animation, MSDF rendering, word-wrap, BiDi, and
1973
2473
  text outlines / drop-shadows are all V2.
1974
2474
 
@@ -2137,7 +2637,7 @@ one unified rendering path for everything triangle-shaped.
2137
2637
  (`buildLine`, `buildPath`, `buildCircle`, `buildEllipse`,
2138
2638
  `buildRectangle`, `buildPolygon`, `buildStar`) now return a
2139
2639
  `MeshGeometryData` plain object — `{ vertices: Float32Array,
2140
- indices: Uint16Array, points: Array<number> }` — directly suitable
2640
+ indices: Uint16Array, points: Array<number> }` — directly suitable
2141
2641
  for `new Mesh({ ... })`.
2142
2642
  - **`WebGl2PrimitiveRenderer` and `WebGpuPrimitiveRenderer` removed.**
2143
2643
  Their work moved entirely into the existing `*MeshRenderer`s. Both
@@ -2166,18 +2666,14 @@ one unified rendering path for everything triangle-shaped.
2166
2666
  // Before (0.6.4)
2167
2667
  import { DrawableShape, Geometry, RenderingPrimitives, Color } from '@codexo/exojs';
2168
2668
 
2169
- const shape = new DrawableShape(
2170
- new Geometry({ vertices: [0, 0, 100, 0, 50, 100], indices: [0, 1, 2] }),
2171
- Color.red,
2172
- RenderingPrimitives.Triangles,
2173
- );
2669
+ const shape = new DrawableShape(new Geometry({ vertices: [0, 0, 100, 0, 50, 100], indices: [0, 1, 2] }), Color.red, RenderingPrimitives.Triangles);
2174
2670
 
2175
2671
  // After (0.6.5)
2176
2672
  import { Mesh, Color } from '@codexo/exojs';
2177
2673
 
2178
2674
  const mesh = new Mesh({
2179
- vertices: new Float32Array([0, 0, 100, 0, 50, 100]),
2180
- indices: new Uint16Array([0, 1, 2]),
2675
+ vertices: new Float32Array([0, 0, 100, 0, 50, 100]),
2676
+ indices: new Uint16Array([0, 1, 2]),
2181
2677
  });
2182
2678
  mesh.tint = Color.red;
2183
2679
  ```
@@ -2253,13 +2749,13 @@ match the rest of ExoJS.
2253
2749
  ```ts
2254
2750
  // Before (0.6.3)
2255
2751
  import { capabilities, isSupported } from '@codexo/exojs';
2256
- if (capabilities.webgpu) startWebGpu(); // false positives possible
2752
+ if (capabilities.webgpu) startWebGpu(); // false positives possible
2257
2753
  if (isSupported('touch')) showTouchUi();
2258
2754
 
2259
2755
  // After (0.6.4)
2260
2756
  import { Capabilities } from '@codexo/exojs';
2261
2757
  const caps = await Capabilities.ready;
2262
- if (caps.webgpuAdapter) startWebGpu(); // strict adapter check
2758
+ if (caps.webgpuAdapter) startWebGpu(); // strict adapter check
2263
2759
  if (caps.touch) showTouchUi();
2264
2760
 
2265
2761
  // Or via Application after start:
@@ -2379,12 +2875,12 @@ and breaks freely between minors.
2379
2875
  - **`Scene` is class-only; the plain-object definition constructor is
2380
2876
  gone.** `new Scene({ update() { ... } })` no longer works. Subclass
2381
2877
  to define a scene — `class GameScene extends Scene { override
2382
- update(...) { ... } }` for named scenes, `new class extends Scene
2383
- { ... }` for one-offs. The `SceneData` interface and
2878
+ update(...) { ... } }` for named scenes, `new class extends Scene
2879
+ { ... }` for one-offs. The `SceneData` interface and
2384
2880
  `SceneInstance<T>` type alias are removed (they only existed to
2385
2881
  type the spread-into-`this` constructor). Internal Scene fields
2386
2882
  move from ECMAScript `#`-private to TS `protected _app/_root/
2387
- _stackMode/_inputMode` — subclasses can now reach internal state
2883
+ _stackMode/_inputMode` — subclasses can now reach internal state
2388
2884
  directly when they need to.
2389
2885
  - **npm package shape simplified.** Dropped: `dist/exo.global.js` /
2390
2886
  `dist/exo.global.min.js` (legacy IIFE for `<script>` use) and
@@ -2445,33 +2941,49 @@ and breaks freely between minors.
2445
2941
  ```ts
2446
2942
  // Before (0.5.x)
2447
2943
  class GameScene extends Scene {
2448
- override draw(runtime: SceneRenderRuntime): void {
2449
- this.root.render(runtime);
2450
- }
2944
+ override draw(runtime: SceneRenderRuntime): void {
2945
+ this.root.render(runtime);
2946
+ }
2451
2947
  }
2452
2948
 
2453
2949
  const triangleRenderer = new CustomRenderer(app.renderManager);
2454
2950
 
2455
- if (app.renderManager instanceof WebGpuRenderManager) { /* ... */ }
2951
+ if (app.renderManager instanceof WebGpuRenderManager) {
2952
+ /* ... */
2953
+ }
2456
2954
 
2457
2955
  // Plain-object scene
2458
- app.start(new Scene({ update() { /* ... */ } }));
2956
+ app.start(
2957
+ new Scene({
2958
+ update() {
2959
+ /* ... */
2960
+ },
2961
+ }),
2962
+ );
2459
2963
  ```
2460
2964
 
2461
2965
  ```ts
2462
2966
  // After (0.6.0)
2463
2967
  class GameScene extends Scene {
2464
- override draw(backend: RenderBackend): void {
2465
- this.root.render(backend);
2466
- }
2968
+ override draw(backend: RenderBackend): void {
2969
+ this.root.render(backend);
2970
+ }
2467
2971
  }
2468
2972
 
2469
2973
  const triangleRenderer = new CustomRenderer(app.backend);
2470
2974
 
2471
- if (app.backend instanceof WebGpuBackend) { /* ... */ }
2975
+ if (app.backend instanceof WebGpuBackend) {
2976
+ /* ... */
2977
+ }
2472
2978
 
2473
2979
  // Anonymous-subclass scene (or named subclass)
2474
- app.start(new class extends Scene { override update() { /* ... */ } });
2980
+ app.start(
2981
+ new (class extends Scene {
2982
+ override update() {
2983
+ /* ... */
2984
+ }
2985
+ })(),
2986
+ );
2475
2987
  ```
2476
2988
 
2477
2989
  ## [0.5.1] - 2026-04-28
@@ -2565,6 +3077,7 @@ Three focused breaking changes targeted at the first pre-1.0 minor: a hierarchy-
2565
3077
  - `null` — no mask.
2566
3078
 
2567
3079
  Setting `node.mask = node` (self-mask) throws at runtime.
3080
+
2568
3081
  - **`SceneRenderRuntime` mask primitives renamed** to match the new vocabulary:
2569
3082
  - `pushMask(maskBounds)` / `popMask()` → `pushScissorRect(bounds)` / `popScissorRect()` (lower-level scissor primitive used internally by the `Rectangle` mask path).
2570
3083
  - New `composeWithAlphaMask(content, mask, x, y, width, height, blendMode)` — used internally by the Texture/RenderTexture/RenderNode mask paths.
@@ -2582,19 +3095,19 @@ Three focused breaking changes targeted at the first pre-1.0 minor: a hierarchy-
2582
3095
 
2583
3096
  ### Migration
2584
3097
 
2585
- | Before (0.4.x) | After |
2586
- |---|---|
2587
- | `import { Transformable } from '@codexo/exojs'`; `class X extends Transformable` | `import { SceneNode } from '@codexo/exojs'`; `class X extends SceneNode` |
2588
- | `import { TransformableFlags } from '@codexo/exojs'` | Internal flag enum is no longer public; use SceneNode's high-level transform accessors instead. |
2589
- | `node.mask = anyShapeNode` *(silently clipped to bounding rect)* | `node.mask = anyShapeNode` *(now a real shape mask via alpha compositing — except bare SceneNode which is rejected at compile time)* |
2590
- | Want fast axis-aligned clipping? | `node.mask = new Rectangle(x, y, w, h)` |
2591
- | Want to clip with a texture's alpha channel? | `node.mask = texture` or `node.mask = renderTexture` |
2592
- | Want a transformed/positioned alpha mask? | `node.mask = new Sprite(texture)` (Sprite's transform/position/scale apply to the mask source) |
2593
- | `runtime.pushMask(rect)` / `runtime.popMask()` | `runtime.pushScissorRect(rect)` / `runtime.popScissorRect()` (renamed; behavior unchanged) |
2594
- | `class Group extends SceneNode { override render() {...} }` | `class Group extends RenderNode { override render() {...} }` |
2595
- | `class CustomContainer extends Container { override addChild(child: SceneNode) {...} }` | `class CustomContainer extends Container { override addChild(child: RenderNode) {...} }` |
2596
- | `Scene.create({ update() {...} })` | `new Scene({ update() {...} })` (drop-in replacement; same `this` typing via `ThisType<Scene & T>`) |
2597
- | `Scene.create({})` | `new Scene()` |
3098
+ | Before (0.4.x) | After |
3099
+ | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
3100
+ | `import { Transformable } from '@codexo/exojs'`; `class X extends Transformable` | `import { SceneNode } from '@codexo/exojs'`; `class X extends SceneNode` |
3101
+ | `import { TransformableFlags } from '@codexo/exojs'` | Internal flag enum is no longer public; use SceneNode's high-level transform accessors instead. |
3102
+ | `node.mask = anyShapeNode` _(silently clipped to bounding rect)_ | `node.mask = anyShapeNode` _(now a real shape mask via alpha compositing — except bare SceneNode which is rejected at compile time)_ |
3103
+ | Want fast axis-aligned clipping? | `node.mask = new Rectangle(x, y, w, h)` |
3104
+ | Want to clip with a texture's alpha channel? | `node.mask = texture` or `node.mask = renderTexture` |
3105
+ | Want a transformed/positioned alpha mask? | `node.mask = new Sprite(texture)` (Sprite's transform/position/scale apply to the mask source) |
3106
+ | `runtime.pushMask(rect)` / `runtime.popMask()` | `runtime.pushScissorRect(rect)` / `runtime.popScissorRect()` (renamed; behavior unchanged) |
3107
+ | `class Group extends SceneNode { override render() {...} }` | `class Group extends RenderNode { override render() {...} }` |
3108
+ | `class CustomContainer extends Container { override addChild(child: SceneNode) {...} }` | `class CustomContainer extends Container { override addChild(child: RenderNode) {...} }` |
3109
+ | `Scene.create({ update() {...} })` | `new Scene({ update() {...} })` (drop-in replacement; same `this` typing via `ThisType<Scene & T>`) |
3110
+ | `Scene.create({})` | `new Scene()` |
2598
3111
 
2599
3112
  No deprecated aliases are provided. The migration is mechanical and the project is pre-1.0 with explicit "may break between minors" policy.
2600
3113