melonjs 20.7.0 → 20.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (243) hide show
  1. package/README.md +6 -3
  2. package/build/application/defaultApplicationSettings.d.ts +5 -0
  3. package/build/application/defaultApplicationSettings.d.ts.map +1 -1
  4. package/build/application/header.d.ts.map +1 -1
  5. package/build/application/settings.d.ts +88 -0
  6. package/build/application/settings.d.ts.map +1 -1
  7. package/build/audio/backend/engine.d.ts +0 -1
  8. package/build/audio/backend/sound.d.ts +0 -1
  9. package/build/camera/camera2d.d.ts.map +1 -1
  10. package/build/camera/camera3d.d.ts +95 -8
  11. package/build/camera/camera3d.d.ts.map +1 -1
  12. package/build/geometries/box3d.d.ts +24 -6
  13. package/build/geometries/box3d.d.ts.map +1 -1
  14. package/build/geometries/ellipse.d.ts +14 -0
  15. package/build/geometries/ellipse.d.ts.map +1 -1
  16. package/build/geometries/polygon.d.ts +14 -0
  17. package/build/geometries/polygon.d.ts.map +1 -1
  18. package/build/geometries/sphere.d.ts +35 -6
  19. package/build/geometries/sphere.d.ts.map +1 -1
  20. package/build/index.d.ts +9 -2
  21. package/build/index.d.ts.map +1 -1
  22. package/build/index.js +2770 -538
  23. package/build/index.js.map +4 -4
  24. package/build/input/pointerevent.d.ts +8 -0
  25. package/build/input/pointerevent.d.ts.map +1 -1
  26. package/build/level/gltf/GLTFModel.d.ts +17 -31
  27. package/build/level/gltf/GLTFModel.d.ts.map +1 -1
  28. package/build/level/gltf/GLTFScene.d.ts +40 -1
  29. package/build/level/gltf/GLTFScene.d.ts.map +1 -1
  30. package/build/level/level.d.ts +35 -0
  31. package/build/level/level.d.ts.map +1 -1
  32. package/build/level/tiled/TMXLayer.d.ts +5 -2
  33. package/build/level/tiled/TMXLayer.d.ts.map +1 -1
  34. package/build/level/tiled/TMXObject.d.ts +0 -1
  35. package/build/level/tiled/TMXTile.d.ts +2 -1
  36. package/build/level/tiled/TMXTile.d.ts.map +1 -1
  37. package/build/level/tiled/TMXTileMap.d.ts +7 -4
  38. package/build/level/tiled/TMXTileMap.d.ts.map +1 -1
  39. package/build/level/tiled/TMXTileset.d.ts +1 -1
  40. package/build/level/tiled/TMXTileset.d.ts.map +1 -1
  41. package/build/level/tiled/TMXTilesetGroup.d.ts +5 -0
  42. package/build/level/tiled/TMXTilesetGroup.d.ts.map +1 -1
  43. package/build/level/tiled/factories/shape.d.ts +0 -1
  44. package/build/level/tiled/renderer/TMXHexagonalRenderer.d.ts +6 -5
  45. package/build/level/tiled/renderer/TMXHexagonalRenderer.d.ts.map +1 -1
  46. package/build/level/tiled/renderer/TMXIsometricRenderer.d.ts +5 -0
  47. package/build/level/tiled/renderer/TMXIsometricRenderer.d.ts.map +1 -1
  48. package/build/level/tiled/renderer/TMXObliqueRenderer.d.ts +8 -7
  49. package/build/level/tiled/renderer/TMXObliqueRenderer.d.ts.map +1 -1
  50. package/build/level/tiled/renderer/TMXOrthogonalRenderer.d.ts +5 -0
  51. package/build/level/tiled/renderer/TMXOrthogonalRenderer.d.ts.map +1 -1
  52. package/build/level/tiled/renderer/TMXRenderer.d.ts +19 -4
  53. package/build/level/tiled/renderer/TMXRenderer.d.ts.map +1 -1
  54. package/build/level/tiled/renderer/autodetect.d.ts +0 -4
  55. package/build/level/tiled/renderer/autodetect.d.ts.map +1 -1
  56. package/build/loader/loader.d.ts +9 -0
  57. package/build/loader/loader.d.ts.map +1 -1
  58. package/build/loader/loadingscreen.d.ts +1 -2
  59. package/build/loader/loadingscreen.d.ts.map +1 -1
  60. package/build/loader/parsers/shader.d.ts +0 -2
  61. package/build/math/color.d.ts +27 -4
  62. package/build/math/color.d.ts.map +1 -1
  63. package/build/particles/emitter.d.ts.map +1 -1
  64. package/build/particles/particle.d.ts +22 -1
  65. package/build/particles/particle.d.ts.map +1 -1
  66. package/build/particles/settings.d.ts +69 -0
  67. package/build/particles/settings.d.ts.map +1 -1
  68. package/build/physics/adapter.d.ts +9 -1
  69. package/build/physics/adapter.d.ts.map +1 -1
  70. package/build/physics/builtin/body.d.ts +3 -2
  71. package/build/physics/builtin/body.d.ts.map +1 -1
  72. package/build/physics/builtin/builtin-adapter.d.ts.map +1 -1
  73. package/build/physics/builtin/detector.d.ts +0 -1
  74. package/build/physics/builtin/detector.d.ts.map +1 -1
  75. package/build/physics/builtin/sat.d.ts +0 -7
  76. package/build/physics/builtin/sat3d.d.ts +46 -0
  77. package/build/physics/builtin/sat3d.d.ts.map +1 -1
  78. package/build/physics/response.d.ts +45 -62
  79. package/build/physics/response.d.ts.map +1 -1
  80. package/build/renderable/collectable.d.ts +6 -1
  81. package/build/renderable/collectable.d.ts.map +1 -1
  82. package/build/renderable/colorlayer.d.ts +4 -4
  83. package/build/renderable/colorlayer.d.ts.map +1 -1
  84. package/build/renderable/container.d.ts +23 -17
  85. package/build/renderable/container.d.ts.map +1 -1
  86. package/build/renderable/entity/entity.d.ts +12 -16
  87. package/build/renderable/entity/entity.d.ts.map +1 -1
  88. package/build/renderable/frameAnimation.d.ts +1 -1
  89. package/build/renderable/groundshadow.d.ts +1 -0
  90. package/build/renderable/groundshadow.d.ts.map +1 -1
  91. package/build/renderable/imagelayer.d.ts +12 -5
  92. package/build/renderable/imagelayer.d.ts.map +1 -1
  93. package/build/renderable/instanced_mesh.d.ts +2 -9
  94. package/build/renderable/instanced_mesh.d.ts.map +1 -1
  95. package/build/renderable/mesh.d.ts +283 -4
  96. package/build/renderable/mesh.d.ts.map +1 -1
  97. package/build/renderable/nineslicesprite.d.ts +3 -13
  98. package/build/renderable/nineslicesprite.d.ts.map +1 -1
  99. package/build/renderable/renderable.d.ts +90 -27
  100. package/build/renderable/renderable.d.ts.map +1 -1
  101. package/build/renderable/sprite.d.ts +5 -19
  102. package/build/renderable/sprite.d.ts.map +1 -1
  103. package/build/renderable/sprite3d.d.ts +27 -16
  104. package/build/renderable/sprite3d.d.ts.map +1 -1
  105. package/build/renderable/text/bitmaptext.d.ts +13 -3
  106. package/build/renderable/text/bitmaptext.d.ts.map +1 -1
  107. package/build/renderable/text/bitmaptextdata.d.ts +0 -4
  108. package/build/renderable/text/bitmaptextdata.d.ts.map +1 -1
  109. package/build/renderable/text/text.d.ts +5 -3
  110. package/build/renderable/text/text.d.ts.map +1 -1
  111. package/build/renderable/text/textmetrics.d.ts +2 -0
  112. package/build/renderable/text/textmetrics.d.ts.map +1 -1
  113. package/build/renderable/trail.d.ts +0 -1
  114. package/build/renderable/trail.d.ts.map +1 -1
  115. package/build/renderable/trigger.d.ts +27 -5
  116. package/build/renderable/trigger.d.ts.map +1 -1
  117. package/build/renderable/ui/progressbar.d.ts +194 -0
  118. package/build/renderable/ui/progressbar.d.ts.map +1 -0
  119. package/build/renderable/ui/uibaseelement.d.ts +48 -3
  120. package/build/renderable/ui/uibaseelement.d.ts.map +1 -1
  121. package/build/renderable/ui/uispriteelement.d.ts +19 -3
  122. package/build/renderable/ui/uispriteelement.d.ts.map +1 -1
  123. package/build/system/legacy_pool.d.ts +2 -2
  124. package/build/system/legacy_pool.d.ts.map +1 -1
  125. package/build/system/pool.d.ts +10 -0
  126. package/build/system/pool.d.ts.map +1 -1
  127. package/build/tweens/tween.d.ts +49 -2
  128. package/build/tweens/tween.d.ts.map +1 -1
  129. package/build/video/canvas/canvas_renderer.d.ts +34 -23
  130. package/build/video/canvas/canvas_renderer.d.ts.map +1 -1
  131. package/build/video/effects/blendEffect.d.ts +0 -1
  132. package/build/video/effects/bloom.d.ts +72 -0
  133. package/build/video/effects/bloom.d.ts.map +1 -0
  134. package/build/video/effects/blur.d.ts +3 -2
  135. package/build/video/effects/blur.d.ts.map +1 -1
  136. package/build/video/effects/chromaticAberration.d.ts +3 -2
  137. package/build/video/effects/chromaticAberration.d.ts.map +1 -1
  138. package/build/video/effects/colorMatrix.d.ts +3 -2
  139. package/build/video/effects/colorMatrix.d.ts.map +1 -1
  140. package/build/video/effects/desaturate.d.ts +10 -2
  141. package/build/video/effects/desaturate.d.ts.map +1 -1
  142. package/build/video/effects/dissolve.d.ts +3 -2
  143. package/build/video/effects/dissolve.d.ts.map +1 -1
  144. package/build/video/effects/dropShadow.d.ts +3 -2
  145. package/build/video/effects/dropShadow.d.ts.map +1 -1
  146. package/build/video/effects/flash.d.ts +3 -2
  147. package/build/video/effects/flash.d.ts.map +1 -1
  148. package/build/video/effects/glow.d.ts +3 -2
  149. package/build/video/effects/glow.d.ts.map +1 -1
  150. package/build/video/effects/hologram.d.ts +3 -2
  151. package/build/video/effects/hologram.d.ts.map +1 -1
  152. package/build/video/effects/invert.d.ts +10 -2
  153. package/build/video/effects/invert.d.ts.map +1 -1
  154. package/build/video/effects/outline.d.ts +3 -2
  155. package/build/video/effects/outline.d.ts.map +1 -1
  156. package/build/video/effects/pixelate.d.ts +3 -2
  157. package/build/video/effects/pixelate.d.ts.map +1 -1
  158. package/build/video/effects/radialGradient.d.ts +8 -3
  159. package/build/video/effects/radialGradient.d.ts.map +1 -1
  160. package/build/video/effects/scanline.d.ts +3 -2
  161. package/build/video/effects/scanline.d.ts.map +1 -1
  162. package/build/video/effects/sepia.d.ts +10 -2
  163. package/build/video/effects/sepia.d.ts.map +1 -1
  164. package/build/video/effects/shadereffect.d.ts +10 -4
  165. package/build/video/effects/shadereffect.d.ts.map +1 -1
  166. package/build/video/effects/shine.d.ts +3 -2
  167. package/build/video/effects/shine.d.ts.map +1 -1
  168. package/build/video/effects/tintPulse.d.ts +3 -2
  169. package/build/video/effects/tintPulse.d.ts.map +1 -1
  170. package/build/video/effects/tonemap.d.ts +121 -0
  171. package/build/video/effects/tonemap.d.ts.map +1 -0
  172. package/build/video/effects/vignette.d.ts +3 -2
  173. package/build/video/effects/vignette.d.ts.map +1 -1
  174. package/build/video/effects/wave.d.ts +3 -2
  175. package/build/video/effects/wave.d.ts.map +1 -1
  176. package/build/video/gpu/vertexformat.d.ts +26 -0
  177. package/build/video/gpu/vertexformat.d.ts.map +1 -1
  178. package/build/video/gradient.d.ts +0 -1
  179. package/build/video/gradient.d.ts.map +1 -1
  180. package/build/video/renderer.d.ts +111 -7
  181. package/build/video/renderer.d.ts.map +1 -1
  182. package/build/video/renderstate.d.ts +2 -0
  183. package/build/video/renderstate.d.ts.map +1 -1
  184. package/build/video/rendertarget/canvasrendertarget.d.ts +3 -4
  185. package/build/video/rendertarget/canvasrendertarget.d.ts.map +1 -1
  186. package/build/video/rendertarget/render_target_pool.d.ts +0 -1
  187. package/build/video/rendertarget/webglrendertarget.d.ts +0 -1
  188. package/build/video/rendertarget/webglrendertarget.d.ts.map +1 -1
  189. package/build/video/rendertarget/webgpurendertarget.d.ts +18 -2
  190. package/build/video/rendertarget/webgpurendertarget.d.ts.map +1 -1
  191. package/build/video/texture/atlas.d.ts +1 -0
  192. package/build/video/texture/atlas.d.ts.map +1 -1
  193. package/build/video/texture/cache.d.ts +1 -3
  194. package/build/video/texture/frametexture.d.ts +0 -1
  195. package/build/video/texture/frametexture.d.ts.map +1 -1
  196. package/build/video/texture/noise_texture2d.d.ts +1 -0
  197. package/build/video/texture/noise_texture2d.d.ts.map +1 -1
  198. package/build/video/utils/autodetect.d.ts +0 -2
  199. package/build/video/webgl/batchers/batcher.d.ts +0 -9
  200. package/build/video/webgl/batchers/batcher.d.ts.map +1 -1
  201. package/build/video/webgl/batchers/lit_quad_batcher.d.ts +11 -0
  202. package/build/video/webgl/batchers/lit_quad_batcher.d.ts.map +1 -1
  203. package/build/video/webgl/batchers/material_batcher.d.ts +4 -2
  204. package/build/video/webgl/batchers/material_batcher.d.ts.map +1 -1
  205. package/build/video/webgl/batchers/mesh_batcher.d.ts +0 -3
  206. package/build/video/webgl/batchers/mesh_batcher.d.ts.map +1 -1
  207. package/build/video/webgl/batchers/quad_batcher.d.ts +4 -0
  208. package/build/video/webgl/batchers/quad_batcher.d.ts.map +1 -1
  209. package/build/video/webgl/buffer/index.d.ts +0 -1
  210. package/build/video/webgl/buffer/retained_geometry.d.ts +0 -1
  211. package/build/video/webgl/glshader.d.ts +5 -3
  212. package/build/video/webgl/glshader.d.ts.map +1 -1
  213. package/build/video/webgl/renderers/tmxlayer/orthogonal.d.ts +0 -4
  214. package/build/video/webgl/texture/store.d.ts +0 -1
  215. package/build/video/webgl/webgl_renderer.d.ts +63 -24
  216. package/build/video/webgl/webgl_renderer.d.ts.map +1 -1
  217. package/build/video/webgpu/batchers/mesh_batcher.d.ts +0 -3
  218. package/build/video/webgpu/batchers/mesh_batcher.d.ts.map +1 -1
  219. package/build/video/webgpu/batchers/quad_batcher.d.ts +0 -2
  220. package/build/video/webgpu/batchers/quad_batcher.d.ts.map +1 -1
  221. package/build/video/webgpu/batchers/webgpu_batcher.d.ts +0 -2
  222. package/build/video/webgpu/batchers/webgpu_batcher.d.ts.map +1 -1
  223. package/build/video/webgpu/pipeline/cache.d.ts.map +1 -1
  224. package/build/video/webgpu/renderers/tmxlayer/orthogonal.d.ts +0 -2
  225. package/build/video/webgpu/texture/frametexture.d.ts +0 -2
  226. package/build/video/webgpu/texture/frametexture.d.ts.map +1 -1
  227. package/build/video/webgpu/texture/store.d.ts +0 -1
  228. package/build/video/webgpu/webgpu_renderer.d.ts +7 -75
  229. package/build/video/webgpu/webgpu_renderer.d.ts.map +1 -1
  230. package/package.json +1 -1
  231. package/skills/melonjs-3d/SKILL.md +277 -19
  232. package/skills/melonjs-3d-assets/SKILL.md +76 -9
  233. package/skills/melonjs-camera-and-drawing/SKILL.md +57 -1
  234. package/skills/melonjs-effects-and-shaders/SKILL.md +341 -29
  235. package/skills/melonjs-input/SKILL.md +48 -4
  236. package/skills/melonjs-particles-and-trails/SKILL.md +106 -4
  237. package/skills/melonjs-performance/SKILL.md +79 -2
  238. package/skills/melonjs-physics/SKILL.md +101 -27
  239. package/skills/melonjs-renderables/SKILL.md +2 -1
  240. package/skills/melonjs-scenes-and-state/SKILL.md +121 -1
  241. package/skills/melonjs-ui-and-text/SKILL.md +165 -4
  242. package/build/level/gltf/srgb.d.ts +0 -20
  243. package/build/level/gltf/srgb.d.ts.map +0 -1
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: melonjs-performance
3
- description: "Use this skill when a melonJS game drops frames, allocates heavily, or needs to scale to many objects. Covers object pooling, draw-call batching, atlases, baking with CanvasRenderTarget, instancing, culling and alwaysUpdate, the debug plugin, and where the real costs are. Triggers on: performance, slow, frame rate, fps, lag, pool, pool.register, pool.pull, batching, draw calls, InstancedMesh, culling, alwaysUpdate, inViewport, CanvasRenderTarget, optimise, profiling, debug plugin, memory."
3
+ description: "Use this skill when a melonJS game drops frames, allocates heavily, or needs to scale to many objects. Covers object pooling, draw-call batching, atlases, baking with CanvasRenderTarget, instancing, culling and alwaysUpdate, the debug plugin, and where the real costs are. Triggers on: performance, slow, frame rate, fps, lag, pool, pool.register, pool.pull, batching, draw calls, InstancedMesh, culling, alwaysUpdate, inViewport, CanvasRenderTarget, optimise, profiling, debug plugin, memory, render targets, hdr."
4
4
  license: MIT
