player-glow.ts
PURPOSE
Renders a colored radial glow halo underneath the player ship. Idle state is a light-blue tinted ring at fixed opacity; gameplay events flash the glow to a new color for ~0.3 seconds and fade back to default. Most recent flash wins (newer events stomp older). Performance is structured so the idle path (the vast majority of frames) costs a single drawImage with no composite mode change, while the multiply-tint path runs only during the brief flash window.
OWNS
- Module-level flash state:
_flashR,_flashG,_flashB,_flashTimer. - Two pre-baked offscreen radial-gradient canvases:
_defaultStamp(blue) and_whiteStamp(white). - Constants:
DEFAULT_R/G/B(100, 180, 255),DEFAULT_ALPHA(0.50),FLASH_DECAY(0.3 s),FLASH_ALPHA(0.80),STAMP_SZ(128). - The
PlayerGlowexport object exposingflash,update,draw,reset. - The internal radial stamp baking routine
_bakeRadialStamp(gradient stops at 0, 0.3, 0.7, 1.0 with alphas 1.0, 0.6, 0.2, 0).
READS FROM
Camera.toSfrom../rendering/camerato convert world coordinates to screen coordinates.camerafrom../coreforcamera.zoom, used to scale the glow radius into screen space.- The
worldRadiusargument passed intodraw(ship collision radius), multiplied by 2.5 to size the halo wider than the hull silhouette. - The
dtargument passed intoupdateto decay the flash timer.
PUSHES TO
- The supplied
CanvasRenderingContext2Dindraw: mutatesglobalAlpha, optionallyglobalCompositeOperation(set tomultiplyonly on the flash path) andfillStyle, then issuesdrawImage,arc, andfillcalls. All mutations are bracketed byctx.save()/ctx.restore(). - Constructs DOM canvases lazily via
document.createElement('canvas')inside_bakeRadialStampon first draw (cached for the lifetime of the page).
DOES NOT
- Does not own or read ship position, velocity, or facing — caller passes world coordinates and radius.
- Does not own the rendering frame loop, the camera, or composite ordering — caller decides when to invoke
draw. - Does not classify events. Color selection (shield/hull/XP/weapon) happens in the call sites; this module only consumes RGB triples.
- Does not blend or queue multiple concurrent flashes — each
flashcall overwrites the prior color and resets the timer to full. - Does not emit audio, telemetry, or particles.
- Does not allocate per frame on the idle path: no
createRadialGradient, no temporary objects beyond the screen-space coord returned byCamera.toS. - Does not draw anything when
glowRis zero or the ship is offscreen — there is no culling here; the caller decides whether to invokedraw.
Signals
PlayerGlow.flash(r, g, b)— set the current flash color to the given RGB (0-255) and reset the flash timer toFLASH_DECAY. Idempotent; the most recent call wins.PlayerGlow.update(dt)— decay the flash timer bydt, clamped at zero. No effect if no flash is active.PlayerGlow.draw(ctx, wx, wy, worldRadius)— render the glow. Computest = _flashTimer / FLASH_DECAYandease = t * t. Ifease < 0.01, takes the idle path (singledrawImageof the blue stamp atDEFAULT_ALPHA). Otherwise, takes the flash path: draws the white stamp at interpolated alpha betweenDEFAULT_ALPHAandFLASH_ALPHA, then overlays a multiply-blended filled arc tinted to the interpolated RGB between default-blue and flash color.PlayerGlow.reset()— clear_flashTimerto zero (called on run reset).
Entry points
engine/bridge.tscallsPlayerGlow.reset()on run reset andPlayerGlow.update(rawDt)once per frame in the engine tick. The correspondingPlayerGlow.drawcall inside the world render pass is currently commented out with aDISABLED — FPS profilingnote.engine/combat/damage.tscallsPlayerGlow.flash(255, 255, 255)on shield hit andPlayerGlow.flash(255, 40, 40)on hull hit.engine/effects/actions.tscallsPlayerGlow.flash(r, g, b)from generic effect actions (XP collect and other color-driven cues).engine/weapons/weapons.tscallsPlayerGlow.flash(r, g, b)on weapon fire using rarity color.
Pattern notes
- Pre-baked stamps avoid per-frame gradient construction; the radial gradient is computed once and cached on the first draw via
_ensureStamps. - The two-path design (idle vs. flash) is a deliberate fast-path optimization: the idle path skips the multiply composite operation entirely because the stamp is already pre-tinted blue, while the flash path uses a neutral white stamp plus multiply tint so any RGB color is reachable from a single baked asset.
- Easing is
ease = t * t(quadratic ease-out from peak), giving a fast initial fade and a slower settle. - Glow radius is
worldRadius * 2.5 * camera.zoom— the 2.5 multiplier ensures the halo bleeds visibly beyond the ship hull rather than being clipped to the silhouette. - The multiply tint on the flash path is clipped to a circular
arcrather than afillRectto avoid showing a visible square box around the glow at flash peak. - Module state is file-scoped
letbindings, not a class instance — there is exactly one player glow per page lifetime, matching the single-player game model. - No null checks on
getContext('2d')result (uses!); the stamp bake assumes a standards-compliant 2D canvas context is always available.