data/enemies.ts

Top-level barrel re-export. All actual enemy data, type definitions, and lookup tables now live in ./enemies/; this file exists solely to preserve the historic import path '../data/enemies' (and from './data/enemies') for every consumer in the engine, renderer, bridge, spawner, ability runtime, and metagame. One named export block, no logic, no transformation — straight pass-through from ./enemies/index.

λ — Re-export surface

Single export { ... } from './enemies/index' statement. Two type re-exports, fourteen value re-exports, two function re-exports.

SymbolKindOrigin moduleRole
EnemyRaritytype./enemies/_typesUnion 'common' | 'uncommon' | 'rare' | 'epic' | 'legendary'.
EnemySetIdtype./enemies/_typesTagged spawn-pool flavor id ('bugs', 'city', 'bugs_mortar', etc.).
EnemyTypeDeftype./enemies/_typesPer-id runtime def (hp, speed, radius, xp, tags, archetype-specific extras).
BaseArchetypetype./enemies/_typesPer-archetype base record fed into the rarity-cube builder.
RARITY_TINTSRecord<EnemyRarity, string>./enemies/_typesPer-rarity hex tint stamped on every cube entry.
RARITY_MULTSRecord<EnemyRarity, {hp, speed, radius, xp, damage}>./enemies/_typesPer-rarity multiplicative scalars applied to base stats.
RARITY_ORDEREnemyRarity[]./enemies/_typesCanonical ordering ['common','uncommon','rare','epic','legendary']. Used as the inner-loop axis and as an index for per-rarity sub-curves.
ENEMY_TYPESEnemyTypeDef[]./enemies/indexMaterialized list: 16 archetypes × 5 rarities = 80 cube entries, plus 20 hand-authored boss-roster entries appended.
ENEMY_TYPE_MAPRecord<string, EnemyTypeDef>./enemies/indexFast id → def lookup built from ENEMY_TYPES.
ENEMY_TIER_COLORSRecord<string, string>./enemies/indexRenderer-side tier hex map (default/common/uncommon/rare/legendary). Skips epic.
PERSONALITY_PROFILESRecord<string, {c,t,s}>./enemies/indexPer-archetype {c, t, s} triple. Only the 8 original archetypes appear; t25/t26/t48–t52 variants inherit nothing here.
ENEMY_COLLISION_SCALE3.85./enemies/indexMultiplier on radius for collision tests.
getEnemyCollisionRadius(e: {radius}) => number./enemies/indexe.radius * ENEMY_COLLISION_SCALE.
SPAWN_POOLSArray<{progressMin, progressMax, types}>./enemies/indexDefault bugs pool (Landing Site). 5 progress bands, orb-dominated.
getSpawnPool(progress: number) => string[]./enemies/indexPick band for default SPAWN_POOLS by progress.
getSpawnPoolForSet(setId, progress) => string[]./enemies/indexPick band for the per-set variant pool (bugs_mortar/shooter/charger/sniper/field/racer/mixed/heavy, or CITY_SPAWN_POOLS, or default).
pickEliteForSet(setId, progress, rarityCap?) => {leaderId, followerArchetype, eliteRarity}./enemies/indexRoll an elite leader from the set’s leader archetypes; 50/30/20 rare/epic/legendary; optional rarity cap.
pickEliteForProgress(progress, rarityCap?) => {leaderId, followerArchetype, eliteRarity}./enemies/indexSame as above but on the default progress pool; follower archetype equals leader archetype.
HULL_SHAPE_MAPRecord<string, string>./enemies/indexPer-id polygon-shape key consumed by the renderer’s polygon fallback. Covers all 16 archetypes × 5 rarities + every boss-roster id.
SET_SWARM_ARCHETYPERecord<EnemySetId, string>./enemies/indexPer-set follower archetype; 'orb' for every bugs variant, 'gunner' for city.

No default export. No additional logic, no wrappers, no side effects — data/enemies.ts is functionally a redirect.

Κ — What lives inside ./enemies/index (and why this barrel matters)

./enemies/index is the actual implementation. The barrel exists because relocating the data triggered an import-path churn that would have rippled across every spawner, bridge, renderer, and ui consumer; keeping data/enemies resolvable means migrations stay local.