5
5
  ---
6
6
 
@@ -81,6 +81,23 @@ Two rules that cause subtle bugs when missed:
81
81
  successfully recycled object never sees it. Pair event subscriptions with
82
82
  `onActivateEvent` / `onDeactivateEvent` instead, or you leak handlers.
83
83
 
84
+ - **`removeChild` is DEFERRED, which breaks a pool you re-lend in the same
85
+ frame.** The removal is queued and runs after the update and draw stack has
86
+ unwound, so an object you remove and immediately hand out again is torn out
87
+ from under its new owner when the queue drains: it vanishes, and the frame
88
+ after that the removal throws `Child is not mine.` Take it out immediately
89
+ instead, keeping the instance alive for reuse:
90
+
91
+ ```js
92
+ world.removeChildNow(bullet, true); // keepalive: do NOT destroy it
93
+ this.freeList.push(bullet);
94
+ ```
95
+
96
+ `removeChildNow(child, keepalive)` with `keepalive` true skips the destroy,
97
+ which is what you want for anything you own the lifetime of. This is the
98
+ usual cause of a pooled effect that flickers out one frame after it is
99
+ recycled.
100
+
84
101
  Engine classes are poolable too: `pool.pull("Tween", target)`. The canonical
85
102
  names are unprefixed — `Entity`, `Collectable`, `Trigger`, `Light2d`,
