What's New
19.9.1 is a focused bug-fix release for the 19.9 line — 17 fixes, two long-standing open issues closed, no API additions, drop-in upgrade from 19.9.0.
The headline: video sprites no longer stall in fullscreen. Large videos (1080p/4K) dropped to ~4fps the moment the game went fullscreen while everything else kept rendering at 60 — the browser parks off-DOM video elements on a slow background timer once the page compositor idles, and a fullscreen canvas is exactly what idles it. The engine now keeps a requestVideoFrameCallback pending on the element, which forces full-rate frame delivery, and as a bonus only uploads to the GPU when the video actually presented a new frame.
Also closed: #1231 — the canvas slowly growing on every window resize (alt-tab, devtools) when its container sits inside a wrapper with no fixed CSS size. Two root causes, both fixed: the engine canvas now defaults to display: block (the browser's inline default added a baseline gap that fed back into the auto-scale measurement), and auto-scale measures the container's content box instead of its border box.
Bug Fixes
- Video fullscreen frame stall — pending
requestVideoFrameCallbackkeeps detached video elements at full frame rate (see above); video sprites also correctly pause onstate.pause()now (the listener had a wrong event name since its introduction) - Canvas growth feedback loop on resize (#1231) —
display: blockdefault + content-box measurement (see above) - Renderables batch:
onVisibilityChangefired a spurious leave/enter pair every frame for visible objects;Container/Entitydestroy()passed anArgumentsobject toonDestroyEventinstead of the actual arguments;Container.getNextChild()returned the first child for a non-member;Text/BitmapTextdestroy()wiped a caller-provided string array;NineSliceSpriteshowed ~2px seams for region sizes not divisible by 4 - Animation batch: a completed
loop: falseanimation could never be replayed viaplay()/setCurrentAnimation(); frames with a0delay froze the game in an infinite loop (now advance one frame per tick) - Camera batch: destroyed cameras kept reacting to game reset / canvas resize (crash after destroy, one leaked camera per state switch with
cameraClass— stages now destroy the cameras they construct); the bounds clamp ignored a non-zero world origin, making the far edges unreachable - Particles: dead particles were released to the pool while their removal was still deferred — a same-frame respawn could silently vanish, and a same-frame emitter teardown could poison the pool
- Drag & drop:
DropTarget.setCheckMethod()validated the current method instead of the new one, accepting invalid names that crashed later on drop - Hardening:
ShaderEffect.setTexture()rejects anHTMLVideoElementwith a clearTypeErrorinstead of silently freezing on its first frame; BMFontpaddingwas parsed withpadLeft/padRightswapped; the texture cache's dead frame-dimension refinement is removed (#1489) with the first-registered-atlas behavior now explicit and test-codified
Performance
- Video textures upload to the GPU only when a new frame was actually presented — previously a full
texImage2Dran on every render tick per sprite (~2× redundant for 30fps video at 60Hz, more when several sprites share one element)
Changed
- The engine-inserted canvas defaults to
display: block; set any inlinedisplaystyle on the canvas to keep your own value - Videos now genuinely pause on
state.pause()— this was always the documented behavior, but the listener never fired
Install
npm install melonjs@19.9.1CDN:
<script type="module" src="https://cdn.jsdelivr.net/npm/melonjs@19/+esm"></script>🤖 Generated with Claude Code