diff --git a/app/features/comp-analyzer/core/damage-combinations.test.ts b/app/features/comp-analyzer/core/damage-combinations.test.ts index c114aad59..649ce382e 100644 --- a/app/features/comp-analyzer/core/damage-combinations.test.ts +++ b/app/features/comp-analyzer/core/damage-combinations.test.ts @@ -171,6 +171,38 @@ describe("calculateDamageCombos - threshold filtering", () => { }); }); +describe("calculateDamageCombos - one-shot exclusion", () => { + test("excludes combos where main/special hit one-shots without sub", () => { + const combos = calculateDamageCombos([SPLAT_CHARGER_ID, SPLATTERSHOT_ID]); + + for (const combo of combos) { + const hasNoSubWeapon = combo.segments.every((s) => !s.isSubWeapon); + const hasOneShot = combo.segments.some((s) => s.damageValue >= 100); + + const isInvalidCombo = hasNoSubWeapon && hasOneShot; + expect(isInvalidCombo).toBe(false); + } + }); + + test("keeps combos with 100+ damage when sub weapon is present", () => { + const combos = calculateDamageCombos([SPLAT_CHARGER_ID, SPLATTERSHOT_ID]); + + const comboWithSubAndOneShot = combos.find((combo) => { + const hasSub = combo.segments.some((s) => s.isSubWeapon); + const hasOneShot = combo.segments.some((s) => s.damageValue >= 100); + return hasSub && hasOneShot; + }); + + expect(comboWithSubAndOneShot).toBeDefined(); + expect( + comboWithSubAndOneShot!.segments.some((s) => s.isSubWeapon), + ).toBe(true); + expect( + comboWithSubAndOneShot!.segments.some((s) => s.damageValue >= 100), + ).toBe(true); + }); +}); + describe("calculateDamageCombos - sorting", () => { test("sorts results by totalDamage closest to 100 (lethal threshold)", () => { const combos = calculateDamageCombos([ diff --git a/app/features/comp-analyzer/core/damage-combinations.ts b/app/features/comp-analyzer/core/damage-combinations.ts index 212000427..1660e00a1 100644 --- a/app/features/comp-analyzer/core/damage-combinations.ts +++ b/app/features/comp-analyzer/core/damage-combinations.ts @@ -302,9 +302,17 @@ function backtrack( } function filterAndSortCombos(combos: DamageCombo[]): DamageCombo[] { - const filtered = combos.filter( - (combo) => combo.totalDamage >= COMBO_DAMAGE_THRESHOLD, - ); + const filtered = combos.filter((combo) => { + if (combo.totalDamage < COMBO_DAMAGE_THRESHOLD) { + return false; + } + + if (hasOneShotWithoutSub(combo)) { + return false; + } + + return true; + }); filtered.sort((a, b) => { const aDistTo100 = Math.abs(a.totalDamage - 100); @@ -318,6 +326,12 @@ function filterAndSortCombos(combos: DamageCombo[]): DamageCombo[] { return filtered.slice(0, MAX_COMBOS_DISPLAYED); } +function hasOneShotWithoutSub(combo: DamageCombo): boolean { + const hasNoSubWeapon = combo.segments.every((s) => !s.isSubWeapon); + const hasOneShot = combo.segments.some((s) => s.damageValue >= 100); + return hasNoSubWeapon && hasOneShot; +} + const SPLASH_O_MATIC_ID = 20; /**