86
103
  `Particle`, `Sprite`, `NineSliceSprite`, `Renderable`, `Text`, `BitmapText`,
@@ -95,13 +112,34 @@ name in new code; do not "correct" an `me.`-prefixed one, it is not broken.
95
112
  ## `scale()` is multiplicative
96
113
 
97
114
  A pooled sprite arrives carrying its previous life's transform, and calling
98
- `scale()` each frame compounds. For absolute scaling:
115
+ `scale()` each frame compounds. `rotate()` is the same. For absolute values,
116
+ reset first:
99
117
 
100
118
  ```js
101
119
  sprite.currentTransform.identity();
102
120
  sprite.currentTransform.scale(s, s, 1);
103
121
  ```
104
122
 
123
+ **Order matters, and it is the opposite of what reads naturally.**
124
+ `Matrix2d.scale` multiplies the matrix ROWS, so it applies AFTER whatever the
125
+ matrix already holds. Scale first and then rotate; rotate first and the scale
126
+ lands in the rotated frame and SHEARS anything non-uniform:
127
+
128
+ ```js
129
+ // correct: a long thin quad, rotated
130
+ t.identity();
131
+ t.scale(length / texW, width / texH);
132
+ t.rotate(angle);
133
+
134
+ // wrong: the same quad arrives as a parallelogram
135
+ t.identity();
136
+ t.rotate(angle);
137
+ t.scale(length / texW, width / texH);
138
+ ```
139
+
140
+ The symptom is a sprite that looks right at 0 and 90 degrees and skews in
141
+ between, which reads as a bad texture rather than as a transform bug.
142
+
105
143
  ## Culling and update cost
