engine/affixes/palette
Pure-data leaf module: per-affix halo colors plus the shared default tuning constants consumed by the affix runtime, the enemy-orbit halo, the world props layer, and the test suite.
PURPOSE
Hold the per-affix VFX palette and the numeric defaults for every world-roaming elite affix in one zero-import file. The split exists so consumers that only need a color tint or a single radius default don’t drag in the affix runtime chain (runtime.ts → data/bosses → engine/affixes/index → data/affixes → back into runtime). That chain is a real cycle that only resolves cleanly when entered through bridge.ts at boot; any other entry point sees partially-bound exports and crashes on hook references like shieldedHooks.onSpawn. Routing the palette through this leaf keeps lookups cycle-free.
OWNS
AFFIX_VFX_PALETTE— the color map. One{ r, g, b }tint per world-roaming affix id:burning_aura,volatile,regenerating,reflective_burst,phasing,summoner,hardened,gravity_well. Boss-only affixes (shielded,shielded_respawn,gated,respawn_as,periodic_invuln,armored) are deliberately absent so they cannot trigger the world-elite halo.getAffixVfxColor(affixId)— lookup function. Returns the matching{ r, g, b }for any key in the palette; returnsnullfor unknown or boss-only ids.- Burning-aura defaults:
BURNING_AURA_DEFAULT_RADIUS(200 world px),BURNING_AURA_DEFAULT_DPS(6). - Volatile defaults:
VOLATILE_DEFAULT_RADIUS(250 world px),VOLATILE_DEFAULT_DAMAGE(25). - Regenerating default:
REGENERATING_DEFAULT_FRAC_PER_SEC(0.015 — 1.5% of max HP per second). - Reflective-burst defaults:
REFLECTIVE_BURST_DEFAULT_THRESHOLD(12 hits),REFLECTIVE_BURST_DEFAULT_RADIUS(180 world px),REFLECTIVE_BURST_DEFAULT_DAMAGE(10). - Phasing defaults:
PHASING_DEFAULT_WINDOW_DURATION(1.2 s of host invulnerability),PHASING_DEFAULT_CYCLE_INTERVAL(8 s window-to-window). - Summoner defaults:
SUMMONER_DEFAULT_HP_THRESHOLD_FRAC(0.5 — proc when host crosses 50% HP),SUMMONER_DEFAULT_MINION_TYPE_ID('orb_common'),SUMMONER_DEFAULT_MINION_COUNT(2),SUMMONER_DEFAULT_SPAWN_DISTANCE_PX(60). - Hardened defaults:
HARDENED_DEFAULT_MAX_REDUCTION(0.5 — host takes 50% of incoming damage at full ramp),HARDENED_DEFAULT_RAMP_DURATION(30 s of host life to reach max reduction). - Gravity-well defaults:
GRAVITY_WELL_PULL_RADIUS(150 world px),GRAVITY_WELL_PULL_ACCEL(60 world px/s²). See DOES NOT — both are currently dead.
READS FROM
Nothing. The file has no imports and depends on no other module. That isolation is the whole point of the split.
PUSHES TO
engine/affixes/runtime— importsAFFIX_VFX_PALETTEdirectly and references entries (AFFIX_VFX_PALETTE.burning_aura,.volatile,.reflective_burst,.regenerating,.gravity_well,.phasing,.summoner,.hardened) inside the per-affix hook implementations to emit colored VFX bursts and trails.engine/vfx/enemy-orbit— importsgetAffixVfxColorand calls it on each affix def attached to an enemy to pick the halo-ring tint for the orbit pass.engine/world/props— importsgetAffixVfxColorand callsgetAffixVfxColor('burning_aura')to color the fire VFX on the Volatile Crystal afterburn proc.data/affixes— re-exports the eight world-roaming default tuning constants (BURNING_AURA_DEFAULT_RADIUS/_DPS,VOLATILE_DEFAULT_RADIUS/_DAMAGE,REGENERATING_DEFAULT_FRAC_PER_SEC,REFLECTIVE_BURST_DEFAULT_THRESHOLD/_RADIUS/_DAMAGE) so authoring-side data tables can reference them without importing the runtime.tests/engine/affixes/elite-affixes.test— importsgetAffixVfxColorto verify every world-affix id resolves to a color and that unknown ids plus boss-only ids (shielded,armored,gated) returnnull.
DOES NOT
- Does not import or depend on any other module. Adding any import would re-introduce the cycle the file was split off to avoid.
- Does not own the affix definitions, hook implementations, or roll logic — those live in
engine/affixes/runtimeandengine/affixes/roll. - Does not host palette entries for boss-only affixes.
shielded,shielded_respawn,gated,respawn_as,periodic_invuln, andarmoredhave no color and resolve tonullthroughgetAffixVfxColor, which suppresses the world-elite halo on bosses. - Does not consume
GRAVITY_WELL_PULL_RADIUSorGRAVITY_WELL_PULL_ACCEL. Both constants are defined and exported here but have no importers anywhere in the codebase. The gravity-well affix’s actual pull radius and acceleration are configured elsewhere; these two are dead constants kept for future reuse. - Does not validate that every entry in
AFFIX_VFX_PALETTEcorresponds to a real affix def, and does not validate the reverse. The palette and the affix def list are kept in sync by hand.
Signals
None. The file is pure data and a single synchronous lookup. No events, no telemetry, no side effects.
Entry points
AFFIX_VFX_PALETTE—constmap; consumed by direct property access (e.g.AFFIX_VFX_PALETTE.burning_aura) in the runtime hooks, and bygetAffixVfxColorfor dynamic lookup.getAffixVfxColor(affixId: string): { r: number; g: number; b: number } | null— string-keyed palette lookup used by the enemy-orbit halo renderer, the world props afterburn VFX, and the affix test suite.- All
*_DEFAULT_*tuning constants — imported individually byengine/affixes/runtimefor hook behavior and re-exported bydata/affixesfor authoring use.
Pattern notes
- Pure-data leaf module with zero imports. Keep it that way. Any consumer that needs a color or a default radius should import from here, not from
runtime.ts. AFFIX_VFX_PALETTEis typedas constso each color entry is a readonly literal and the keys narrow to the union of affix ids —getAffixVfxColoruseskeyof typeof AFFIX_VFX_PALETTEfor the cast after theincheck.- Boss-only affixes are kept out of the palette as the mechanism for suppressing the world-elite halo — there is no separate “is this a world affix?” flag. If a new world-roaming affix is added, its tint must be appended to
AFFIX_VFX_PALETTEor the orbit halo will skip it. - The
gravity_welltint (160, 90, 255 — violet) is intentionally matched to the Magnetar Pulse prop so the two related mechanics share a visual language. The cycle-comment in the runtime header preserves the same intent for any future reorganizing. - Constants are grouped by gameplay slice in source order (world-roaming → Slice 2 phasing/summoner/hardened → Slice 3 gravity-well). When adding a new affix, keep the slice grouping so the data history stays readable.
GRAVITY_WELL_PULL_RADIUSandGRAVITY_WELL_PULL_ACCELare dead exports. Either wire them into the gravity-well hook inruntime.tsor delete them; do not add new dead constants alongside them.