PURPOSE
Loads weapon and modifier icon PNGs, prebakes each into a 512×512 canvas with a sticker outline (15px white inner stroke, 7px black outer stroke), and serves them via a cache to the HUD. Also handles emoji-glyph modifiers that lack a PNG asset by baking them with the same outline treatment so every level-up card icon reads in one consistent family.
OWNS
ICON_IDSlist of weapon +mod_*modifier IDs that have PNG assets.EMOJI_STICKER_IDSmap of emoji/text-glyph modifier IDs to their source glyph._cacheof prebakedHTMLCanvasElementinstances keyed by icon ID._loadedboot-once flag.- Sticker outline constants:
STAMP_SIZE(512),WHITE_OUTLINE_PX(15),BLACK_OUTLINE_PX(7),TOTAL_OUTLINE_PX(22),ICON_AREA(468),OUTLINE_SAMPLES(48). - Precomputed
_outDx/_outDyunit-circle offsets used to stamp outlines. - Private bake helpers:
_makeSilhouette,_makeSilhouetteFromCanvas,_bakeEmojiIcon,_bakeEmojiStickerIcon,_bakeStickerIcon.
READS FROM
../../data/weapon-icons—hasWeaponIcon(id)guard andgetWeaponIconPath(id)for the PNG URL of each ID inICON_IDS.- Browser DOM —
document.createElement('canvas')andnew Image()to load PNGs. img.naturalWidth/naturalHeightfor fit-scale math inside_bakeStickerIcon.
PUSHES TO
_cache— every successful prebake writes the resulting canvas under its ID.- Returns prebaked canvases to callers (HUD) via
getWeaponIconImgandgetOrBakeEmojiIcon. console.warnfor load failures, zero-dimension images, and the post-boot missing-asset summary.
DOES NOT
- Does not draw to the screen — only returns canvases for other renderers (hud.ts) to draw.
- Does not own a sprite atlas or texture; each icon is its own
HTMLCanvasElement. - Does not handle gameplay logic, weapon stats, or modifier effects.
- Does not retry failed image loads — failures are swallowed with a warn and the entry is simply absent from the cache.
- Does not evict cache entries; entries live for the lifetime of the page.
- Does not bake on first paint — assets must be preloaded at boot or icons resolve to
nulluntil ready (except emoji-glyph IDs, which can lazy-bake on demand).
Signals
- Asynchronous:
preloadWeaponIconsreturns aPromise<void>that resolves once every PNG has either loaded-and-baked or failed gracefully, and everyEMOJI_STICKER_IDSentry has been baked. - Idempotent: re-calling
preloadWeaponIconsafter_loadedis true resolves immediately. - Side-channel: console warnings on per-icon load failure, zero-dimension load, and a post-boot summary listing any known PNG assets that did not make it into the cache.
Entry points
preloadWeaponIcons(): Promise<void>— call once at app boot. Loads everyICON_IDSPNG viagetWeaponIconPath, runs each through_bakeStickerIcon, then bakes everyEMOJI_STICKER_IDSglyph via_bakeEmojiStickerIcon, and flips_loaded.getWeaponIconImg(id: string): HTMLCanvasElement | null— cache lookup, then lazy emoji-sticker bake for anyEMOJI_STICKER_IDSentry, then PNG-asset guard viahasWeaponIcon. Returnsnullwhen the asset is known but not yet preloaded.getOrBakeEmojiIcon(text: string): HTMLCanvasElement— lazy plain-emoji bake (no sticker outline) for any glyph; cached on first call under the glyph string.
Pattern notes
- Sticker outline is built in three passes inside a single canvas: a black silhouette stamped at
TOTAL_OUTLINE_PXradius, a white silhouette stamped atWHITE_OUTLINE_PXradius, then the source image on top. Both stamping passes loop the precomputed 48-sample unit-circle offsets. - Silhouettes are produced by drawing the source into an offscreen canvas, then using
globalCompositeOperation = 'source-in'plusfillRectto recolor only the opaque pixels. _bakeStickerIconfits PNG sources into the 468px safe area (ICON_AREA = STAMP_SIZE - TOTAL_OUTLINE_PX * 2) preserving aspect ratio and centers the result inside the 512×512 stamp._bakeEmojiStickerIconfirst renders the emoji into a full-stamp buffer at 72% font size, then stamps outlines at(0,0)because the emoji bake already covers the full footprint.- Modifier IDs use a
mod_prefix to avoid collision with weapon IDs in the same cache. mod_xp_gainis the plain-text glyph'XP', baked through the same emoji-sticker path so it reads as one icon family with the emoji modifiers.getWeaponIconImgreturnsnull(not a placeholder) when an asset is known but not yet loaded — callers are expected to handle the null case rather than receive a fallback canvas.- Emoji-sticker icons can lazy-bake on the first
getWeaponIconImgcall, butpreloadWeaponIconsproactively bakes all of them so level-up cards never flash a bare emoji glyph mid-reveal. getOrBakeEmojiIconshares the same_cacheas PNG and sticker-emoji entries — callers should pick text keys that won’t collide with ICON_IDS or EMOJI_STICKER_IDS keys.