Combat & Damage Calculation

The full damage pipeline — hit chance, damage, mitigation order, block, resistances, crushing blow, life leech, and attack speed — with the real formulas.

On this page

This page is the exact reference for how a swing turns into damage in Echoes of Theron. Every number below is read straight from the engine (src/core/formulas.ts, src/core/simulation.ts) and its hand-tunable constants (src/data/balance/). For a visual box-by-box version, see the Damage Flow charts.

Overview — the damage pipeline

Each landed swing resolves in a strict order. Physical and elemental damage are separate hits that ride the same swing and obey opposite rules.

  1. Roll to hit — compare the attacker’s Attack Rating (AR) to the defender’s Defense Rating (DR) on a tanh curve, then apply a small level-difference shift. Miss → 0 damage.
  2. Roll raw damage — a uniform integer between the fighter’s Min and Max Damage.
  3. Outgoing multipliers — skill damage multiplier, then the passive/rage multiplier (Wrath + Apex + Rampage). Round → scaledRaw.
  4. Mitigation (only when the hero is the defender) — block first, then the capped percentage-reduction bucket, then flat reduction.
  5. On-hit effects — life leech, crushing blow, thorns, elemental riders, bleed/DoTs, procs.

Two asymmetries are critical and never change:

  • Physical damage to enemies ignores resistance, AR and DR, and enemies never block. AR/DR only decide whether a swing lands, never how hard.
  • Elemental damage is a separate flat-per-hit rider. It bypasses the skill multiplier and Wrath, and it is the only player damage reduced by the target’s resistance.

Hit chance — Attack Rating vs Defense Rating

AR and DR feed a smooth log-ratio sigmoid centred on 70% at AR = DR. This is the whole of what AR/DR do to offense (Defense Rating additionally grants the defender a small capped physical reduction — see Incoming mitigation).

From hitChanceFromRatings(AR, DR):

logRatio = ln( AR / DR )
curve    = tanh( logRatio × 1.1 )
chance   = 0.70 + curve × (0.95 − 0.70)   if curve ≥ 0   (toward the ceiling)
         = 0.70 + curve × (0.70 − 0.05)   if curve < 0   (toward the floor)
chance   = clamp(chance, 0.05, 0.95)

AR and DR are each floored at 1 before the ratio. The constants (FORMULA_CONFIG):

ConstantValueMeaning
hitChanceAtEqualRatings0.70Hit chance when AR = DR
hitChanceCurveSteepness1.1Multiplier on ln(AR/DR) inside tanh
maxHitChance0.95Ceiling, even with enormous AR
minHitChance0.05Floor, even against overwhelming DR

The range is asymmetric: an AR advantage climbs from 70% toward the 95% ceiling (a 25-point band), while an AR deficit falls from 70% toward the 5% floor (a 65-point band), so being out-rated costs more than being over-rated gains.

Worked hit-chance values

ARDRln(AR/DR)tanh(×1.1)Hit chance
1001000070.0%
2001000.693+0.64286.1%
4001001.386+0.91092.7%
50100−0.693−0.64228.2%
25100−1.386−0.91010.9%

Flat hit-chance bonus

Some affixes and passives add a flat bonus on top of the sigmoid via applyFlatHitChanceBonus, which re-clamps to [0.05, 0.95]. Gear flat hit-chance is capped at +30% (MAX_HIT_CHANCE_FLAT_BONUS = 0.3); the combined total (gear + Precision + tavern drinks) is capped at +50% in aggregate before the clamp.

Level difference

The level gap between attacker and defender changes the chance once more, at every level.

Enemies attacking the hero get a flat shift through the same applyFlatHitChanceBonus path (levelDeltaHitChanceShift), so it obeys the same [0.05, 0.95] clamp:

shift = clamp( (enemyLevel − heroLevel) × 0.01, −0.10, +0.10 )

The hero attacking (playerLevelHitChanceRule):

  • Out-levelling the enemy adds the same points: +1% per level, at most +10%.
  • An enemy above the hero takes a share of the hero’s hit chance instead of points: 1% of it per level, at most 10% (10 levels and more). It applies after the flat bonus and is clamped into [0.05, 0.95] again:
chance = clamp( chanceWithFlatBonus × (1 − min(levelsAbove × 0.01, 0.10)), 0.05, 0.95 )
Hit chance beforeEnemy 10+ levels aboveLost
80%72%a tenth
40%36%a tenth
20%18%a tenth