106
144
 
107
145
  - `Container` recomputes `inViewport` for every child each frame against every
@@ -231,6 +269,45 @@ SwiftShader, where the absolute numbers are wildly pessimistic and the JS
231
269
  profile shows the time as `(program)`, outside JS entirely. Ratios transfer;
232
270
  milliseconds do not.
233
271
 
272
+ ## Render targets, and what `hdr` costs
273
+
274
+ A render target is a canvas-sized GPU surface. The engine allocates them
275
+ lazily and only where a multi-pass chain actually needs one, which is worth
276
+ knowing before assuming an effect is expensive:
277
+
278
+ - a renderable with **no** post effects: none
279
+ - a renderable with **exactly one** post effect: still none. It takes the
280
+ `customShader` fast path and draws straight into whatever target is already
281
+ bound
282
+ - a **camera** with effects: a pair (one capture, one ping-pong intermediate)
283
+ - a sprite with **two or more** effects: its own pair
284
+
285
+ So the first lever is the count, not the cost of any one effect: going from
286
+ one effect to two on a sprite is what allocates, not going from a cheap shader
287
+ to an expensive one.
288
+
289
+ `hdr: true` changes the format of that camera pair from 8 bits per channel to
290
+ half-float, which is **8 bytes per pixel instead of 4**. At 1920×1080 that is
291
+ about 8 MB per target instead of 4 MB, so roughly +8 MB for the pair. With
292
+ `antiAlias` on, the capture half is also multisampled and its multisampled
293
+ surface takes the same format, so that part doubles too.
294
+
295
+ Nothing else moves. Sprite chains and the advanced-blend target stay 8-bit
296
+ whatever `hdr` says, for a correctness reason rather than a thrifty one: their
297
+ final blit composites with `ONE_MINUS_SRC_ALPHA`, and additive blending
298
+ accumulates alpha past 1 in a float target, which would turn that factor
299
+ negative and subtract the backdrop.
300
+
301
+ **With `hdr` off, nothing changes at all** — every target keeps the 8-bit
302
+ attachment it has always had, and a game that never adds a camera post-effect
303
+ allocates no render targets in the first place. The setting costs nothing
304
+ until something is actually using it.
305
+
306
+ ```js
307
+ // what you actually got, which is not always what you asked for
308
+ if (app.renderer.supportsHDR === false) { /* grade for a chain that clamps */ }
309
+ ```
310
+
234
311
  ## Where the costs actually are
