PURPOSE
Sprite utility module providing two pieces: a generic image downsampler reused by enemy-sprites, and a silhouette-based collision radius calculator that derives tight hitboxes from the alpha channel of v4 player ship diffuse textures. The legacy per-rarity player loader is gone; this file only computes geometry on top of v4 sprites now.
OWNS
downsampleSprite(src, targetSize)— canvas-based proportional resize of anHTMLImageElementto a target longest-axis dimension, returning a new image via dataURL round-trip.getContentMaxFrac(hullClass)— returnsmax(contentW, contentH) / max(spriteW, spriteH)for a hull’s v4 diffuse, used to scale hull polygons to rendered content size.getSilhouetteCollisionRadius(shipRadius, hullClass, rarity, shipScale)— returns world-unit collision radius derived from the tallest column of solid pixels in the v4 diffuse.- Per-hull caches:
_silhouetteHeightFracand_contentMaxFrac(module-localRecord<string, number>). SILHOUETTE_ALPHA_THRESHOLD = 155constant defining what counts as a solid silhouette pixel.- Inline collision-math constants mirroring draw.ts:
SHIP_DRAW_SCALE = 6.3,SHIP_SPRITE_PAD_MULT = 1.6, extra1.2WebGL pad factor, and theRARITY_SIZE_MULTtable (common 0.92, uncommon 0.96, rare 1.00, epic 1.06, legendary 1.12). - Internal helper
computeSilhouetteHeightFrac(img, key)that scans every column for top/bottom solid pixels and records the tallest run as a fraction ofnaturalHeight.
READS FROM
getShipV4(hullClass)fromships-v4-loader.ts— pulls the diffuseHTMLImageElementfor the active hull.ShipRaritytype from../../data/ships.- The DOM canvas 2D context (
document.createElement('canvas'),getContext('2d'),getImageData) for pixel inspection and image scaling.
PUSHES TO
- Module-local caches
_silhouetteHeightFracand_contentMaxFrac— populated lazily on first request per hull key. - Newly created
HTMLImageElementinstances returned fromdownsampleSprite(consumers own the result). - No external stores, no telemetry, no events.
DOES NOT
- Does not load or fetch any image — relies on
ships-v4-loaderto have decoded the diffuse first. - Does not draw to the framebuffer or interact with WebGL; all canvases used here are throwaway 2D contexts for pixel reads.
- Does not handle rarity-specific sprite variants — v4 ships only one diffuse per hull, so the cache keys are hull class alone.
- Does not invalidate its caches; values persist for the session once computed.
- Does not throw on unloaded sprites —
getSilhouetteCollisionRadiusandgetContentMaxFracreturn0so callers can fall back. - Does not import from
draw.ts— collision constants are duplicated inline to avoid a circular dependency.
Signals
- Return value
0fromgetSilhouetteCollisionRadiusorgetContentMaxFracsignals “sprite not ready” — caller must fall back (collision uses the shield-based radius formula in that case). downsampleSpritereturning the originalsrcsignals either “already small enough” or “decode failure during dataURL round-trip” (theonerrorhandler resolves with the original image).- A cached
0.5fraction in either cache signals a degenerate sprite (zero dimensions or no solid pixels found); subsequent calls reuse that value.
Entry points
downsampleSprite— called fromenemy-sprites.tsduring enemy sprite preparation.getSilhouetteCollisionRadius— called from the player collision sizing path; consumesship.radius, hull class, rarity, and optionalshipScale.getContentMaxFrac— called when scaling hull polygons to match rendered content within the padded sprite bounds.
Pattern notes
- Lazy memoisation with a sentinel: caches check
existing !== undefinedso a legitimate0stays cached. - Pixel-perfect silhouette analysis runs at full natural resolution via
getImageDataon a one-shot 2D canvas; cost is paid once per hull then cached for the session. - Constants from
draw.tsare intentionally copy-pasted with a comment marking them as a must-match-exactly mirror — preferred over a circular import. - Height-of-tallest-column metric (rather than width) is deliberate: when ships render sideways the height axis is the narrower silhouette dimension, producing a tighter collision radius that stays inside the visible hull.
shipSzformulashipRadius * SHIP_DRAW_SCALE * SHIP_SPRITE_PAD_MULT * 1.2 * rarityMult * shipScaleand the aspect normaliser1 / max(1, aspect)together reproduce the WebGL draw scale so the returned radius is in the same world units the renderer uses.imageSmoothingQuality = 'high'is set explicitly on the downsample canvas; the dataURL round-trip is async because newImage()decode is async even from a data URL.