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 flaton top of a base20contributes+5, regardless of what other modifiers exist.percent— value is treated as a multiplier contribution. All percent modifiers on a stat are summed, then1 +that sum becomes the final multiplier.+0.25 percentmeans “+25% of the post-flat total.” Two+0.25 percentmodifiers stack to+50%, not+56.25%— percents are additive among themselves, multiplicative against flat.set— overrides the recalculated stat entirely. Lastsetwins. 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 flatfollowed by+50% percenton a base100gives(100 + 10) × 1.5 = 165, not100 + 10 + 50 = 160and not100 × 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% damageand+0.2 × base damage as flatdiverge 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
percentfor scaling multipliers (e.g.+15% fire rate);flatfor additive bumps to stats with small base values (e.g.+1 projectile). - Level-up picks — mix of
flatandpercentdepending on the picked stat’s curve. Speed, drag, and similar low-base stats leanflat; damage, health, fire-rate leanpercent. - Mods — same rules as level-up picks; the mod-roll system selects mode per affix definition.
- Temporary buffs (heat, status effects) — usually
percentwith a non-zerodurationso they expire viaModifiers.tick. - Hard overrides —
setmode, 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.
Related
engine/core/modifiers.ts— implementation ofModifiers.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.