Affix death drops
What this is
Six of the eight world-roaming elite affixes register an onDeath hook that calls the prop-spawn adapter to drop one themed prop at the host’s death point. The pattern is a Diablo-style “elite drops loot” beat — narrative continuity from affix identity to corpse-prop, palette-matched per pair (regenerating green → mineral vein emerald, phasing ice-blue → comet ice-blue, gravity well violet → magnetar violet, hardened silver/cyan → drone wreck cyan, volatile magenta → volatile crystal magenta, summoner red → scrap pile rust).
The adapter is wired at engine boot in bridge.ts:
setAffixSpawnPropAt((x, y, typeId) => getPropPool().forceSpawnAt(x, y, typeId));
forceSpawnAt bypasses the prop spawner’s cadence + viewport gate + velocity-cone bias and claims a slot directly at the requested world coordinate. Burning-aura and reflective-burst affixes do not have onDeath death-drop hooks beyond reflective_burst’s volatile crystal drop (burning_aura drops nothing; reflective_burst shares the same drop as volatile).
The drop table
| Affix | Prop dropped | Count | Placement | Drop telemetry event |
|---|---|---|---|---|
hardened | drone_wreck | 1 | host (x, y) | affix_proc:hardened_drops_drone |
regenerating | mineral_vein | 1 | host (x, y) | affix_proc:regenerating_drops_mineral |
gravity_well | magnetar_pulse | 1 | host (x, y) | affix_proc:gravity_well_drops_magnetar |
summoner | scrap_pile | 3 | triangle ring, 40 px radius, even-spaced + 0..0.3 rad jitter | affix_proc:summoner_drops_scrap_cluster |
volatile | volatile_crystal | 1 | host (x, y) | affix_proc:volatile_drops_crystal |
reflective_burst | volatile_crystal | 1 | host (x, y) | affix_proc:reflective_burst_drops_volatile |
phasing | comet_fragment | 1 | host (x, y) | affix_proc:phasing_drops_comet |
burning_aura | (none) | — | — | — |
Drop chance is 100% per affix instance — there is no roll inside onDeath. World-roaming affixes are already gated to rare-tier+ elite-pack leaders by rollEliteAffixes, so rarity gating happens upstream.
Summoner’s drop count is governed by two constants local to the summonerHooks.onDeath body:
| Constant | Value | Role |
|---|---|---|
SUMMONER_DROP_COUNT | 3 | Number of scrap_pile props attempted |
SUMMONER_DROP_RADIUS_PX | 40 | Ring radius around the host’s death point |
Pool-cap fail-silent rule
forceSpawnAt returns a boolean: true when a pool slot was claimed, false when the pool was already at POOL_SIZE capacity. Affix onDeath hooks treat that return value purely as a telemetry signal — they record dropped ? 1 : 0 (or, for summoner, the number of successful spawns out of 3) on the prop-drop telemetry event and exit. No retry, no defer, no error. A pool-full state results in a missed drop and an observable 0 in cloud telemetry, but the affix’s other death-time effects (volatile’s burst + crystal chain, reflective_burst’s preceding bursts) still fire normally.
This is the standard fail-silent pattern for forceSpawnAt consumers — it was first established by the mineral-cascade and supply-pod-cascade chain synergies inside props.ts before affixes claimed the same adapter at affix-system tick 1 (volatile crystal), tick 9 (phasing/summoner/hardened, adding three more consumers), and tick 23 (gravity well).
Crossover breaks
The dropped props each carry their own break-time synergies inside breakProp in engine/world/props.ts. Two of those synergies extend the affix theme back out as a recursive narrative beat — the elite dies, the prop lingers, the prop breaks, and a new affix-palette effect fires:
| Prop dropped | Break-synergy chance | Effect on break |
|---|---|---|
volatile_crystal | 25% | burning_aura-palette afterburn — orange fire particles + single proximity damage tick (radius-gated) |
mineral_vein | 30% | Chain cascade — forceSpawnAt 2× comet_fragment at the break point |
comet_fragment | 35% | Ship-velocity speed boost — multiplies current velocity, capped at a max-ratio of max speed |
drone_wreck | 30% | Ship invuln grant — engages ship.invulnerable + invulnTimer for a fixed duration |
scrap_pile | 15% | XP-orb magnet pulse — attracts nearby orbs to ship |
magnetar_pulse | 100% | Enemy gravity pull — drags every enemy within MAGNETAR_PULL_RADIUS toward the break point |
The volatile-crystal afterburn is the explicit reverse-loop: a volatile elite drops a volatile_crystal, the crystal has a 25% chance on break to fire a burning_aura-palette damage tick — so killing one world-roaming affix produces a chance-gated proc themed on a second world-roaming affix’s palette. The afterburn uses the burning_aura palette via getAffixVfxColor('burning_aura') and routes player damage through the normal damagePlayer chain so it respects invuln, shield, and reduction.