Every hero loses the same part of their hits, however much Attack Rating they have (a flat 10 points would have cost the 20% hero half of them). The rule is deliberately ungated: in the Endless Delve, where enemy level climbs past the hero cap of 99, it settles at a constant tenth. Constants (FORMULA_CONFIG): levelDeltaHitChancePerLevel = 0.01, levelDeltaHitChanceMaxShift = 0.10, playerLevelPenaltySharePerLevel = 0.01, playerLevelPenaltyMaxShare = 0.10.

Derived combat stats

These are built once per stat-panel rebuild in computePlayerStats. Attributes are floored at 0; each (1 + percent) multiplier is floored at 0.2 (MIN_STAT_MULTIPLIER) so a −100% roll can never fully zero a stat. Per-point coefficients (src/data/balance/player.ts):

AttributePer-point effect
Vitality+5 Max HP (HERO_HP_PER_VITALITY)
Strength+0.25% physical damage (STR_DAMAGE_PERCENT_PER_POINT = 0.0025) and +3 Attack Rating (HERO_AR_PER_STRENGTH)
Dexterity+4 Attack Rating (HERO_AR_PER_DEXTERITY) and +0.05% attack speed (DEX_ATTACK_SPEED_PER_POINT = 0.0005)
Constitution+5 Defense Rating (HERO_DR_PER_CONSTITUTION), +0.06% physical reduction (CON_PHYS_REDUCTION_PER_POINT = 0.0006), and flat per-hit reduction of 0.0025 × level (CON_FLAT_REDUCTION_PER_POINT_PER_LEVEL)

Per level (independent of attributes): +3 Max HP, +4 Attack Rating, +2 Defense Rating (HERO_*_PER_LEVEL). A fresh hero starts at base 70 HP, 50 AR, 1.12 s interval.

Max HP

MaxHP = max( 12, (baseHp + flatHpAffix + 5×VIT + 3×level) × (1 + hp%) )

Attack Rating

AR = max( 1, baseAR + flatARaffix + 4×DEX + 3×STR + 4×level )
AR × (1 + gearAR% + trainingAR% + drinkAR% + Σ buffAR%)     ← one pooled multiplier

Note the class-rebalance coefficients: DEX gives 4 AR/pt (not 5) and STR gives 3 AR/pt, so Strength buys accuracy and DEX no longer monopolises it; 4 AR per level keeps every hero’s hit chance from sliding as enemy Defense Rating climbs. All AR% sources — unique-item affixes, Leather armor / Blades weapon training, the tavern AR drink and active buff skills — are summed into a single additive pool and applied once on top of the flat AR (in applyPlayerDerivedStats); Strike’s AR% is a separate multiplier after that. Rank 1 is the bare 0.125% base and S(rank) is the skill’s accumulated effect steps — 27 at rank 20, so a maxed Strike is AR × 1.035. Magic/Rare gear only rolls flat AR.

Defense Rating

DR = max( 1, (baseDR + flatDRaffix + 5×CON + 2×level) × (1 + DR%) )

Min / Max damage

Flat-damage affixes are speed-normalised by the weapon’s own interval so they yield equal DPS on fast and slow weapons; the weapon’s own min/max (and the enhanced-damage % that scales it) are already DPS-normalised and left untouched. Strength is a speed-neutral multiplier, not flat per hit.

Scope: Flat Damage and Enhanced Damage rolled on a weapon boost that weapon only, and the same affixes on a shield boost only its Shield-Strike damage — both are already folded into the range printed on the item’s own card (class passive included). From every other slot (ring, amulet, armour) they are character-wide and add on top of whichever damage source you currently swing.

An unrevealed unique contributes its base item stats in full but none of its affixes — an Enhanced Damage or Flat Damage roll adds nothing until the Trader reveals the item.

normFlat = round( (flatDamageAffix_weapon + flatDamageAffix_otherSlots) × weaponBaseInterval / 1.0 )
                                                                   # RAGE_REFERENCE_INTERVAL = 1.0
edOther  = max( 0.2, 1 + enhancedDamage% from non-weapon, non-shield slots )
strMult  = max( 0.2, 1 + 0.0025 × STR )
MinDamage = max( 1, (weaponMin + normFlat) × edOther × strMult )
MaxDamage = max( 2, (weaponMax + normFlat) × edOther × strMult )

weaponMin/weaponMax already include the weapon’s own enhanced-damage % (which scales weapon base only, before flat and before Strength) plus any smith-temper / witch-enchant. After the stored stat is built, the flow chart’s later layers — the class weapon-class multiplier and War Cry — are applied to the stored damage stat before the swing. There is no critical-hit chance; variance comes only from the Min–Max roll and elemental rolls.

Two-handed weapons

