Combat math NEW v12

v12 derivation: Damage formula derived end-to-end from dump.cs (HeroCalculateDamageAbility → HealthAbility chain). Two structural findings worth flagging immediately: ① Ammo bonuses share the same additive pool as the AttackDamage skill — Big Bullet + AttackDamage T3 + Crit does NOT compound multiplicatively. ② Enemies have no Armor — full damage applied. ③ Hammer-style relics (ModifyHeroParameterBonusData) bake into HeroDamage BEFORE the percent pool, so flat relic damage gets multiplied by every downstream skill/ammo % — structurally stronger than vendor offers. v12: Формула шкоди виведена з dump.cs. Аммо-бонуси та AttackDamage в одному адитивному пулі — не множаться.
FACTS / ASSUMPTIONS / MISSING: Class signatures and call chain are FACTS verified in dump.cs. Specific float values for AttackDamage tiers (+15%/+25%/+50%) and Big Bullet (+200%) are INFERRED — the AddPercentFloatModifier raw values aren't in the .txt dumps. Per-enemy DodgeChance is UNKNOWN (field exists but no extracted JSON has per-enemy values).

📐 Canonical single-shot formula📐 Канонічна формула одного пострілу

FinalDamage = ( HeroDamage_runtime + Sum(flatModifiers) ) * ( 1 + Sum(percentModifiers) ) * critMultiplier

HeroDamage_runtime is the hero's Damage ParameterReference at fire time — it already includes any ModifyHeroParameterBonusData (e.g. Hammer relic +35..+825 flat) baked into its base value via the ModifiableValue mechanism. Sum(flatModifiers) and Sum(percentModifiers) are the AddFixedFloatModifier and AddPercentFloatModifier values from EVERY active ModifyDamageEffectConfig in IEffectManager at the moment HeroCalculateDamageAbility.CalculateBaseDamage(target) runs — this includes active skill tiers, vendor offers, ammo type bonuses (Bomb +150% / Big Bullet bonus), milestone Damage bonus, and any relic-spawned ModifyDamageBonusData. critMultiplier = (1 + CritDamage_runtime) if RollCritDamage() succeeds AND no DisableCriticalDamageEffectConfig is active (Bomb suppresses crit), else 1.0. There is NO enemy armor reduction — enemies do not have an Armor parameter and HealthAbility.TakeDamage subtracts the full incoming amount.

🪜 Step-by-step pipeline🪜 Поетапний конвеєр

