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.
| Symbol | Kind | Origin module | Role |
|---|---|---|---|
EnemyRarity | type | ./enemies/_types | Union 'common' | 'uncommon' | 'rare' | 'epic' | 'legendary'. |
EnemySetId | type | ./enemies/_types | Tagged spawn-pool flavor id ('bugs', 'city', 'bugs_mortar', etc.). |
EnemyTypeDef | type | ./enemies/_types | Per-id runtime def (hp, speed, radius, xp, tags, archetype-specific extras). |
BaseArchetype | type | ./enemies/_types | Per-archetype base record fed into the rarity-cube builder. |
RARITY_TINTS | Record<EnemyRarity, string> | ./enemies/_types | Per-rarity hex tint stamped on every cube entry. |
RARITY_MULTS | Record<EnemyRarity, {hp, speed, radius, xp, damage}> | ./enemies/_types | Per-rarity multiplicative scalars applied to base stats. |
RARITY_ORDER | EnemyRarity[] | ./enemies/_types | Canonical ordering ['common','uncommon','rare','epic','legendary']. Used as the inner-loop axis and as an index for per-rarity sub-curves. |
ENEMY_TYPES | EnemyTypeDef[] | ./enemies/index | Materialized list: 16 archetypes × 5 rarities = 80 cube entries, plus 20 hand-authored boss-roster entries appended. |
ENEMY_TYPE_MAP | Record<string, EnemyTypeDef> | ./enemies/index | Fast id → def lookup built from ENEMY_TYPES. |
ENEMY_TIER_COLORS | Record<string, string> | ./enemies/index | Renderer-side tier hex map (default/common/uncommon/rare/legendary). Skips epic. |
PERSONALITY_PROFILES | Record<string, {c,t,s}> | ./enemies/index | Per-archetype {c, t, s} triple. Only the 8 original archetypes appear; t25/t26/t48–t52 variants inherit nothing here. |
ENEMY_COLLISION_SCALE | 3.85 | ./enemies/index | Multiplier on radius for collision tests. |
getEnemyCollisionRadius | (e: {radius}) => number | ./enemies/index | e.radius * ENEMY_COLLISION_SCALE. |
SPAWN_POOLS | Array<{progressMin, progressMax, types}> | ./enemies/index | Default bugs pool (Landing Site). 5 progress bands, orb-dominated. |
getSpawnPool | (progress: number) => string[] | ./enemies/index | Pick band for default SPAWN_POOLS by progress. |
getSpawnPoolForSet | (setId, progress) => string[] | ./enemies/index | Pick 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/index | Roll 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/index | Same as above but on the default progress pool; follower archetype equals leader archetype. |
HULL_SHAPE_MAP | Record<string, string> | ./enemies/index | Per-id polygon-shape key consumed by the renderer’s polygon fallback. Covers all 16 archetypes × 5 rarities + every boss-roster id. |
SET_SWARM_ARCHETYPE | Record<EnemySetId, string> | ./enemies/index | Per-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:
- The 16×5 rarity-cube —
buildEnemyTypes()walksBASE_ARCHETYPES(16 entries, ordered: 8 originals + 8 later-tick variants —brute,wisp,lurker,bombardier,sprinter,spitter,burner,suppressor) and for each one emits 5EnemyTypeDefrows, one perRARITY_ORDERrarity. Each row’sid = ${archetype}_${rarity}, stats areround(base.* × RARITY_MULTS[rarity].*),tags = [...base.tags, rarity], andtint = 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 byRARITY_ORDER.indexOf(rarity). Net output is 80 cube entries. - The boss-roster patch —
BOSS_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 uniqueidthat does NOT follow the${archetype}_${rarity}convention. The block is appended toENEMY_TYPESviaENEMY_TYPES.push(...BOSS_ENEMY_TYPES)after the cube build, thenENEMY_TYPE_MAPis 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. - The hull-shape map —
HULL_SHAPE_MAP: Record<string, string>is double-populated. First a loop overRARITY_ORDERwrites one entry per${archetype}_${rarity}pair routing to a base polygon shape:orb/charger/shooter/mortar/gunner/field/sniper/racermap to themselves;sprinter→racer,spitter→shooter,burner→field,suppressor→gunner,brute→charger,wisp→orb,lurker→sniper,bombardier→mortar. Then a second loop overBOSS_ENEMY_TYPESwritesHULL_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 bybridge.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/indexand./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 _bakeBossSpritesmatches againstpublic/ships-v4/sprite filenames. Renaming a boss typeId silently breaks its sticker.
Π — Editing notes
- Adding a new enemy archetype: edit
./enemies/index(push intoBASE_ARCHETYPES), add a new per-archetype file, add the variant’s hull-shape entry if it reuses an existing polygon, add it toSET_LEADER_ARCHETYPES[setId]for any set that should treat it as a candidate elite, thread it into the relevantBUGS_*_POOLSband. The barrel does not need to be edited. - Adding a new boss-roster entry: edit
BOSS_ENEMY_TYPESin./enemies/indexplus 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/indexexports the value but no consumer can reach it via'../data/enemies'. - Type-only re-exports use the
typemodifier (type EnemyRarity,type EnemySetId,type EnemyTypeDef,type BaseArchetype); value re-exports do not. Mixing the two in one block is intentional andisolatedModules-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 toexport *(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
grepforimport.*EnemyRarityfinds two hits (this file and./enemies/_types) — could co-locate all type re-exports into a siblingdata/enemies-types.tsfor typed-only consumers, leaving this file for value re-exports only. - The
ENEMY_TIER_COLORSmap skipsepic(onlydefault/common/uncommon/rare/legendarykeys) — a real gap if anything reads the map by rarity name and assumes full coverage. Worth either filling in theepickey or constraining the type to the actual key set. PERSONALITY_PROFILESonly covers the 8 original archetypes —brute,wisp,lurker,bombardier,sprinter,spitter,burner,suppressorare absent, so any consumer that readsPERSONALITY_PROFILES[def.archetype]for a t25/t26/t48–t52 variant getsundefined. Either extend the table to all 16 archetypes or document the inherit-from-base contract.- The barrel hides where
./enemies/indexactually 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.