Stat Modes

Every modifier applied through Modifiers.add declares a mode argument that controls how its value combines with the entity’s base stat. The mode is one of 'flat', 'percent', or 'set', and it is the single knob that determines whether a buff stacks additively or multiplicatively. Same numeric value, different mode, completely different outcome.

The three modes

  • flat — value is added directly to the running total before any multiplier is applied. Stacks additively with every other flat modifier on the same stat. A +5 flat on top of a base 20 contributes +5, regardless of what other modifiers exist.
  • percent — value is treated as a multiplier contribution. All percent modifiers on a stat are summed, then 1 + that sum becomes the final multiplier. +0.25 percent means “+25% of the post-flat total.” Two +0.25 percent modifiers stack to +50%, not +56.25% — percents are additive among themselves, multiplicative against flat.
  • set — overrides the recalculated stat entirely. Last set wins. Used for hard overrides like “stat is now exactly this number.”

Final-stat formula

The recalculation in Modifiers.recalc is:

final = (base + Σ(flat × stacks)) × max(0, 1 + Σ(percent × stacks))

If any set modifier exists for that stat, it replaces the entire result.

The multiplier is floored at 0. This matters because percent contributions sum additively, so a stat with many negative-percent modifiers (e.g. -0.10 drag per Speed pick × 12 picks = -1.20) would otherwise produce a negative multiplier and flip the stat’s sign. A fully-negated stat sits at zero, never below.

Order matters

Flat is applied before percent. This is the central rule. Consequences:

  • A +10 flat followed by +50% percent on a base 100 gives (100 + 10) × 1.5 = 165, not 100 + 10 + 50 = 160 and not 100 × 1.5 + 10 = 160.
  • Adding flat HP before stacking percent HP multipliers means the flat bonus gets scaled too. Designers leverage this — flat early-game pickups become disproportionately strong as percent multipliers pile up.
  • Two sources that grant “the same buff” but with different modes are not equivalent. +20% damage and +0.2 × base damage as flat diverge the moment any other modifier touches the stat.

Stacks

Each modifier carries a stacks field (default 1). When recalculating, the contribution is value × stacks. Refresh-mode modifiers increment stacks on re-application (capped by maxStacks); independent-mode modifiers are added as new entries. Either way, the math above operates on the per-mod value × stacks product.

Who uses what

All modifier sources feed through the same Modifiers.add call and the same formula, so the mode argument is the only place where source intent is encoded:

  • Artifacts — typically percent for scaling multipliers (e.g. +15% fire rate); flat for additive bumps to stats with small base values (e.g. +1 projectile).
  • Level-up picks — mix of flat and percent depending on the picked stat’s curve. Speed, drag, and similar low-base stats lean flat; damage, health, fire-rate lean percent.
  • Mods — same rules as level-up picks; the mod-roll system selects mode per affix definition.
  • Temporary buffs (heat, status effects) — usually percent with a non-zero duration so they expire via Modifiers.tick.
  • Hard overridesset mode, used sparingly (e.g. forcing a stat to a fixed value during a scripted phase).

Because every source funnels through the same recalc, the mode choice at the call site is the entire authoring contract — there is no separate per-source stacking rule.

  • engine/core/modifiers.ts — implementation of Modifiers.add, Modifiers.recalc, stack handling.
  • gameplay/concepts/ — sibling concept pages on artifacts, level-up picks, and mods describe which mode each source picks per affix.