Three structural artefacts live there:

  1. The 16×5 rarity-cubebuildEnemyTypes() walks BASE_ARCHETYPES (16 entries, ordered: 8 originals + 8 later-tick variants — brute, wisp, lurker, bombardier, sprinter, spitter, burner, suppressor) and for each one emits 5 EnemyTypeDef rows, one per RARITY_ORDER rarity. Each row’s id = ${archetype}_${rarity}, stats are round(base.* × RARITY_MULTS[rarity].*), tags = [...base.tags, rarity], and tint = RARITY_TINTS[rarity]. Archetype-specific scalars (orb AOE radius/cooldown/damage; mortar / bombardier blast radius + fuse; gunner / suppressor hitscan range + burst count + burst cooldown; field / burner field radius / fade-in / duration / damage; sniper charge time + beam damage) get per-rarity sub-curves layered on top via fixed multiplier arrays indexed by RARITY_ORDER.indexOf(rarity). Net output is 80 cube entries.
  2. The boss-roster patchBOSS_ENEMY_TYPES: EnemyTypeDef[] is a hand-authored 20-entry array (Backwater Killer Croc + Caiman + Caiman basic; Junkrats Stinger / Orca / Pierre / Marco; Industria Loader / Drillbot / Bigbot / Digbot; Solaris Hauler / Valet / Oracle; Prism Citrine / Ruby / Jade / Pearl), each with its own unique id that does NOT follow the ${archetype}_${rarity} convention. The block is appended to ENEMY_TYPES via ENEMY_TYPES.push(...BOSS_ENEMY_TYPES) after the cube build, then ENEMY_TYPE_MAP is rebuilt from the merged list. Boss data files (data/bosses/*.ts) reference these typeIds directly; without the patch the spawner would fall back to a polygon-default with no archetype and the boss anchor HP override path would still work but sprite/behavior dispatch would break.
  3. The hull-shape mapHULL_SHAPE_MAP: Record<string, string> is double-populated. First a loop over RARITY_ORDER writes one entry per ${archetype}_${rarity} pair routing to a base polygon shape: orb/charger/shooter/mortar/gunner/field/sniper/racer map to themselves; sprinterracer, spittershooter, burnerfield, suppressorgunner, brutecharger, wisporb, lurkersniper, bombardiermortar. Then a second loop over BOSS_ENEMY_TYPES writes HULL_SHAPE_MAP[et.id] = et.id (boss id used as its own shape key) so the renderer can route to per-boss ships-v4 sprite atlas regions baked by bridge.ts _bakeBossSprites.

Σ — Where this flows

  • Consumed by: every importer of '../data/enemies' — spawner (engine/enemies/spawner), bridge (engine/bridge), renderer (engine/rendering/enemy-sprites), boss-spawn-profile runtime (engine/enemies/boss-spawn-profile), boss data files (data/bosses/*), AI director, screens, telemetry, sandbox utilities.
  • Source of truth: all behavior lives in ./enemies/index and ./enemies/_types. Per-archetype base records live one-file-per-archetype under ./enemies/: orb.ts, charger.ts, shooter.ts, mortar.ts, gunner.ts, field.ts, sniper.ts, racer.ts, brute.ts, wisp.ts, lurker.ts, bombardier.ts, sprinter.ts, spitter.ts, burner.ts, suppressor.ts.
  • No tuning here: the barrel cannot be edited to change game numbers — every value passes through unchanged from ./enemies/index.
  • Atlas dependency: the boss-roster typeIds are the strings that bridge.ts _bakeBossSprites matches against public/ships-v4/ sprite filenames. Renaming a boss typeId silently breaks its sticker.

Π — Editing notes

  • Adding a new enemy archetype: edit ./enemies/index (push into BASE_ARCHETYPES), add a new per-archetype file, add the variant’s hull-shape entry if it reuses an existing polygon, add it to SET_LEADER_ARCHETYPES[setId] for any set that should treat it as a candidate elite, thread it into the relevant BUGS_*_POOLS band. The barrel does not need to be edited.
  • Adding a new boss-roster entry: edit BOSS_ENEMY_TYPES in ./enemies/index plus the matching boss data file (data/bosses/*.ts). The barrel does not need to be edited.
  • Adding a new value re-export (new constant or function in ./enemies/index): append the name to the export list in this file. Forgetting to update the barrel is the failure mode — ./enemies/index exports the value but no consumer can reach it via '../data/enemies'.
  • Type-only re-exports use the type modifier (type EnemyRarity, type EnemySetId, type EnemyTypeDef, type BaseArchetype); value re-exports do not. Mixing the two in one block is intentional and isolatedModules-safe.

EXTRACT-CANDIDATE

  • The barrel is mechanically equivalent to export * from './enemies/index'. The explicit list was kept to make the public surface auditable, but it duplicates the index’s export wall and drifts every time a new re-export is added. Either commit to export * (drops the audit value) or generate the explicit list from a script at build time (keeps audit, loses manual drift).
  • Type vs value mixing in one statement reads cleanly but means a grep for import.*EnemyRarity finds two hits (this file and ./enemies/_types) — could co-locate all type re-exports into a sibling data/enemies-types.ts for typed-only consumers, leaving this file for value re-exports only.
  • The ENEMY_TIER_COLORS map skips epic (only default/common/uncommon/rare/legendary keys) — a real gap if anything reads the map by rarity name and assumes full coverage. Worth either filling in the epic key or constraining the type to the actual key set.
  • PERSONALITY_PROFILES only covers the 8 original archetypes — brute, wisp, lurker, bombardier, sprinter, spitter, burner, suppressor are absent, so any consumer that reads PERSONALITY_PROFILES[def.archetype] for a t25/t26/t48–t52 variant gets undefined. Either extend the table to all 16 archetypes or document the inherit-from-base contract.
  • The barrel hides where ./enemies/index actually fans out (per-archetype files + boss-roster patch + hull-shape backfill loops). A second roll-up wiki page documenting the rarity-cube generation rules and per-rarity sub-curve multiplier arrays (cooldownMults [1.0, 0.85, 0.7, 0.35, 0.35], burstMults [1.0, 1.0, 1.2, 1.4, 2.0], cdMults [1.0, 0.9, 0.8, 0.65, 0.5], chargeMults [1.0, 0.9, 0.8, 0.7, 0.5]) would prevent rediscovery on every balance pass.