Every one-handed family sits on the same base DPS per tier — a gilded knife at 1.0 s and a gilded hammer at 3.5 s roll very different dice but land the same damage per second, so the choice between them is speed class, hit size and weight, never raw throughput. A two-handed weapon’s dice are that same baseline scaled by one constant:

perHitDamage = oneHandedBaseDPS × TWO_HANDED_DAMAGE_MULT × weaponBaseInterval   # mult = 1.75

Greatswords and greataxes (2.5 s), claymores and halberds (3.0 s) and mauls (3.5 s) — and the staves line, Quarterstaff (1.0 s), Bo (1.5 s), Battlestaff (2.0 s), Ironstaff (2.5 s) and Khakkhara (3.0 s) — therefore all land on 1.75× the one-handed DPS and differ only in how big a single hit is. Everything downstream — flat-damage normalisation, Strength, enhanced damage, the skill multiplier, Wrath — treats a two-hander exactly like any other weapon; the premium is paid for by the empty off-hand (no block, no shield defense, and the shield-strike skills fall back to a plain attack). See Item Base Types.

Dual wield

Two one-handed weapons run two independent swing timers, each at its own interval, and each timer resolves the full selected main skill. Every hand’s swing is scaled by two constants:

handDamage = ownWeaponRoll × DUAL_WIELD_DAMAGE_MULT   # mult = 0.65
handEffect = nominalEffect  × DUAL_WIELD_EFFECT_MULT  # mult = 0.50

Two hands at 0.65 damage add up to 130% of a single weapon’s output — the pair’s gain over one weapon is 30% plus a second affix set, not 100%. Per-hit effects sit lower, at 0.50 per hand: two hands proc at 100% of a single weapon’s rate, so swinging twice as often does not also double your rage, bleeds, stuns, leech or crushing blows.

What scales per hand (the EFFECT multiplier): Rage / Valor per hit and the On Hit affixes, crushing-blow chance — the gear/training chance and a skill’s built-in rider such as Crush’s —, Aegis’s and Smite’s stun chance, Rend’s bleed fraction, Shatter’s armor shred per hit, Reaver’s leech per rank, the monk riders’ elemental share, and Flurry’s attack-speed stack. The elemental status procs (ignite, freeze, lightning stun) scale their chance the same way; their damage needs no scaling because each hand carries its own weapon’s flat elemental values at the same 0.65 damage scale.

What applies once, never per weapon: Berserk’s defense penalty, the attack-speed multipliers of Crush / Berserk / Flurry (they scale each timer, they do not stack), and the main skill’s rank.

Per-hand details worth knowing:

  • Each hand rolls its own weapon’s dice and its own weapon-scoped affixes (flat damage, enhanced damage, flat elemental, life leech, Rage On Hit, attack speed); everything character-level counts once for both.
  • Accuracy is per hand too. Attack Rating, Attack Rating % and Hit Chance rolled on a weapon only sharpen that weapon’s swings, so each hand rolls to hit with its own attack rating and its own flat hit chance. The same affixes on armour, an amulet, a ring or a shield stay character-wide and count for both hands, as do the material-set bonus, passive skills, buffs and Strike’s own Attack Rating perk. While dual wielding the hero box groups its Damage and Hit Chance rows under a Strike (Main Hand) and a Strike (Off-Hand) header — the selected main skill’s name plus the hand — and each hand gets its own swing bar. Active skills, shield strikes and counter-attacks always use the main hand’s rating.
  • The class weapon multiplier is evaluated per hand, from the speed class of the weapon actually swinging — a Duelist swinging a fast off-hand knife and a slow main-hand hammer gets +20% on one and −20% on the other.
  • Zeal keeps its three hits per hand, each at Zeal’s per-hit damage × 0.65; the Rage On Hit split across the hits works as it always did, then takes the 0.50 effect scale.
  • Weapon-training bonuses are main-hand only — T1–T5 bonuses and the T6 procs (Riposte, Breach, Concussion, Exploit Opening) never fire from an off-hand swing. Rep is split instead: each hand’s landed swing earns half the normal rep for its own weapon’s class, so the pair earns what one weapon would. Riposte is still primed by any block; only a main-hand swing spends the prime. Bulwark is a shield mechanic and cannot apply at all.
  • Every stat outside combat — offline progress, the enemy-threat math behind auto-potion, the tooltip DPS, the character sheet — describes the main hand.

Shield-Strike damage

Shield Strike and Smite swing the shield, so the stored Min/Max damage is replaced by the shield’s own damage — built the same way, but normalised against the shield’s interval. The equipped weapon contributes nothing here.