235
312
 
236
313
  In rough order for a typical 2D game:
@@ -258,7 +258,8 @@ built-in-only spelling of this and are deprecated since 20.7.0.
258
258
  | `RoundRect` | exact | exact (decomposed) | sampled down to 12 vertices |
259
259
  | `Ellipse` | exact | circle of the average radius | circle of the average radius |
260
260
  | `Line` | exact | thin quad along the segment | thin quad along the segment |
261
- | `Point`, `Box3d`, `Sphere` | supported | **throws** | **throws** |
261
+ | `Box3d`, `Sphere` | exact, and in 3D | **throws** | **throws** |
262
+ | `Point` | supported | **throws** | **throws** |
262
263
 
263
264
  Two of those are worth knowing before you author geometry. Box2D caps a
264
265
  polygon's vertex count and says nothing when you go over: measured, a
@@ -329,6 +330,29 @@ body's collision shapes are measured from that same frame, so a shape of
329
330
  a shape's own `pos` still offsets it inside that frame (a small hitbox at the
330
331
  feet, a hurtbox at the head).
331
332
 
333
+ **That frame's origin is its TOP-LEFT corner, which is the trap for a small
334
+ shape.** `new Rect(0, 0, w, h)` reads naturally because it spans the frame.
335
+ A circle or a `Sphere` does not span anything, so `new Sphere(0, 0, 0, r)` on
336
+ a centre-anchored sprite sits on the sprite's corner, not on the sprite. Put
337
+ it at half the frame:
338
+
339
+ ```js
340
+ // centred on a 24x128 sprite whose anchorPoint is (0.5, 0.5)
341
+ bolt.bodyDef = { type: "dynamic", shapes: [new Sphere(24 / 2, 128 / 2, 0, r)] };
342
+ ```
343
+
344
+ Measured on exactly that sprite: with the shape at `(0, 0, 0)`,
345
+ `adapter.getBodyShapes()` reported it at `(-12, -64)` — **64 units up the
346
+ screen, four times its own radius** — so the projectile collided well away
347
+ from where the player saw it, in a way that still looked like a working game
348
+ because the targets were large.
349
+
350
+ The offset comes from the renderable's own `width` / `height`, NOT from what
351
+ it draws at. Scale a sprite through `currentTransform` and those stay the
352
+ texture's size, so the half-frame term is half the texture, not half the
353
+ drawn quad. The debug overlay is the quickest confirmation: the red shape
354
+ should sit on the art.
355
+
332
356
  This was broken until 20.7: shapes were measured from `pos` regardless of the
333
357
  anchor, so anything not anchored at its top-left corner collided where it was
334
358
  not drawn, by half a body at the default centred anchor and by a full body
@@ -400,7 +424,30 @@ survives but fights collision resolution and jitters. To teleport, go through
400
424
  The lifecycle is `onCollisionStart` (contact begins), `onCollisionActive` (every
401
425
  step while it persists) and `onCollisionEnd` (contact breaks). None of them are
402
426
  defined on `Renderable` — you just declare the ones you want and the dispatcher
403
- checks `typeof`. The legacy `onCollision` still exists and has a trap:
427
+ checks `typeof`. That is also why they cannot be declared on the base class: a
428
+ field there would SHADOW the prototype method every subclass that implements
429
+ one defines.
430
+
431
+ **In TypeScript the response is `CollisionResponse`**, exported from `melonjs`:
432
+
433
+ ```ts
434
+ import type { CollisionResponse, Renderable } from "melonjs";
435
+
436
+ class Coin extends Sprite {
437
+ onCollisionStart(response: CollisionResponse, other: Renderable) { … }
438
+ }
439
+ ```
440
+
441
+ On a renderable you do not subclass, declare the hooks you want as OPTIONAL
442
+ members of a local interface and assign through one plain cast — that is the
443
+ opt-in, and it needs no `as unknown as`:
444
+
445
+ ```ts
446
+ interface Contacts {
447
+ onCollisionStart?: (response: CollisionResponse, other: Renderable) => void;
448
+ }
449
+ (rock as Contacts).onCollisionStart = (response, other) => { … };
450
+ ``` The legacy `onCollision` still exists and has a trap:
404
451
 
405
452
  > On the built-in world, `onCollision` fires **twice per frame** for
406
453
  > dynamic-dynamic pairs — once per outer-loop visit — and gets the raw SAT
@@ -496,50 +543,76 @@ Two prerequisites that produce empty results rather than errors:
496
543
  - On the **built-in** adapter, the broadphase is cleared and rebuilt inside