#StepКрокValue sourceДжерело значенняConfidenceДостовірністьNotesНотатки
1Read hero Damage parameter at runtimeHeroParametersAbility.Damage.Value (ParameterReference -> ModifiableValue)FACTBase value is HeroConfig.Levels[currentLevel-1].Damage. ModifyHeroParameterBonusData (e.g. Hammer relic, Damage milestone slot at L40 if it spawns Hammer-style) adds modifiers DIRECTLY to this ParameterReference, so they are folded into Value BEFORE the shot formula. Hammer Rare tier values (relic_modifiers.json): 35,75,120,175,240,320,415,530,665,825 flat damage.
2Filter active ModifyDamageEffectConfig setIEffectManager.GetEffects() filtered by EnemyTypeConfig[] target match and Distance comparisonFACTEach ModifyDamageEffectConfig carries one IFloatModifier (AddFixed or AddPercent). Filters: target's EnemyType is in the EnemyTypes array (or array empty = all) AND target distance satisfies the DistanceComparisonResult condition.
3Aggregate modifiers via Modify(baseValue, currentValue)currentValue starts at HeroDamage_runtime; each ModifyDamageEffectConfig.Modifier.Modify(baseValue=HeroDamage_runtime, currentValue=accumulator) updates itFACT
4Apply ModifyDamageToFrozenEnemiesEffectConfig (if target frozen)Same Modify(baseValue, currentValue) accumulator pattern using ModifyDamageToFrozenEnemiesEffectConfigFACTJoins the same additive pool. Triggered only against frozen targets (after Snowball freeze, or any Freeze effect).
5Apply ModifyDamageByCurrencyEffectConfig (if active)Same accumulator pattern with currency-balance gatingFACTEffect fires only if hero wallet holds the specified currency. Joins additive pool.
6Roll crit, apply crit damageIf RollCritDamage(target) == true AND no DisableCriticalDamageEffectConfig active: damage *= CritDamage parameter value (default 2.0)FACTBomb attaches DisableCriticalDamageEffectConfig on its AmmoItemConfig — Bomb shots NEVER crit. CritChance can be raised by ModifyCritChanceBonusData; CritDamage parameter can be raised by ModifyCritDamageBonusData (both seen as relic/vendor offer types). GuaranteedCritDamage* effects (Scout L100 sig 'AfterTakeDamage', OnChangeTarget, OnFirstHit) force RollCritDamage to true under their trigger conditions.
7Apply punishment damage (if rolled)If RollPunishment(ammo) == true: ApplyPunishmentDamage(ref damage, target, isAdditionalAmmo, wasBounced) — adds bonus damage according to AmmoPunishmentEffectConfigFACTLikely 'extra free shot' / 'execute' style bonus. Modifies same `damage` by reference, so it is a post-crit additive layer.
8HeroLaunchAmmoAbility.Launch passes damage to HeroProjectileBehaviourAbility.LaunchLaunch(launcher, ammoItemConfig, target, damage, isMultishot, pitch)FACTFrom this point onwards `damage` is fixed for the projectile.
9On hit, projectile applies ModifyAmmoDamage / ModifyAmmoExplosionDamage / ModifyAmmoRadius effectsAggregate via same Modify(base, current) pattern over per-ammo and per-AOE damageFACTThese are SECONDARY pools — they fire on impact, scoped to the specific AmmoItemConfig (e.g. vendor's PepperBomb buff = ModifyAmmoDamageBonusData targeting AmmoItemConfig(Bomb) only). They modify the explosion/AOE damage portion separately from the main hit damage.
10Apply ModifyBounceDamage / ModifyMultishotDamage (if bounced or multishot)Aggregate via same patternFACT
11HeroDealDamageAbility.DealDamage -> HealthAbility.TakeDamageHealthAbility.CurrentValue -= amount (after invoking DamageTakenAction)FACTNO enemy-side damage reduction. There is no EnemyParametersAbility class and EnemyDamageAbility only exposes an OUTGOING Damage (enemy-to-hero). Bosses have no Armor field (boss_data.json). The ONLY take-damage path that subtracts via a modifier is HeroTakeDamageAbility (hero receiving damage) which applies ModifyReceivedDamageEffectConfig — that's hero defense, not enemy defense.
12Snowball special caseAmmoItemConfig(Snowball) carries DisableDamageEffectConfig — final damage forced to 0; FreezeEffectConfig applied insteadFACT

🧮 Modifier stacking — additive vs multiplicative pools🧮 Стек модифікаторів — адитивний vs мультиплікативний пули

Additive pool (stacks linearly on base damage)Адитивний пул (стек лінійно на базову шкоду)

  • AttackDamage skill (T1/T2/T3/Super) — ModifyDamageEffectConfig pptr from SkillConfig
  • L40 milestone Damage bonus — ModifyDamageBonusData (single fixed/percent depending on hero)
  • Vendor Fork — ModifyDamageBonusData (per VendorConfig ENTRY[3])
  • Vendor Garlic — ModifyDamageBonusData (VendorConfig ENTRY[9])
  • Vendor Cake — ModifyDamageBonusData (boss/super-typed, VendorConfig ENTRY[13])
  • Active relic ModifyDamageBonusData modifiers (rare in current relic set)
  • Ammo: Bomb +150% / Big Bullet +bonus (per AmmoItemConfig)
  • ModifyDamageToFrozenEnemiesEffectConfig (when target frozen)
  • ModifyDamageByCurrencyEffectConfig (when wallet condition met)

All ModifyDamageEffectConfig modifiers contribute to the same pool inside HeroCalculateDamageAbility.CalculateBaseDamage. Percent modifiers stack additively on baseValue (Hero Damage parameter) — they do NOT compound multiplicatively. Flat modifiers add before percent.

Multiplicative pool (applied after the base aggregation)Мультиплікативний пул (застосовується після базової агрегації)

  • Crit damage multiplier — applied via ApplyCritDamage(ref damage) AFTER base aggregation
  • ModifyAmmoDamage / ModifyAmmoExplosionDamage — applied on projectile impact (separate accumulator in HeroProjectileBehaviourAbility.Launch)
  • ModifyBounceDamage / ModifyMultishotDamage — applied conditionally inside projectile Launch

These run AFTER CalculateBaseDamage returns. Crit uses ref-mutate; ammo/bounce/multishot effects use their own Modify(base, current) aggregator with their own baseValue (the post-base damage).

📚 Where damage comes from (full source inventory)📚 Звідки береться шкода (повний інвентар джерел)

SourceДжерелоMechanismМеханізмRangeДіапазонJoins poolПриєднується до пулу
Hero base Damage (per-level)HeroConfig.Levels[currentLevel-1].Damage -> Hero.Damage ParameterReferenceL1=30, L100=1500 (all 5 heroes share the same curve — hero_data.json)base_value_pool
Milestone L40 Damage slotModifyDamageBonusData (single tier, applied once)Value not extracted (HeroConfig L40 bonus payload — see HeroConfig.txt ENTRY[3] ModifyDamageBonusData)additive_pool
Hero L80/L100 signature bonusesVaries per hero — Scout L100 = GuaranteedCritDamageAfterTakeDamageBonusData (force crit after being hit)Per-hero unique. Scout's adds a conditional always-crit trigger, not a damage % directly.Affects step 6 (crit pipeline), not the additive pool
Active skill: AttackDamage tierSkillConfig -> ModifyDamageEffectConfig pptr (T1='Attack Damage 115%', T2='Attack Damage 125%', T3='Attack Damage 150%', Super=T3+AmmoExplode AmmoBlowUp)Asset names imply +15% / +25% / +50% (i.e. multiplier becomes 1.15/1.25/1.50). Could also be interpreted as the OUTPUT scaling (115%, 125%, 150% of base) — same numeric effect either way. Existing _report_data.json labels these as '+115%/+125%/+150%' which is INTERNALLY INCONSISTENT but most likely the report is mis-labelled and the true effect is +15%/+25%/+50% additive percent.additive_pool
Active relic: Hammer (Rare)ModifyHeroParameterBonusData targeting DamageParameterConfig with AddFixedFloatModifierPer tier: +35, +75, +120, +175, +240, +320, +415, +530, +665, +825 flat Damagebase_value_pool
Vendor in-run offer: ForkVendorConfig ENTRY[3] ModifyDamageBonusData targeting all enemies+10% per existing report (numeric not in dumps)additive_pool
Vendor in-run offer: PepperBombVendorConfig ENTRY[5] ModifyAmmoDamageBonusData targeting AmmoItemConfig(Bomb)Per-Bomb shot only (NOT the main additive pool — fires inside projectile Launch via ModifyAmmoDamageEffectConfig step)multiplicative_pool (ammo step)
Vendor in-run offer: GarlicVendorConfig ENTRY[9] ModifyDamageBonusData?additive_pool
Vendor in-run offer: CakeVendorConfig ENTRY[13] ModifyDamageBonusData targeting EnemyTypeConfig(Boss) + EnemyTypeConfig(Super)?additive_pool (conditional on target type)
Vendor in-run offer: ChitinModifyReceivedDamageBonusData — DEFENSIVE, hero damage taken reduction. Not part of outgoing shot formula.?(not relevant to shot damage)
Ammo type — BombAmmoItemConfig(Bomb) ModifyDamageEffectConfig(Attack Damage +150%) + DisableCriticalDamageEffectConfig+150% additive percent (label asserts +150%, raw float not in dumps); crit forced offadditive_pool
Ammo type — Big BulletAmmoItemConfig(BigBullet) ModifyDamageEffectConfig(Big Bullet Attack Damage)INFERRED +200% per design brief / reportadditive_pool
Ammo type — SnowballDisableDamageEffectConfig — zeroes damage; FreezeEffectConfig applies freezeDamage = 0(short-circuits formula)
Crit rollRollCritDamage -> ApplyCritDamage(ref damage). CritChance base = 0.05, CritDamage parameter base = 2.0 (hero_stats.csv tail_floats).Multiplier 2.0 -> 6.0+ with stacked ModifyCritDamageBonusData. Disabled by DisableCriticalDamageEffectConfig (Bomb).multiplicative_pool
Hero L100 sig: Scout — GuaranteedCritDamageAfterTakeDamageGuaranteedCritDamageAfterTakeDamageBonusData -> GuaranteedCritDamageAfterTakeDamageEffectConfig; while active, RollCritDamage always returns true?Forces step 6 to succeed (crit always lands during the trigger window)

🛡️ Enemy defense — armor + dodge🛡️ Захист ворогів — броня + ухилення

Enemy armor — DOES NOT EXIST

No EnemyParametersAbility class. EnemyDamageAbility (dump.cs:309198) only stores outgoing damage. boss_data.json has no Armor field. HealthAbility.TakeDamage(GameUnit owner, float amount, DamageType damageType) directly subtracts amount.

Status: FACT — enemies have no armor stat in Claw Master.

Enemy dodge — exists but inactive in current buildУхилення ворогів — існує, але неактивне у поточній збірці

EnemyDodgeAbility (dump.cs:309308) with float DodgeChance field. IgnoreDodgeEffectConfig (dump.cs:298730) can suppress it.

Big Bullet / Bomb / Snowball all attach IgnoreDodgeEffectConfig — only normal Bullets can be dodged. The DodgeChance per enemy is not in the standard EnemyConfig fields parsed in extractions; INFERRED to be 0 for most enemies (the field would otherwise show up in additionalValue1/2 or a separate config — needs verification).

🧪 Worked examples — 4 scenarios🧪 Опрацьовані приклади — 4 сценарії

Scout L100, AttackDamage T3 only, no relics/vendor/crit, Bullet ammo, normal enemy

HeroDamage_runtime = 1500
additive_percent_pool = +0.50 (AttackDamage T3 = +50%)
additive_flat_pool = 0
ammo_multiplier = Bullet = no contribution
crit = no roll (5% base chance, assume miss)
(1500 + 0) * (1 + 0.50) * 1.0 = 2,250
Base 1500 from Hero_Scout L100 Damage. AttackDamage T3 contributes +50% to additive pool. No crit. Result = 2,250 per shot.

Scout L100, AttackDamage T3, Big Bullet, lucky crit roll

HeroDamage_runtime = 1500
additive_percent_pool = +0.50 (AttackDamage T3) + ~+2.00 (Big Bullet, INFERRED) = +2.50
additive_flat_pool = 0
crit_multiplier = 2.0
(1500 + 0) * (1 + 2.50) * 2.0 = 1500 * 3.5 * 2.0 = 10,500
Big Bullet's '+200%' joins the same pool as AttackDamage T3's +50%. Sum = +250% additive. Multiplier becomes 3.5x. Crit doubles -> 10,500. NOTE: this gives a smaller spike than the design brief's 16,875 because both ammo and skill share an additive pool — the surprise.

GoldBoy L100, AttackDamage T3, Hammer Rare T10 (last tier = +825 flat), Bomb ammo, no crit (Bomb disables crit)

HeroDamage_runtime = 1500 + 825 (Hammer T10 flat via ModifyHeroParameter) = 2325
additive_percent_pool = +0.50 (AttackDamage T3) + +1.50 (Bomb) = +2.00
additive_flat_pool = 0
crit_multiplier = 1.0 (DisableCriticalDamage active from Bomb)
(2325 + 0) * (1 + 2.00) * 1.0 = 2325 * 3.0 = 6,975
Hammer is structural — its +825 modifies the HERO PARAMETER directly so it sits in HeroDamage_runtime and gets multiplied by every subsequent percent buff. AttackDamage T3 (+50%) and Bomb (+150%) share the additive pool -> 3.0x. No crit possible with Bomb.

Scout L100, no skills, Bullet, vanilla 5% crit landed

HeroDamage_runtime = 1500
additive_percent_pool = +0.00
crit_multiplier = 2.0
1500 * 1.0 * 2.0 = 3,000
Base case crit. No skill modifiers in the pool.

❓ Open questions (couldn't determine from source alone)❓ Відкриті питання (не вдалося визначити лише з коду)

To resolve: verify by observing in-game damage numbers OR by binary-grepping the ModifyDamageEffectConfig payloads with the path_id list in combat_math.json.

🎯 Design implications for Claw Fight🎯 Дизайнерські висновки для Claw Fight