normFlat = round( (flatDamage_shield + flatDamage_otherSlots) × shieldInterval / 1.0 ) + blessedShield
MinDamage = (shieldMin × (1 + enhancedDamage%_shield) + normFlat) × edOther × strMult
MaxDamage = (shieldMax × (1 + enhancedDamage%_shield) + normFlat) × edOther × strMult

The shield’s crafts count exactly as on a weapon: its smith-temper adds to enhancedDamage%_shield (it lifts the shield’s dice, alongside its defense), and a witch-enchant scales the shield’s own Enhanced Damage and Flat Damage affixes.

Blessed Shield sits outside the normalisation: its bonus is already a per-hit value (the skill tooltip prints it directly), so the shield’s speed never scales it. It grows with your level squared: (base + steps × perStep) × (min(level, 80) ÷ 80)² (BUFF_SHIELD_DAMAGE_FLAT_FULL_LEVEL, BUFF_SHIELD_DAMAGE_FLAT_LEVEL_EXPONENT), so rank 20 adds +17.5 at level 40, +39 at level 60 and its full +70 from level 80 on.

Hand requirements and the plain swing

Every skill node belongs to one class’s trees, and some nodes additionally declare the hands they were built for: a two-handed weapon (Cleave, Executioner), two weapons (Thousand Cuts, Ambidexterity), a shield (Shield Strike, Aegis, Smite, Judgement, Bastion, Shield Charge, Shield Wall) or a staff (Cataclysm, Hundred Palms, Guarded Stance).

The lock is soft and resolved at swing time, not at learn time:

  • a gated main skill whose requirement is unmet resolves as the plain swing (Strike, 100% weapon damage) — your selection is remembered and comes back with the right hands. A shield that has broken counts as no shield. A hero who has learned no main skill yet swings the same way.
  • a gated active or passive contributes nothing while unmet.

Actives have no cooldowns. Their pacing is the class resource (20 Rage, Valor or Enlightenment, or 40 for a capstone buff), the cast time, and the trigger rule — see Active Skills.

Status procs from the elemental pipeline are capped twice: 20% before speed normalisation and 50% after it and any multiplier (see Elements & Status).

Attack speed & swing interval

Gear IAS and Dexterity are summed and capped first, then the main-skill and buff/Flurry speed multipliers divide the interval one after another.

iasMod   = clamp( attackSpeed% + 0.0005×DEX, −0.85, +0.85 )        # ATTACK_SPEED_MODIFIER_CAP
interval = clamp( weaponBaseInterval ÷ (1 + iasMod), 0.38, 3.6 )   # intermediate clamp
# then ÷ main-skill IAS mult, ÷ (1 + buff/Flurry IAS)
interval = clamp( interval, 0.45, 6.0 )                            # FINAL clamp

Gear, Dexterity and buffs all divide the same way: +45% attack speed is 1.45× the swings per second, +70% is 1.70×.

The 0.38 … 3.6 clamp inside computePlayerStats is intermediate; the final interval is re-clamped to 0.45 s (fastest) … 6.0 s (slowest) after the multiplicative speed layers. Throughput ≈ damage ÷ interval.

No attack-speed breakpoints. Unlike many ARPGs, the swing interval is a continuous float, not a frame-quantised value — every point of IAS shortens the timer smoothly. There is nothing to “round up” to; the only discontinuities are the two clamp edges at 0.45 s and 6.0 s (MIN_ATTACK_INTERVAL = 0.45 in src/data/balance/player.ts; the final clamps live in applyPlayerDerivedStats, src/core/simulation-helpers.ts).

Outgoing physical damage at swing time

After the stored Min/Max stat is built, each swing (applyResolvedHit) does:

raw        = randomInt(minDamage, maxDamage)
scaledRaw  = round( raw × skillDamageMultiplier × outgoingPassiveMult )

outgoingPassiveMult = 1 + Wrath + Apex Predator + Rampage + … (clamped ≥ 0; every contributor is listed under Damage passives that stack), evaluated per-hit because rage and the kill stack move mid-fight; Apex Predator’s own condition is the enemy’s rarity — rare, special or a boss — and is fixed for the fight. Wrath turns the live rage pool (0–100) into a physical multiplier. The Sentinel’s Unrelenting (+1.5% → +25%) adds only to hits with the weapon: a hit with the shield (the shield main skills, Bastion’s answer to a block, Shield Charge, Taunt) leaves it out (shieldHit). These are the last physical multipliers and hit the rolled value — they do not touch elemental riders.

Incoming mitigation (defending hero)

When the hero is the defender, mitigation runs in a strict order: block → capped percentage bucket → flat reduction. (Enemies get none of this — enemy hits are only the raw roll.)

