Repository navigation
Tweens: document what exists, and stop hand-rolling it - #1711
Merged
Merged
Conversation
The engine has eleven easing families and the skills showed one of them,
Quadratic.Out, twice. The one place offering "the full list" pointed at
`{@link Easing}`, which typedoc has no page for, so it went nowhere. Nothing
named Back, which is the difference between a fade and an impact.
Tween.Easing now carries the list itself: every family, what each is for, and
the trap that Back and Elastic leave the range between the endpoints so an
opacity or a colour channel has to be clamped. EasingFunction and
InterpolationFunction are exported as types; they were referenced by the docs
but not included in them, which typedoc warned about twice.
The skill gains the half that prevents the mistake rather than describing the
cure: the longhand shape to recognise (a *Ms field only update() touches, a
division by a *_MS constant, `k * k` inline, an `if (x <= 0)` cleanup arm that
is onComplete), and when NOT to reach for a tween, since a damp toward a moving
target, an oscillation, a cooldown gate and a per-instance effect all look
similar and are not. Its triggers now match what someone WRITING one would say,
"fade out", "flash", "pulse", rather than the word Tween, which is what they
would never search for. melonjs-ui-and-text had no Related skills block at all.
Two conversions, both one-shot singletons with a curve spelled by hand:
- afterBurner's death wash. It lives on the camera rather than on a renderable
so nothing ticked it, which had forced a whole HUD#update(dt) and a call site
in the game loop purely to drive the fade. Both are gone.
- jungleRabbit's multiplier punch, which was Quadratic.Out written out as
`1 + MULT_PUNCH * (1 - k) * (1 - k)`.
Deliberately not converted: per-enemy hit flashes, score popups and contrail
nodes, where dozens are live and a float subtract beats a tween each.
Also in afterBurner, GAME OVER is sized to the viewport instead of a fixed
42px, measured from a throwaway bake because Text rasterizes at its font size
and a scaled-up bake is blurry at rest. It takes a dark outline, since red text
over the red death wash had almost no contrast, and the same Back.Out drop
Earth Defender uses. No shake: the death already fires one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NGvtaUNATVCVxD2qcbiY4t
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The engine has eleven easing families. The skills showed exactly one of
them,
Quadratic.Out, twice. The one place that offered "the full list"pointed at
{@link Easing}— a page typedoc does not generate — so it wentnowhere, and nothing named
Back, which is the difference between a fade andan impact.
This started because I hand-rolled an easing curve by hand while the engine had
the named one sitting there, then found the same thing in two other examples.
Engine
Tween.Easingcarries the list itself: all eleven families, what each isfor, and the trap that
BackandElasticleave the range between yourendpoints, so an opacity or a colour channel has to be clamped. Plus the note
that overshoot follows the direction of travel, which is not obvious: a value
tweened DOWN overshoots BELOW its target.
EasingFunctionandInterpolationFunctionexported as types. They werereferenced by the documentation but not included in it — typedoc warned on
both. Warnings go 166 → 164 and both type pages now exist.
easing.tsitself is untouched:Backhas always been there. The gap was thatnothing pointed at it.
Skill
melonjs-scenes-and-statealready had a good## Tweenssection. What itlacked was the half that prevents the mistake rather than describing the
cure:
beside the tween, and the tells: a
*Ms/*Agefield onlyupdate()touches;a division by a
*_MSconstant;k * kor** 1.4inline, which is aneasing spelled by hand; an
if (x <= 0)arm that only cleans up at the end,which is
onComplete; a second timer counting the same duration, which isdelay.oscillation, a cooldown gate and a per-instance effect all look similar and
are none of them tweens. The rule: fixed duration, known endpoint,
one-shot, few instances.
BackandBounceby name.Its triggers now match what someone writing one would say (
fade out,flash,pulse,punch,countdown in update) rather than the word "Tween",which is precisely what they would never search for.
melonjs-ui-and-texthadno
## Related skillsblock at all; it has one now, andmelonjs-renderables/melonjs-effects-and-shadersgained the pointer.Also: where a
Textgradient actually livesSame file, same class of problem, and the one that started the whole thread.
Text.fillStyleaccepts aGradient; it never holds one. Theconstructor routes a gradient to
fillGradientand leavesfillStyleas thepooled
Colorit always is, which is what keepsfillStyle.alphagating thefill and keeps the colour owned by the pool. So changing a ramp after
construction means assigning the field it landed in:
I hit that
TS2740and misread it as a typing gap, nearly "fixing" it bywidening
fillStyletoColor | Gradient— which would have broken thealpha > 0fill gate, thecopy()path and the pooling. The engine is right;the documentation was missing. No typing changed here, only the skill.
The
melonjs-ui-and-textskill also gains a table of whatfillStylemeans ineach class, since the name is shared and the behaviour is not: a real
Coloron
TextagainstRenderable#tintonBitmapText, where white is theabsence of a tint rather than white glyphs, gradients are impossible, there
is no stroke, and
sizeis a ratio rather than pixels.Every claim in that section was checked against
text.jsandbitmaptext.jsrather than written from memory.
Conversions
Two, both one-shot singletons with a curve written by hand:
renderable, so nothing ticks it — which had forced a whole
HUD#update(dt)and a call site in the game loop purely to drive the fade. Both are now
gone. This is the case where a tween is not merely tidier: it removes
machinery that existed only to work around not having one.
Quadratic.Outwritten out as1 + MULT_PUNCH * (1 - k) * (1 - k).Deliberately not converted: per-enemy hit flashes, score popups and
contrail nodes. Dozens are live at once and a float subtract beats a tween
each — which is the "when not to" rule earning its place.
Also in afterBurner
GAME OVERwas a fixed 42px. It is now sized to 82% of the viewport, measuredfrom a throwaway bake rather than scaled with a transform, because
Textrasterizes at its font size and a scaled-up bake would be blurry at rest.
It takes a dark outline at 3% of the size, since red text over the red death
wash had almost no contrast at the one moment you are meant to read it. And it
drops with the same
Back.Outstamp used elsewhere — with no shake, sincethe death already fires
shake(22, 900, force).Verification
Engine suite 7534 passed | 10 skipped,
npm run doc0 errors, biome cleanacross 731 files. The examples package sits at its 564 pre-existing type
errors, unchanged (counted with ANSI codes stripped —
grep "error TS"silentlymatches nothing against coloured tsc output, which had been hiding the real
figure).
All three touched examples were loaded in a browser and driven through start,
input and restart with zero page or console errors.
🤖 Generated with Claude Code
https://claude.ai/code/session_01NGvtaUNATVCVxD2qcbiY4t