497
544
  `world.update()`, so querying before the first update returns nothing.
498
545
 
499
- ## 3D collision: `Box3d` and the Z pushback
546
+ ## 3D collision: `Box3d`, `Sphere` and the Z pushback
500
547
 
501
- The narrowphase is not 2D-only. `Box3d` is the one shape with a depth extent,
502
- and a `Box3d`-vs-`Box3d` contact is resolved in three dimensions — it is the
503
- only pair in the engine that can push back along **Z**.
548
+ The narrowphase is not 2D-only. Two shapes carry a depth extent — `Box3d` and
549
+ `Sphere` — and any pair of them is resolved in three dimensions, pushing back
550
+ along **Z** as well as X and Y.
504
551
 
505
552
  ```js
506
- import { Box3d } from "melonjs";
553
+ import { Box3d, Sphere } from "melonjs";
507
554
 
508
- // centre offset from the body, then half-extents on each axis
509
- rock.body.addShape(new Box3d(0, 0, 0, 40, 60, 40));
510
- boat.body.addShape(new Box3d(0, 0, 0, 30, 40, 60));
555
+ // a box: centre offset from the body, then half-extents on each axis
556
+ crate.body.addShape(new Box3d(0, 0, 0, 40, 60, 40));
557
+ // a sphere: centre offset, then one radius
558
+ rock.body.addShape(new Sphere(0, 0, 0, 30));
511
559
  ```
512
560
 
513
- Reach for this for anything moving in the XZ plane under a `Camera3d` — a
561
+ Reach for these for anything moving in the XZ plane under a `Camera3d` — a
514
562
  runner dodging obstacles, a 2.5D platformer, pickups on a course. Hand-rolling
515
563
  a distance check there is the usual mistake, and it throws away the
516
564
  penetration vector the response already carries.
517
565
 
518
- **Read the Z axis off the response, not `overlapN`.** The MTV is a single
519
- axis, so exactly one of `overlapN.x`, `overlapN.y` and `overlapNZ` is
520
- non-zero:
566
+ **Which of the two.** A `Box3d` is world-axis-aligned and cannot be oriented:
567
+ it is right for walls, floors, crates and anything else that stays square to
568
+ the world, and wrong for anything that turns. A `Sphere` has no orientation to
569
+ get wrong, which makes it the shape for things that tumble, and for anything
570
+ laid out over a curved surface — on a globe every object's own "up" points a
571
+ different way, so no single axis-aligned box fits any of them. It is also the
572
+ cheaper test: one distance against a sum of radii.
573
+
574
+ **Z arrives beside `overlapN`, not inside it.** `overlapN` stays the 2D unit
575
+ vector it always was and `overlapNZ` carries the depth component, because
576
+ `Vector3d` does not extend `Vector2d` and widening the field would have broken
577
+ every existing 2D handler. The two together are a 3D unit vector, and
578
+ `overlapZ = overlapNZ * overlap` exactly as `overlapV = overlapN * overlap`.
579
+
580
+ **How much of the normal is in each half depends on the shapes.** A `Box3d`
581
+ pair separates along ONE world axis, so a depth contact leaves the 2D fields
582
+ at zero and a legacy 2D handler correctly applies no push. A `Sphere`
583
+ separates along the line between the two centres, which is generally diagonal:
584
+ all three components are non-zero at once. So subtract both halves rather than
585
+ branching on one:
521
586
 
522
587
  ```js
523
588
  onCollision(response, other) {
524
- if (response.overlapNZ !== 0) {
525
- // a depth-only contact: overlapN / overlapV are BOTH zero here
526
- this.pos.z -= response.overlapZ;
527
- }
528
- return true;
589
+ // works for either shape: the 2D half is zero when there is no 2D push
590
+ this.pos.x -= response.overlapV.x;
591
+ this.pos.y -= response.overlapV.y;
592
+ this.pos.z -= response.overlapZ;
593
+ return false;
529
594
  }
530
595
  ```
531
596
 
532
- That is deliberate: a collision resolved along Z leaves the 2D fields at zero,
533
- so an existing 2D `onCollision` applies no push rather than a wrong one.
597
+ Branching on `if (response.overlapNZ !== 0)` and then moving only `pos.z` is
598
+ the mistake to avoid: correct for a box pair, and for a sphere it throws away
599
+ the planar half of the contact.
534
600
 
535
- **Mixed pairs degrade to 2D.** `Box3d` against a `Polygon`, `Rectangle`,
536
- `RoundRect` or `Ellipse` tests the box's XY footprint and treats the planar
537
- shape as unbounded along Z. Only `Box3d`-vs-`Box3d` gives a depth result, so
538
- give BOTH sides a `Box3d` when you want one.
601
+ **Mixed pairs degrade to 2D.** A `Box3d` or a `Sphere` against a `Polygon`,
602
+ `Rectangle`, `RoundRect` or `Ellipse` tests the 3D shape's XY footprint and
603
+ treats the planar shape as unbounded along Z, which is what keeps a sphere or
604
+ box body colliding with an ordinary Tiled collision layer. `overlapZ` is zero
605
+ for those, since only one side has a depth to resolve. Give BOTH sides a 3D
606
+ shape when you want a depth result.
539
607
 
540
608
  This is the narrowphase and is independent of the 3D broadphase queries above
541
- — `querySphere` / `raycast3d` need `world.sortOn === "depth"`; `Box3d` shapes
542
- do not.
609
+ — `querySphere` / `raycast3d` need `world.sortOn === "depth"`; a 3D body shape
610
+ does not.
611
+
612
+ `raycast3d` measures a `Box3d` body as its box and a body that is one `Sphere`
613
+ as that sphere, both exactly. Anything else, including a body mixing the two,
614
+ is measured as the AABB containing its 3D shapes, and a body with none falls
615
+ back to a bounding sphere derived from the renderable's 2D bounds.
543
616
 