Order of operations

  1. Block — with probability blockChance (cap 0.75), the hit is multiplied by (1 − 0.75). A block removes 75% of the hit (blockDamageReduction); it is partial, never full negation. Applied to the raw hit before the percentage bucket.
  2. Percentage-reduction bucket — a single shared bucket capped at 0.5 (MAX_PHYSICAL_REDUCTION):
    totalReduction = min( 0.5,
        constitution%          (0.0006 × CON)
      + alwaysOnPassives       (Iron Resilience — pooled with CON% into damageReductionPercent)
      + reducedPhysicalAffix
      + conditionalPassives    (e.g. Last Stand)
      + physReductionFromDR )
  3. Flat reduction — Constitution’s flat cut plus the Damage-Reduced-Flat affix is subtracted last. It bypasses the 50% cap, but a single hit can never lose more than half its remaining damage this way (FLAT_REDUCTION_MAX_FRACTION_OF_HIT = 0.5):
    flat = floor( damageReducedFlatAffix + 0.0025 × CON × level )
    out  = out − min( flat, floor(out × 0.5) )

Every stage floors the result at 1 — a landed hit always deals at least 1.

Defense Rating → physical reduction

Beyond hit-avoidance, DR grants a level-invariant, capped physical reduction that folds into the 0.5 bucket above (physicalReductionFromDefenseRating):

physReductionFromDR = min( 0.25, 0.25 × DR / (DR + 2000) )

Constants: DR_MITIGATION_CAP = 0.25, DR_MITIGATION_HALF_VALUE = 2000 (at 2000 DR you get half the cap, i.e. 12.5%). It is kept well below the 0.5 shared cap so DR can never fill the bucket alone: DR plus Constitution reach about a third at level 95, and Reduced-Physical, Iron Resilience, Vigilance and Last Stand keep headroom. A given DR is always worth the same reduction; armour never goes obsolete as enemies grow. DR reduces physical only, never elemental.

Defense RatingPhysical reduction from DR
5005.0%
10008.3%
200012.5%
400016.7%
800020.0%

Constitution

Constitution has a dual defensive identity, present in both the % bucket and the flat step:

  • % physical reduction: 0.0006 × CON (into the capped 0.5 bucket).
  • Flat per-hit reduction: 0.0025 × CON × level (level-scaled, applied after the cap, itself limited to half the hit).

It also grants +5 DR/point (which further feeds the DR→reduction curve above). Constitution grants no HP regen.

Worked example

Incoming physical hit scaledRaw = 200; defender at level 50 with CON 100, DR 2000, a +5% Reduced-Physical affix, no Last Stand.

  • physReductionFromDR = 0.25 × 2000/(2000+2000) = 0.125
  • constitution% = 0.0006 × 100 = 0.06
  • totalReduction = min(0.5, 0.125 + 0.06 + 0.05) = 0.235
  • flat = floor(0.0025 × 100 × 50) = 12

Not blocked: 200 × (1 − 0.235) = 153, then 153 − min(12, 76) = 141.

Blocked: 200 × (1 − 0.75) = 50 → 50 × (1 − 0.235) = 38 → 38 − min(12, 19) = 26.

Block multiplies before the cap, and flat is always last.

Worked examples

The sections above give each stage in isolation. These two swings walk the whole pipeline end-to-end so you can see the pieces compose.

Example A — outgoing physical swing (hero → enemy)

A weapon whose stored Min/Max damage is 100–150, using Crush at rank 20 (skill multiplier ×2.75 — its 1.40 base plus 27 effect steps × 5% Enhanced Damage per step), at full rage with Wrath at rank 20 (+35%), against an ordinary enemy:

  1. Roll to hit — assume it lands (AR/DR only decide whether, never how hard).
  2. Roll raw damage — the uniform roll comes up at the top: raw = 150.
  3. Skill multiplier — Crush: 150 × 2.75 = 412.5.
  4. Outgoing passive multiplier — 1 + Wrath + Apex + Rampage = 1 + 0.35 = 1.35: scaledRaw = round(412.5 × 1.35) = 557.
  5. Enemy mitigation — none. Physical damage to enemies ignores resistance, AR and DR, and enemies never block. The hit lands for 557.

Any flat elemental rider on the weapon resolves as a separate hit on the same swing — it skips steps 3–4 entirely (no skill multiplier, no Wrath) and is reduced only by the enemy’s matching resistance.

Example B — incoming physical hit (enemy → hero)

A raw enemy hit of 200 against a hero at level 60 with CON 200, DR 4000, a conditional Last Stand +15% active, and a +10% Reduced-Physical affix. Mitigation runs block → %-bucket → flat, each stage floored at 1:

