Boss Kind Multipliers

Boss-tier encounters (mini-boss / boss levels) inflate the roster’s base stats so a single BossDef can serve both tiers without duplicating data. The encounter spawner reads a kind: 'mini' | 'boss' argument and applies a fixed HP and damage multiplier to every roster body at spawn time. Depth scaling then layers a per-level ramp on top.

The constants

Defined in data/level-progression.ts:

ConstantValue
BOSS_KIND_HP_MULT_MINI4.0
BOSS_KIND_HP_MULT_BOSS8.0
BOSS_KIND_DAMAGE_MULT_MINI4.0
BOSS_KIND_DAMAGE_MULT_BOSS8.0

Post-v5.162.2 these are the across-the-board 4× buff values — bosses were inflated so they “feel like real walls” rather than slightly chunky mobs. HP and damage move together at the same scale.

Locked ratio: BOSS = 2 × MINI

The boss tier is exactly twice the mini tier on both axes. The header comment on the constants is explicit:

Boss tier doubles mini’s mult so the relative scale (boss = 2× mini) is preserved.

This ratio is the design contract. If the mini constants change, the boss constants must double them — no independent tuning per tier.

Where it fires

spawnBoss(defId, game, world, kind) in engine/boss/encounter.ts picks the base multipliers:

const baseHpMult  = kind === 'boss' ? BOSS_KIND_HP_MULT_BOSS  : BOSS_KIND_HP_MULT_MINI;
const baseDmgMult = kind === 'boss' ? BOSS_KIND_DAMAGE_MULT_BOSS : BOSS_KIND_DAMAGE_MULT_MINI;

The two tiers map to the run sequence (see level-progression):

  • mini — level 3 in normal mode, levels 3/6/9 in challenge mode (mini_boss slots).
  • boss — level 5 in normal mode, level 10 in challenge mode (boss slot).

Default is 'mini' if no kind is passed (dev/playground spawns).

Depth scaling layered on top

After the kind multiplier, encounter.ts applies a linear depth ramp based on where the encounter sits in the run:

depthFrac = (currentLevel - 1) / (totalLevels - 1)
depthMult = 1.0 + 0.5 * depthFrac   // 1.00 at level 1 → 1.50 at final
hpMult  = baseHpMult  * depthMult
dmgMult = baseDmgMult * depthMult

totalLevels is 5 in normal mode and 10 in challenge mode (read from RUN_LEVEL_SEQUENCE / CHALLENGE_LEVEL_SEQUENCE), so the ramp stretches to fit the run length and tracks deep-run progression in both modes.

Effective multipliers per run slot

Normal mode (5 levels):

LevelKindDepth fracDepth multHP multDamage mult
3mini_boss0.501.25×5.00×5.00×
5boss1.001.50×12.00×12.00×

Challenge mode (10 levels):

LevelKindDepth fracDepth multHP multDamage mult
3mini_boss0.221.11×4.44×4.44×
6mini_boss0.561.28×5.11×5.11×
9mini_boss0.891.44×5.78×5.78×
10boss1.001.50×12.00×12.00×

The final boss always sits at 1.50× depth in both modes — same end-state, longer ramp in challenge.

Application to roster bodies

Inside the spawn loop:

  • HPenemy.hp = entry.hp * hpMult; enemy.hpMax = entry.hp * hpMult. Applied to every roster entry, not just the isBoss anchor — shared-health sharers and untargetable carriers also scale.
  • Damageenemy.damageMult = prior * dmgMult. Multiplied into the existing damageMult expando so charger / melee / projectile damage paths all read it consistently. Mini-tier-with-no-depth (dmgMult === 1) is a no-op.

The multiplier is baked into the entity at spawn — there’s no per-frame re-application. Affixes and abilities that read enemy.damageMult downstream pick up the scaled value naturally.

Why one BossDef serves both tiers

Boss data files (data/bosses/*) author one roster at base stats. The kind multiplier lets the same def appear as:

  • A mini-boss mid-run gate fight (level 3) — moderately tanky, hits hard but survivable.
  • A boss final fight (level 5 / 10) — 2× the HP, 2× the damage, the run’s wall.

Without this layer, every boss would need a mini and a boss variant authored separately — twice the data, twice the drift surface. The constant pair keeps that scaling in one place.

  • level-progression.md — the LevelKind sequence that selects when each tier fires.
  • sealed-arenas.md — the arena that bounds the encounter.
  • Per-boss authoring lives in data/bosses/* — see the boss authoring guide.