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_IDS list of weapon + mod_* modifier IDs that have PNG assets.
  • EMOJI_STICKER_IDS map of emoji/text-glyph modifier IDs to their source glyph.
  • _cache of prebaked HTMLCanvasElement instances keyed by icon ID.
  • _loaded boot-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 / _outDy unit-circle offsets used to stamp outlines.
  • Private bake helpers: _makeSilhouette, _makeSilhouetteFromCanvas, _bakeEmojiIcon, _bakeEmojiStickerIcon, _bakeStickerIcon.

READS FROM

  • ../../data/weapon-iconshasWeaponIcon(id) guard and getWeaponIconPath(id) for the PNG URL of each ID in ICON_IDS.
  • Browser DOM — document.createElement('canvas') and new Image() to load PNGs.
  • img.naturalWidth / naturalHeight for 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 getWeaponIconImg and getOrBakeEmojiIcon.
  • console.warn for 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 null until ready (except emoji-glyph IDs, which can lazy-bake on demand).

Signals

  • Asynchronous: preloadWeaponIcons returns a Promise<void> that resolves once every PNG has either loaded-and-baked or failed gracefully, and every EMOJI_STICKER_IDS entry has been baked.
  • Idempotent: re-calling preloadWeaponIcons after _loaded is 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 every ICON_IDS PNG via getWeaponIconPath, runs each through _bakeStickerIcon, then bakes every EMOJI_STICKER_IDS glyph via _bakeEmojiStickerIcon, and flips _loaded.
  • getWeaponIconImg(id: string): HTMLCanvasElement | null — cache lookup, then lazy emoji-sticker bake for any EMOJI_STICKER_IDS entry, then PNG-asset guard via hasWeaponIcon. Returns null when 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_PX radius, a white silhouette stamped at WHITE_OUTLINE_PX radius, 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' plus fillRect to recolor only the opaque pixels.
  • _bakeStickerIcon fits 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.
  • _bakeEmojiStickerIcon first 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_gain is the plain-text glyph 'XP', baked through the same emoji-sticker path so it reads as one icon family with the emoji modifiers.
  • getWeaponIconImg returns null (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 getWeaponIconImg call, but preloadWeaponIcons proactively bakes all of them so level-up cards never flash a bare emoji glyph mid-reveal.
  • getOrBakeEmojiIcon shares the same _cache as PNG and sticker-emoji entries — callers should pick text keys that won’t collide with ICON_IDS or EMOJI_STICKER_IDS keys.