data/affixes.ts
Affix library — data table that composes runtime behavior primitives by id. Two pools: boss-flavored affixes (assume BossArena + anchor enemies) and world-roaming elite affixes (rolled at spawn time on elite-pack leaders). Hook implementations live in engine/affixes/runtime.ts; this file only declares the defs. The affix dispatcher (engine/affixes/index.ts) auto-registers the catalog at module-init.
λ — Charge across the river
| Export | Kind | Purpose |
|---|---|---|
WORLD_ELITE_AFFIX_DEFS | AffixDef[] | World-roaming elite affix definitions. No BossArena required. Rolled by engine/affixes/roll.rollEliteAffixes(). |
WORLD_ELITE_AFFIX_IDS | string[] | Ordered ids of WORLD_ELITE_AFFIX_DEFS. Roll pool. |
AFFIX_DEFS | AffixDef[] | Full catalog = BOSS_AFFIX_DEFS ⧺ WORLD_ELITE_AFFIX_DEFS. Consumed by the dispatcher. |
Re-exported constants:
| Constant | From | Source-of-truth domain |
|---|---|---|
SHIELDED_DEFAULT_ANCHOR_COUNT | engine/affixes/runtime | Shielded affix anchor count |
SHIELDED_DEFAULT_ANCHOR_TYPE | engine/affixes/runtime | Shielded anchor enemy id |
SHIELDED_ANCHOR_RING_FRAC | engine/affixes/runtime | Ring radius as fraction of arena |
SHIELDED_RESPAWN_DEFAULT_INTERVAL | engine/affixes/runtime | Shielded-respawn cadence (s) |
ARMORED_DEFAULT_MULT | engine/affixes/runtime | Armored incoming-damage multiplier |
BURNING_AURA_DEFAULT_RADIUS | engine/affixes/palette | Burning-aura radius |
BURNING_AURA_DEFAULT_DPS | engine/affixes/palette | Burning-aura damage-per-second |
VOLATILE_DEFAULT_RADIUS | engine/affixes/palette | Volatile on-death blast radius |
VOLATILE_DEFAULT_DAMAGE | engine/affixes/palette | Volatile on-death blast damage |
REGENERATING_DEFAULT_FRAC_PER_SEC | engine/affixes/palette | Regen as fraction of max HP / sec |
REFLECTIVE_BURST_DEFAULT_THRESHOLD | engine/affixes/palette | Reflective-burst HP-loss trigger |
REFLECTIVE_BURST_DEFAULT_RADIUS | engine/affixes/palette | Reflective-burst blast radius |
REFLECTIVE_BURST_DEFAULT_DAMAGE | engine/affixes/palette | Reflective-burst blast damage |
Κ — The dispatcher chain
Each AffixDef binds an id to a priority and up to four lifecycle hooks (onSpawn, onUpdate, onDeath, filterIncomingDamage) imported from engine/affixes/runtime. The dispatcher iterates affixes in priority order (highest first) per event.
BOSS_AFFIX_DEFS (module-private)
Excluded from the world-roaming elite-affix roll pool.
| id | Priority | Hooks |
|---|---|---|
shielded | 100 | onSpawn, filterIncomingDamage |
shielded_respawn | 100 | onSpawn, onUpdate, filterIncomingDamage |
gated | 90 | filterIncomingDamage |
respawn_as | 95 | onUpdate |
periodic_invuln | 85 | onUpdate, filterIncomingDamage |
reflective | 80 | filterIncomingDamage |
armored | 20 | filterIncomingDamage |
WORLD_ELITE_AFFIX_DEFS (exported)
| id | Priority | Hooks | Notes |
|---|---|---|---|
burning_aura | 60 | onUpdate | — |
volatile | 60 | onDeath | — |
regenerating | 50 | onUpdate | — |
reflective_burst | 70 | filterIncomingDamage | — |
phasing | 75 | onUpdate, filterIncomingDamage | Fires before reflective_burst (70) so phasing invuln short-circuits the burst chain. |
summoner | 40 | onUpdate | Fires AFTER damage filters; host must actually drop below HP threshold for the trigger to land. |
hardened | 25 | onUpdate, filterIncomingDamage | Fires close to last so phasing / reflective_burst get their say first. |
gravity_well | 55 | onUpdate, onDeath | Mostly visual tell + death-drop; priority placed in mid-band for clean log ordering. |
Σ — Where this flows
- Consumed by:
engine/affixes/index.ts(dispatcher auto-registersAFFIX_DEFSat module-init). - Roll pool:
engine/affixes/roll.rollEliteAffixes()readsWORLD_ELITE_AFFIX_IDS. - Hook implementations: all hooks resolve to functions exported from
engine/affixes/runtime.ts. - Tuning constants: balance lives in
engine/affixes/runtime.ts(shielded / armored) andengine/affixes/palette.ts(burning_aura / volatile / regenerating / reflective_burst). Re-exported here for downstream importers. - Slice provenance: Slice 2 (tick 9) added
phasing,summoner,hardened. Slice 3 (tick 23) addedgravity_wellas the 8th world-roaming affix.
Π — Priority bands
The priority field defines dispatch order within an affix list. The catalog uses three rough bands:
- 100–80 — gating / invulnerability (
shielded,shielded_respawn,respawn_as,gated,periodic_invuln,reflective). Run first so they can short-circuit damage entirely. - 75–50 — reactive damage / behavior (
phasing,reflective_burst,burning_aura,volatile,gravity_well,regenerating). Run middle. - 40–20 — late triggers / multipliers (
summoner,hardened,armored). Run after gating + reactive so they see the post-filter state.
EXTRACT-CANDIDATE
- Hard-coded priority literals (
100,95,90,85,80,75,70,60,55,50,40,25,20) could be named bands (AFFIX_PRIORITY_GATE,AFFIX_PRIORITY_REACTIVE,AFFIX_PRIORITY_LATE) to make the dispatch model explicit and prevent drift between affixes. BOSS_AFFIX_DEFSis module-private butWORLD_ELITE_AFFIX_DEFSis exported — asymmetry. Either export both for parity (so tests can introspect the boss pool) or document why the boss list intentionally has no roll-pool sibling.- The re-export block spans two source modules (
runtimeandpalette). A consolidatedengine/affixes/constants.tsbarrel would letdata/affixes.tsre-export from one place and clarify that tuning lives there, not here. WORLD_ELITE_AFFIX_IDSis derived fromWORLD_ELITE_AFFIX_DEFS.map((d) => d.id). If the roll pool ever needs weighting, thisstring[]would extract cleanly toArray<{ id: string; weight: number }>co-located here.