First, the shared values:

  • physReductionFromDR = min(0.25, 0.25 × 4000/(4000+2000)) = 0.167
  • constitution% = 0.0006 × 200 = 0.12
  • reducedPhysicalAffix = 0.10
  • conditionalPassive (Last Stand) = 0.15
  • totalReduction = min(0.5, 0.167 + 0.12 + 0.10 + 0.15) = min(0.5, 0.537) = 0.50 ← hits the 0.5 cap
  • flat = floor(0.0025 × 200 × 60) = floor(30) = 30

Not blocked:

200 × (1 − 0.50) = 100                     # capped %-bucket
100 − min(30, floor(100 × 0.5)=50) = 70    # flat, ≤50% of the hit
→ 70 damage

Blocked (blockDamageReduction = 0.75, applied before the bucket):

200 × (1 − 0.75) = 50                      # block first
50  × (1 − 0.50) = 25                      # capped %-bucket
25  − min(30, floor(25 × 0.5)=12) = 13     # flat clamped to half the remaining hit
→ 13 damage

Note two caps engaging at once: the % bucket is clamped to 0.5 even though DR + CON + the affix + Last Stand would sum to 0.537, and on the blocked path the 30-point flat cut is clamped down to 12 because a single hit can never lose more than half its remaining damage to flat reduction.

Resistances & elemental damage

Elemental damage (fire, cold, lightning, poison) is a separate hit that resolves against the target’s matching resistance only — never DR, block, or the physical bucket:

elementalDamage = rawElementalDamage × (1 − resistance)

resistance = max(0, baseResistance − resistDebuffs − targetResReduction) on the enemy side. An enemy’s baseResistance follows its level only, the same in every region: 75% × (level − 1) / 199 (0% at level 1, 75% at level 200 and above, enemyBaseResistanceForLevel), +3% for rares / +6% for specials, ±2% per enemy type and element, capped at 85%. Resistance bounds (FORMULA_CONFIG):

WhoBounds
Hero−75% … 75% (minResistance / maxResistance) per element
Enemy0 … 95% (maxEnemyResistance)

The hero’s floor matters because the mitigation step is × (1 − resistance): a negative resistance amplifies that element, so a rider against −75% lands for 175% of its roll. Only the Region Resonance tax pushes a resistance below zero — −10% to all four elements per applied Resonance level, subtracted from the uncapped gear + skill total and clamped once (so overcapped resistance is headroom), and only on unlocked overworld combat tiles: dungeons and the Endless Delve are exempt. The endgame boss fights take their own penalty the same way instead, 1% per fight level above 100 (−50% echoed dungeons, −75% Brood Matriarch, −100% echoed Dark Citadel finale). See Elements & Status Effects.

Debuff/penetration effectiveness is capped at 90% (MAX_DEBUFF_EFFECTIVENESS). Flat elemental affixes are speed-normalised per hit exactly like flat physical damage. Elemental hits can also inflict status effects (values from the Elements help section):

ElementStatusProcDurationEffect
FireBurn (DoT)10%/hit6 s25% of the hit’s fire damage over the duration (ticks every 0.5 s)
ColdFreeze10%/hit, speed-normalised2 s−30% enemy attack speed
LightningStun10%/hit, speed-normalised2 sPauses the enemy attack timer
PoisonPoison (DoT)Always (every hit)6 sFlat poison value over 6 s, reduced by Poison resistance

DoT instances stack (a new burn does not refresh an old one). Physical bleeds (Rend) are unresisted.

Block

  • Chance cap: 75% (maxBlockChance = 0.75) — the maximum block chance regardless of gear or skills.
  • Needs a shield. With no shield in either hand your block chance is 0%, whatever gear, passives (Shield Mastery) or buffs (Brace) add. The one exception is the Staves T6 Parry: the staff blocks on its own, and then every block source counts as if a shield were worn. The base block value and the Block Chance affix roll on shields only; the block chance on Oak Touch (gloves) and Pendant of the Sentinel (amulet) works only next to a shield or Parry.
  • Damage reduction: 75% (blockDamageReduction = 0.75) — a blocked hit loses 75% of its damage, applied before the percentage bucket.
  • Side effects: thorns deal 150% on a blocked hit, the Rage-on-Block affix generates rage on a successful block, the Heal-on-Block shield affix heals its share of the damage the block prevented (the log’s “(N blocked)”), and the Reflect-on-Block shield affix sends its share of that same amount back at the attacker (see Thorns).

Note: the block chance cap and the block damage reduction are both 75% — two different numbers that happen to share a value: one is how often you block, the other how much a block takes off the hit.

Crushing Blow