544
617
  **Bounds are what the broadphase sorts by, and they come from the SHAPE.**
545
618
  Every item is filed by its renderable's 2D `getBounds()`, so an object with no
@@ -698,6 +771,7 @@ use them. `adapter.capabilities` (`constraints`,
698
771
  | a body sinks through one shape of a multi-shape body | fixed in 20.7; before that the compound pass only ever re-resolved the first overlapping pair |
699
772
  | a body will not stay put on a slope on the builtin | it is riding the slope and sliding: the builtin has no surface friction, only `frictionAir` damping. Damp it yourself, or use matter/planck |
700
773
  | the sprite is drawn offset from its hitbox | pre-20.7 `anchorPoint` moved the drawing but not the collision shapes; upgrade, and drop any offsets you added to compensate |
774
+ | a small shape sits at the sprite's corner | a shape's offset is measured from the drawn frame's TOP-LEFT; `new Sphere(0, 0, 0, r)` is the corner, `(width / 2, height / 2)` is the centre |
701
775
  | a rotated renderable reports an unrotated bounding box | `autoTransform: false` opts `updateBounds()` out of the transform |
702
776
  | a body in a pile sinks into the floor (built-in) | fixed in 20.7 for anything with an immovable side; bodies pinned only by other DYNAMIC bodies still overlap by a pixel or two, which is what planck/matter are for |
703
777
  | forces do nothing after switching adapter | magnitude units differ — re-tune, don't reuse numbers |
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: melonjs-renderables
3
- description: "Use this skill when subclassing Renderable, writing a custom draw() method, handling pointer or click events on game objects, applying post effects and shaders, or controlling draw order. Covers the pos-relative draw contract, isKinematic and input, addPostEffect vs the deprecated shader property, and z-ordering. Triggers on: Renderable, extends Renderable, draw(, update(, isKinematic, pointerEvent, registerPointerEvent, onClick, addPostEffect, removePostEffect, ShaderEffect, addChild, z-order, anchorPoint, floating."
3
+ description: "Use this skill when subclassing Renderable, writing a custom draw() method, handling pointer or click events on game objects, applying post effects and shaders, or controlling draw order. Covers the pos-relative draw contract, isKinematic and input, addPostEffect vs the deprecated shader property, and z-ordering. Triggers on: Tween, easing, animate, Renderable, extends Renderable, draw(, update(, isKinematic, pointerEvent, registerPointerEvent, onClick, addPostEffect, removePostEffect, ShaderEffect, addChild, z-order, anchorPoint, floating."
4
4
  license: MIT
5
5
  ---
6
6
 
@@ -284,6 +284,7 @@ Hand-rolled equivalents miss the batching and the multi-backend support.
284
284
  | `isRenderable = false` did not hide the object | it gates bounds, not drawing — use `alpha` |
285
285
 
286
286
  ## Related skills
287
+ - `melonjs-scenes-and-state` — `Tween` and the easing families, for animating any of this
287
288
 
288
289
  - `melonjs-getting-started` — Application lifecycle and the scene graph
289
290
  - `melonjs-20-migration` — the deprecated `shader` property and other pre-20 APIs
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: melonjs-scenes-and-state
3
- description: "Use this skill for scene structure and game flow in melonJS — Stage subclasses, the state manager, transitions, pausing, the update loop, timers and tweens. Covers onResetEvent versus the constructor, state.set before state.change, the camera and world lifecycle per stage, and pause-aware timing. Triggers on: Stage, state, state.set, state.change, state.transition, state.pause, onResetEvent, onDestroyEvent, onActivateEvent, onDeactivateEvent, PLAY, MENU, LOADING, update loop, GAME_UPDATE, timer, setTimeout, setInterval, Tween, freeze."
3
+ description: "Use this skill for scene structure and game flow in melonJS — Stage subclasses, the state manager, transitions, pausing, the update loop, timers and tweens. Covers onResetEvent versus the constructor, state.set before state.change, the camera and world lifecycle per stage, and pause-aware timing. Triggers on: Tween, easing, fade out, flash, pulse, punch, shrink back, ease in, ease out, animate over time, countdown in update, Stage, state, state.set, state.change, state.transition, state.pause, onResetEvent, onDestroyEvent, onActivateEvent, onDeactivateEvent, PLAY, MENU, LOADING, update loop, GAME_UPDATE, timer, setTimeout, setInterval, Tween, freeze."
4
4
  license: MIT
5
5
  ---
6
6
 
@@ -165,6 +165,18 @@ Returning nothing is falsy and the object can appear frozen while its state move
165
165
  `dt` is milliseconds since the last frame and is the right basis for motion —
166
166
  the engine already paces it. Do not reach for `performance.now()`.
167
167
 
168
+ **Smooth a value toward a target with `math.damp`, not with `lerp` per
169
+ frame.** `lerp(current, target, 0.1)` once per frame moves further on a slow
170
+ frame than on a fast one, so the feel drifts with the frame rate:
171
+
172
+ ```js
173
+ this.speed = math.damp(this.speed, targetSpeed, 5, dt / 1000);
174
+ ```
175
+
176
+ `damp(current, target, lambda, dt)` takes SECONDS, so divide `dt`. Higher
177
+ `lambda` converges faster. See `melonjs-camera-and-drawing` for the camera's
178
+ own `follow()` damping, which is already frame-rate independent for you.
179
+
168
180
  A common idiom for a per-frame game controller with nothing to draw is a
169
181
  `Renderable(0, 0, 1, 1)` with `alwaysUpdate = true` and an empty `draw()`.
170
182
 
@@ -191,6 +203,59 @@ countdown that should not freeze needs. (That argument was ignored by
191
203
 
192
204
  ## Tweens
193
205
 
206
+ ### You are hand-rolling a tween if you write this
207
+
208
+ The shape, in any of its spellings. If you are adding a field that counts
209
+ milliseconds and dividing it by a constant to get a `0..1`, stop:
210
+
211
+ ```js
212
+ // ✗ a tween, written out longhand
213
+ private flashMs = 0;
214
+ // ... in update(dt)
215
+ if (this.flashMs > 0) {
216
+ this.flashMs -= dt;
217
+ const k = this.flashMs / FLASH_MS; // ← the 0..1
218
+ effect.setUniform("uIntensity", k * k); // ← and a curve, by hand
219
+ }
220
+ ```
221
+
222
+ ```js
223
+ // ✓ the same thing
224
+ const driver = { k: 1 };
225
+ new Tween(driver)
226
+ .to({ k: 0 }, { duration: FLASH_MS })
227
+ .easing(Tween.Easing.Quadratic.In) // the curve, named
228
+ .onUpdate(() => effect.setUniform("uIntensity", driver.k))
229
+ .start();
230
+ ```
231
+
232
+ The tells, any one of which is enough: a `*Ms` or `*Age` field that only
233
+ `update()` touches; a division by a `*_MS` constant; `k * k`, `(1 - k) ** 2` or
234
+ `** 1.4` written inline, which is an easing curve spelled by hand; an
235
+ `if (x <= 0)` arm that exists only to clean up at the end, which is
236
+ `onComplete`; a second timer counting the same duration to chain what happens
237
+ next, which is `delay`.
238
+
239
+ It matters beyond tidiness: a hand-rolled fade needs something to tick it, and
240
+ an effect that is NOT a renderable (one living on the camera, say) has nothing
241
+ that will. That forces an `update(dt)` method and a call site in the game loop
242
+ whose only job is to drive the fade. A tween updates itself, so both disappear.
243
+
244
+ ### When NOT to reach for one
245
+
246
+ Four shapes that look similar and are not tweens:
247
+
248
+ | shape | what it is | use |
249
+ | --- | --- | --- |
250
+ | approaching a value that keeps MOVING | a follow, not an animation | `math.damp` |
251
+ | a loop that never ends | oscillation | a sine of your own clock |
252
+ | a gate: cooldown, spawn interval, invulnerability window | a timer, nothing is interpolated | a plain countdown |
253
+ | the same effect on dozens of live objects | per-instance churn | a hand-rolled countdown really is cheaper |
254
+
255
+ The rule in one line: **fixed duration, known endpoint, one-shot, few
256
+ instances.** All four, or it is not a tween.
257
+
258
+
194
259
  ```js
195
260
  import { Tween, pool } from "melonjs";
196
261
 
@@ -216,6 +281,61 @@ Four traps:
216
281
  - **Stop a tween before destroying its target**, or `onUpdate` fires against a
217
282
  dead renderable.
218
283
 
284
+ **To animate something that is not a property, tween a holder and push the
285
+ value in `onUpdate`.** A shader uniform and a light's intensity are both set
286
+ through a method, so there is nothing for `to()` to interpolate directly. The
287
+ callback is invoked with the tweened object as `this` and the eased progress
288
+ `0..1` as its argument:
289
+
290
+ ```js
291
+ const fade = { level: 1 };
292
+ pool.pull("Tween", fade)
293
+ .to({ level: 0 }, { duration: 400 })
294
+ .easing(Tween.Easing.Quadratic.Out)
295
+ .onUpdate(function () {
296
+ effect.setUniform("uIntensity", this.level);
297
+ muzzleLight.intensity = this.level * 3;
298
+ })
299
+ .start();
300
+ ```
301
+
302
+ Use a `function`, not an arrow, if you want `this` to be the tweened object;
303
+ an arrow keeps the enclosing `this` and you read the holder by name instead.
304
+ For a handful of pooled objects a hand-rolled countdown in `update()` is
305
+ cheaper than a `Tween` each — this is the right tool for one-off effects, not
306
+ for every particle.
307
+
308
+ ### Pick the easing, not just the duration
309
+
310
+ Eleven families, each with `In`, `Out` and `InOut`. `Out` is what most UI
311
+ wants: the value arrives decisively instead of creeping in. The two worth
312
+ knowing by name beyond the power curves:
313
+
314
+ | family | what it does |
315
+ | --- | --- |
316
+ | `Back` | **overshoots past the target and settles back**, which is impact: a stamp, a button press, anything that should LAND rather than arrive |
317
+ | `Bounce` | lands and bounces, for something dropping onto a surface |
318
+
319
+ ```js
320
+ // a stamp: scale falls to 1 and springs through it
321
+ const driver = { scale: 5 };
322
+ new Tween(driver)
323
+ .to({ scale: 1 }, { duration: 420 })
324
+ .easing(Tween.Easing.Back.Out)
325
+ .onUpdate(() => { label.currentTransform.identity(); label.scale(driver.scale); })
326
+ .onComplete(() => camera.shake(12, 400)) // the thump, when it truly lands
327
+ .start();
328
+ ```
329
+
330
+ `Back` and `Elastic` **leave the range** between your start and end, so clamp
331
+ anything that cannot take it, an opacity or a colour channel above all. The
332
+ overshoot follows the direction of travel: a value tweened DOWN overshoots
333
+ below its target.
334
+
335
+ Reach for `onComplete` for anything that must happen on arrival, rather than a
336
+ second timer counting the same duration. `delay` in the `to()` options chains
337
+ the next one without a timer either.
338
+
219
339
  ## Guard teardown-adjacent callbacks
220
340
 
221
341
  Callbacks can fire while a stage is being torn down, when the current state is