Boss Bar Resolution
The HUD boss bar is derived live every frame from world.enemies — never stored. Its identity (name + color) and its pool (current + max HP) come from two different surfaces: identity resolves through the anchor, the pool sums across every shared body. The two split lets multi-body bosses like Prism Cluster show one unified bar (“PRISM CLUSTER”) even though the HP pool spans several enemies, and lets the cap drop dynamically as sharing bodies die mid-fight.
Identity vs. pool — two separate resolutions
The HUD’s getBossBar(world) reads four fields and returns null when no sharer is alive:
| Field | Source | How it resolves |
|---|---|---|
name | Bar identity | Active BossDef.displayName via game._activeBossDefId lookup. Falls back to the anchor’s enemy.displayName (the enemy flagged isBoss: true) if the def lookup misses. Final fallback: literal 'BOSS'. |
color | Bar identity | Active BossDef.barColor. Same fallback chain through the anchor’s enemy.barColor, then '#ff4444'. |
hp | Bar pool | Sum of enemy.hp across every alive enemy with sharesHealthWithBoss === true. |
hpMax | Bar pool | Sum of enemy.hpMax across the same alive sharers. |
Anchor enemies (isBoss: true) carry the BossDef’s displayName so the fallback path renders the correct top-level name. Per-entry display names like "CITRINE" or "MARCO-A" exist on non-anchor sharers for inspection/debug only — the HUD never surfaces them.
The sharer-sum loop
Both the HUD’s getBossBar (hud.ts:80) and damage’s getBossBarHpMax (damage.ts:217) walk world.enemies and apply identical predicates before counting a body:
enemy.alive === trueenemy.sharesHealthWithBoss === trueenemy._frozenForLag === falseandenemy._dying === falseenemy.hp > 0
A body that fails any predicate contributes nothing to bar HP or bar max. The cap drops as sharing bodies die — once the last living sharer fails the predicate, the bar disappears entirely (anySharer === false → return null).
This live-sum has two consequences:
- Asymmetric rosters work transparently. Prism Cluster flags multiple gem bodies as anchors of equal weight; each contributes its
hpMaxto the pool, and damage to any gem reduces that gem’s ownhp. - Damage caps scale with the live pool, not per-body.
damage.tsusesgetBossBarHpMax(world) * 0.02 + 30as the per-hit cap on shared-health bodies. When sharing bodies die, the cap shrinks alongside the pool — preventing late-fight nuke spikes when only one sharer remains.
Two death signals — anchor vs. final sharer
The death path in damage.ts (lines 405–437) routes a dying boss-roster enemy through one of three branches based on flags:
| Flag combo | Signal fired |
|---|---|
_isBossAnchor === true (anchor-only frame) | boss_anchor_destroyed |
sharesHealthWithBoss === true and other sharers still alive | boss_body_kill |
sharesHealthWithBoss === true and final sharer (hasRemainingBossSharer returns false) | boss_body_kill and boss_kill |
The final-sharer branch also snapshots enemy.x/y into game._lastBossDeathX/_lastBossDeathY so the encounter-end portal lands on the killing blow’s position — required for asymmetric rosters where the last body to die isn’t the isBoss anchor (e.g. Prism Cluster’s Topaz outliving Citrine).
boss_kill is re-fired once by onBossEncounterEnd('win') carrying def.reward.xp, def.reward.currency, and def.id so the reward contract sees a guaranteed close. Listeners must be idempotent because boss_kill fires twice on a clean win: once at last-sharer death, once at encounter end.
Why two signals, not one
boss_anchor_destroyed and boss_kill exist as separate signals because anchor destruction and pool depletion aren’t the same event:
- Anchor death without pool depletion — possible in scripted multi-anchor encounters.
boss_anchor_destroyedlets cinematic hooks fire on the structurally significant body while combat continues against remaining sharers. - Pool depletion without isBoss anchor death — asymmetric rosters where the last sharer isn’t flagged
isBoss.boss_killfires on the final sharer regardless of which flag that body carried.
Anchor death also suppresses enemy_kill / streak / elite / XP signals (§6 ruling) — anchors are encounter-scale events, not roll-up combat kills.
Related
- Boss Anchor Pattern — full anatomy of
isBoss,sharesHealthWithBoss, anduntargetableflags - Boss Encounter Lifecycle — spawn / teardown / reward flow