Some weapon affixes and the Crush main skill grant a Crushing Blow chance. On proc, it deals a fraction of the enemy’s current HP as extra damage.

  • Chance is speed-normalised by the weapon’s intrinsic interval (× weaponBaseInterval / 1.0), so fast weapons don’t proc more per second than slow ones. Gear chance + skill (Crush) chance are both normalised, then capped at 0.75 (MAX_CRUSHING_BLOW_CHANCE).
  • If a slow weapon’s scaled chance exceeds the cap, the clipped share overflows into magnitude (a bigger crush, up to 2×), preserving expected value per second.
  • Block does not reduce Crushing Blow (it is applied with block chance 0).

Fraction of current HP by target rarity:

TargetFraction of current HP
Common12% (CRUSHING_BLOW_HEALTH_FACTOR_BY_RARITY.common)
Rare9%
Special6%
Boss: regional and echoed dungeon bosses (Echoed Theron included), hunt marks (the Brood Matriarch included)4% (CRUSHING_BLOW_BOSS_HEALTH_FACTOR)

Higher-rarity foes take a smaller chunk so high-HP bosses aren’t trivialised, while it melts trash.

Life Leech

A fraction of your post-mitigation physical damage is returned as healing (applyLifeLeechFromHit): heal = round(mitigatedDamage × leechFactor × enemyShare).

  • Enemy share (LIFE_LEECH_FACTOR_BY_ENEMY): the full heal against a common, ¾ against a rare, ½ against a special, ⅓ against a boss. Bosses are the regional and echoed dungeon bosses (Echoed Theron included) and hunt marks (the Brood Matriarch included); every other enemy counts by its rarity, her brood and the Delve’s wardens too. Siphon’s elemental leech uses the same shares. Skill heals that aren’t leech (Grim Harvest’s own heal, Mending Strike, Renewal, potions) are not affected.

  • Gear leech cap: 90% (MAX_LIFE_LEECH).

  • Total leech cap: 95% (MAX_TOTAL_LIFE_LEECH) — gear + Bloodlust + Reaver + buffs; the runtime clamp is 0.95.

  • Leech triggers only from the physical weapon hit — never from elemental, crushing blow, or thorns.

HP regeneration

Out of combat, and in the respawn gap between one enemy dying and the next appearing, the hero heals passively at HERO_OUT_OF_COMBAT_REGEN_RATE = 1/60 of max HP per second, so a full bar takes 60 seconds (≈1.67% per second). It does not tick while an enemy is standing in front of you.

Constitution grants no regeneration. It pays out in Defense Rating, percentage physical reduction, and the level-scaled flat reduction above. The separate Life Regeneration affix is the one that heals a flat amount of HP per second at all times, in combat included.

Thorns

Two thorn types stack, both dealing physical damage that can kill:

  • Flat thorns — a fixed value returned to the attacker on every hit you take.
  • Reflect thorns — a percentage of the damage that actually landed on you (after block and after the physical-reduction bucket) returned.

Both deal 150% on a blocked hit, and a blocked hit also flips reflect’s base from the landed amount to the pre-block, pre-reduction raw hit — so reflect = round(rawHit × 1.5 × reflect%).

A block adds a third, separate reflect before those two: the damage the block prevented times the Reflect on Block shield affix plus Unyielding’s share while that buff is up — round(blocked × (affix% + Unyielding%)), one hit for both. Thorns then land on top as usual.

Rage (feeds the outgoing multiplier)

Rage powers active skills and drives Wrath. It caps at 100 (BASE_MAX_RAGE, raised by the unique-only Maximum Rage affix), decays 5/sec out of combat (RAGE_DECAY_PER_SECOND, a full 100-rage bar in 20 s), and each active-skill cast costs 20 (ACTIVE_SKILL_RAGE_COST) — 40 for the four capstone buffs (CAPSTONE_BUFF_RAGE_COST), which are the only per-skill override. Plain attacks grant 10 rage/swing, speed-normalised by the weapon’s own interval (RAGE_REFERENCE_INTERVAL = 1.0) so rage-per-second stays neutral across weapon speeds. The weapon-only Rage On Hit affix adds its flat value to that swing’s nominal rage and takes the same interval scaling; on a multi-hit skill (Zeal) it is split across the hits, so a full swing grants it once. It is never granted by a shield strike (no weapon is swung) or by a skill cast. A shield skill with no usable shield falls back to a real weapon swing, and that one does grant it. Reduced-Rage-Decay affixes cut decay by up to 95% (MAX_RAGE_DECAY_REDUCTION). The Sentinel’s Valor stops fading at its floor, 33% of max Valor (VALOR_FLOOR_SHARE; 33 of 100), and below the floor it regenerates 5/sec out of combat and in the respawn wait (VALOR_FLOOR_REGEN_PER_SECOND), never in a fight. Reduced Valor Decay does not change the regen.

