PURPOSE
Loads, caches, and downsamples the eight raw enemy archetype PNGs that live in public/enemies/. Provides the lookup that the bridge’s atlas-patch step reads to bake red-tinted, outlined enemy silhouettes into the sprite atlas (one common variant plus one variant per rarity color per archetype). Pipeline is fully separate from the player /ships-v4/ sprite pipeline.
OWNS
ENEMY_SPRITE_MAP— archetype-string to PNG-path table. Two factions, four archetypes each: Bugs (shooter,orb,mortar,charger) and City (gunner,field,sniper,racer).- Module-private
_cache: Record<string, HTMLImageElement>— keyed by archetype, holds the downsampled image returned bydownsampleSprite. - Module-private
_loaded: boolean— single-shot guard sopreloadEnemySpritesis idempotent. - Exported functions:
preloadEnemySprites,getEnemySprite,archetypeFromTypeId.
READS FROM
SPRITE_TARGET_ENEMYfrom../core/config— the target dimension passed todownsampleSpritefor every enemy sprite.downsampleSpritefrom./sprites— used to shrink each loadedHTMLImageElementtoSPRITE_TARGET_ENEMYbefore caching.- The browser’s
Imageloader, fetching files at/enemies/<archetype>.png(eight static assets underpublic/enemies/).
PUSHES TO
- Its own in-module
_cache— never exposed directly; readers go throughgetEnemySprite. console.warnon load failure (network error, zero-dimension image) — diagnostic only, never throws.- Caller (the atlas patcher in
engine/bridge.ts) —getEnemySprite(archetype)returns the cachedHTMLImageElementwhich the caller draws into a 128-px scratch canvas, source-in fills red, then outlines per rarity before patching the atlas region.
DOES NOT
- Does not draw, blit, or composite. No
ctx.drawImageor atlas writes happen here — those are bridge-side. - Does not tint, outline, or recolor the sprites. The red silhouette and the per-rarity outline/glow are baked downstream by the bridge atlas-patch step.
- Does not handle bosses or boss anchors. Boss sprites come from
public/ships-v4/viaBOSS_TYPE_TO_HULL, not from this module. - Does not share assets with the player
/ships-v4/pipeline. If a player ship needs reuse as an enemy, the PNG is duplicated underpublic/enemies/. - Does not block on missing sprites. A failed load resolves silently; the renderer falls back to polygon rendering until the sprite is ready (same contract as player ship sprites).
- Does not retry failed loads. One attempt at boot; failure is terminal for that session.
- Does not encode rarity. Rarity is appended downstream when the bridge builds atlas region keys (
enemy_<archetype>_<rarity>).
Signals
console.warn('[enemy-sprites] Failed to load: <path>')onimg.onerror.console.warn('[enemy-sprites] Zero dimensions: <path>')when the image loads but reportsnaturalWidthornaturalHeightof 0.- No success log, no telemetry event, no return value other than the resolved
Promise<void>frompreloadEnemySprites.
Entry points
preloadEnemySprites(): Promise<void>— called once during boot fromengine/core/preload.tsas step 2 of the preload sequence (afterpreloadAllShipV4, beforepreloadWeaponIcons). Concurrently loads all eight PNGs viaPromise.alloverObject.entries(ENEMY_SPRITE_MAP); each entry downsamples ononloadand resolves regardless of success or failure.getEnemySprite(archetype: string): HTMLImageElement | null— called fromengine/bridge.tsinside the per-archetype atlas-patch loop (_ALL_ENEMY_ARCHETYPES = ['orb', 'charger', 'shooter', 'mortar', 'gunner', 'field', 'sniper', 'racer']). Returnsnullif the cache entry is missing, the image is notcomplete, ornaturalWidth <= 0; callerscontinuepast archetypes that aren’t ready and retry on the next render pass.archetypeFromTypeId(typeId: string): string— splits a typeId on_and returns the first segment. Maps full typeIds likeshooter_commonororb_legendaryback to the archetype key used byENEMY_SPRITE_MAPandgetEnemySprite.
Pattern notes
- Archetype to PNG mapping is one-to-one and static. The
ENEMY_SPRITE_MAPkeys are the authoritative archetype names;_ALL_ENEMY_ARCHETYPESinengine/bridge.tsmust stay in sync, and atlas slots inatlas-builder.tsmust reserveenemy_<archetype>_<rarity>for every (archetype, rarity) pair. - Rarity is encoded in the typeId suffix, not in the sprite. A single archetype PNG feeds every rarity tier — the bridge tints once to red, then bakes one common outline plus one outline-plus-glow per rarity color (
ENEMY_RARITY_COLORS) into separate atlas regions all sourced from the sameHTMLImageElement. archetypeFromTypeIdis the canonical decoder for typeIds at the rendering boundary. It assumes typeIds always follow<archetype>_<rarity>and does no validation — a typeId without an underscore returns the whole string unchanged.- Idempotent preload via the
_loadedguard; safe to re-call but a no-op after first completion. - Non-blocking load. The 192-px
SPRITE_TARGET_PLAYERand 96-pxSPRITE_TARGET_ENEMYare chosen so enemy sprites can be downsampled aggressively (enemies are small on screen). Atlas-side bake then upscales the cached image into a 128-px scratch canvas before outline baking. - Cache safety check in
getEnemySprite(!img.complete || img.naturalWidth <= 0) is defense against the case wheredownsampleSpritereturned an image whose source hadn’t fully decoded. Returningnulllets the renderer poly-fallback without a crash. - Module state (
_cache,_loaded) is module-scoped and never reset. Reloads require a full page refresh.