Order-of-operations gotchas

  • Enhanced-Damage % is scoped by the slot it sits on. On a weapon it scales that weapon’s dice only, folded in before flat-damage and before Strength; on a shield it scales only the shield’s Shield-Strike dice the same way. From any other slot it is a character-level multiplier applied to dice plus the normalised flat add, so there it does scale your flat damage. It never touches elemental affixes.
  • Flat-Damage is speed-normalised, then added inside the Strength multiplier, so STR scales your flat adds too — and the skill multiplier and Wrath (applied later, at swing time) scale them again.
  • Skill multiplier + Wrath / Apex / Rampage hit the rolled value multiplicatively, after the whole stat panel is built. They are the last physical multipliers.
  • Elemental damage bypasses the skill multiplier and Wrath entirely; it is flat-per-hit and is the only player damage reduced by enemy resistance. Physical to enemies ignores resistance, AR and DR, and enemies never block.
  • AR / DR are hit-chance only. Stacking DR-reduction on an enemy (Sunder/Shatter) raises your hit-rate; it does not amplify each hit.
  • Incoming mitigation order: block (−75%) → %-reduction bucket (DR-curve ≤25% + Reduced-Phys + CON + Last Stand, total capped at 50%) → flat reduction (≤50% of the hit). Order means block multiplies before the cap, and flat is always last.
  • The Hero-tab damage number is optimistic: it adds elemental as raw (no resistance), assumes every swing lands, and omits crushing blow, ignite/bleed/poison DoTs, procs, and conditional passives.

Caps & constants quick reference

ConstantValueSource
Hit chance at AR = DR0.70FORMULA_CONFIG.hitChanceAtEqualRatings
Hit chance curve steepness1.1hitChanceCurveSteepness
Hit chance floor / ceiling0.05 / 0.95minHitChance / maxHitChance
Flat hit-chance cap (gear / total)+30% / +50%MAX_HIT_CHANCE_FLAT_BONUS
Level-difference hit shift, enemy attacks and the hero out-levelling (per level / cap)±1% / ±10%levelDeltaHitChancePerLevel / levelDeltaHitChanceMaxShift
Hero vs a higher enemy: share of the hit chance lost (per level / cap)1% / 10%playerLevelPenaltySharePerLevel / playerLevelPenaltyMaxShare
Attack-speed modifier cap±0.85ATTACK_SPEED_MODIFIER_CAP
Attack interval final clamp0.45 – 6.0 sMIN_ATTACK_INTERVAL + flow
Block chance cap0.75maxBlockChance
Block damage reduction0.75blockDamageReduction
Shared physical-reduction cap0.50MAX_PHYSICAL_REDUCTION
DR reduction cap / half-value0.25 / 2000DR_MITIGATION_CAP / DR_MITIGATION_HALF_VALUE
Flat reduction max per hit0.50 of hitFLAT_REDUCTION_MAX_FRACTION_OF_HIT
Resistance cap (hero / enemy)0.75 / 0.95maxResistance / maxEnemyResistance
Resistance floor (hero)−0.75minResistance
Resonance resistance tax−0.10 per applied levelRESONANCE_RES_MALUS_PER_LEVEL
Debuff effectiveness cap0.90MAX_DEBUFF_EFFECTIVENESS
Crushing Blow chance cap0.75MAX_CRUSHING_BLOW_CHANCE
Crushing Blow HP% (common/rare/special/boss)12 / 9 / 6 / 4%crushing-blow factors
Life leech cap (gear / total)0.90 / 0.95MAX_LIFE_LEECH / MAX_TOTAL_LIFE_LEECH
Leech share vs common / rare / special / boss1 / ¾ / ½ / ⅓LIFE_LEECH_FACTOR_BY_ENEMY
Run-speed cap / respawn factor0.80 / 1.65maxRunSpeed / runSpeedRespawnFactor
Respawn floor0.32 sminRespawnSeconds
Min stat multiplier floor0.20MIN_STAT_MULTIPLIER
Rage cap / decay / cast cost100 + Maximum Rage / 5·s⁻¹ / 20BASE_MAX_RAGE / RAGE_*
Valor floor / regen below it33% of max Valor / 5·s⁻¹VALOR_FLOOR_SHARE / VALOR_FLOOR_REGEN_PER_SECOND

If a number here ever contradicts what the game actually does, the game wins — this is generated from the engine source.