From e66bb07d8091cd453c86db922a33c9e279316f5c Mon Sep 17 00:00:00 2001 From: noobscrub27 Date: Tue, 30 Jul 2024 02:47:33 -0500 Subject: [PATCH] fixed a bug in predictTotal, and updated the code to match upstream --- calc/src/desc.ts | 395 ++++++++++++++++++++++++----------------------- 1 file changed, 201 insertions(+), 194 deletions(-) diff --git a/calc/src/desc.ts b/calc/src/desc.ts index 080ea286b..3cd85e2d0 100644 --- a/calc/src/desc.ts +++ b/calc/src/desc.ts @@ -1,11 +1,11 @@ -import {Generation, Weather, Terrain, TypeName, ID, AbilityName} from './data/interface'; -import {Field, Side} from './field'; -import {Move} from './move'; -import {Pokemon} from './pokemon'; -import {Damage, damageRange} from './result'; -import {error} from './util'; +import { Generation, Weather, Terrain, TypeName, ID, AbilityName } from './data/interface'; +import { Field, Side } from './field'; +import { Move } from './move'; +import { Pokemon } from './pokemon'; +import { Damage, damageRange } from './result'; +import { error } from './util'; // NOTE: This needs to come last to simplify bundling -import {isGrounded} from './mechanics/util'; +import { isGrounded } from './mechanics/util'; export interface RawDesc { HPEVs?: string; @@ -128,6 +128,11 @@ export function getRecovery( recovery[0] = recovery[1] = Math.round(attacker.maxHP() / 6); } + if (move.named('Pain Split')) { + const average = Math.floor((attacker.curHP() + defender.curHP()) / 2); + recovery[0] = recovery[1] = average - attacker.curHP(); + } + if (move.drain) { const percentHealed = move.drain[0] / move.drain[1]; const max = Math.round(defender.maxHP() * percentHealed); @@ -141,13 +146,14 @@ export function getRecovery( } } - if (recovery[1] === 0) return {recovery, text}; + if (recovery[1] === 0) return { recovery, text }; const minHealthRecovered = toDisplay(notation, recovery[0], attacker.maxHP()); const maxHealthRecovered = toDisplay(notation, recovery[1], attacker.maxHP()); - text = `${minHealthRecovered} - ${maxHealthRecovered}${notation} recovered`; - return {recovery, text}; + const change = recovery[0] > 0 ? 'recovered' : 'lost'; + text = `${minHealthRecovered} - ${maxHealthRecovered}${notation} ${change}`; + return { recovery, text }; } // TODO: return recoil damage as exact HP @@ -207,27 +213,27 @@ export function getRecoil( recoil = [minRecoilDamage, maxRecoilDamage]; switch (gen.num) { - case 1: - recoil = toDisplay(notation, 1, attacker.maxHP()); - text = '1hp damage on miss'; - break; - case 2: case 3: case 4: - if (defender.hasType('Ghost')) { - if (gen.num === 4) { - const gen4CrashDamage = Math.floor(((defender.maxHP() * 0.5) / attacker.maxHP()) * 100); - recoil = notation === '%' ? gen4CrashDamage : Math.floor((gen4CrashDamage / 100) * 48); - text = `${gen4CrashDamage}% crash damage`; + case 1: + recoil = toDisplay(notation, 1, attacker.maxHP()); + text = '1hp damage on miss'; + break; + case 2: case 3: case 4: + if (defender.hasType('Ghost')) { + if (gen.num === 4) { + const gen4CrashDamage = Math.floor(((defender.maxHP() * 0.5) / attacker.maxHP()) * 100); + recoil = notation === '%' ? gen4CrashDamage : Math.floor((gen4CrashDamage / 100) * 48); + text = `${gen4CrashDamage}% crash damage`; + } else { + recoil = 0; + text = 'no crash damage on Ghost types'; + } } else { - recoil = 0; - text = 'no crash damage on Ghost types'; + text = `${minRecoilDamage} - ${maxRecoilDamage}${notation} crash damage on miss`; } - } else { - text = `${minRecoilDamage} - ${maxRecoilDamage}${notation} crash damage on miss`; - } - break; - default: - recoil = notation === '%' ? 24 : 50; - text = '50% crash damage'; + break; + default: + recoil = notation === '%' ? 24 : 50; + text = '50% crash damage'; } } else if (move.struggleRecoil) { recoil = notation === '%' ? 12 : 25; @@ -240,7 +246,7 @@ export function getRecoil( text = '50% recoil damage'; } - return {recoil, text}; + return { recoil, text }; } export function getKOChance( @@ -255,11 +261,11 @@ export function getKOChance( damage = combine(damage); if (isNaN(damage[0])) { error(err, 'damage[0] must be a number.'); - return {chance: 0, n: 0, text: ''}; + return { chance: 0, n: 0, text: '' }; } if (damage[damage.length - 1] === 0) { error(err, 'damage[damage.length - 1] === 0.'); - return {chance: 0, n: 0, text: ''}; + return { chance: 0, n: 0, text: '' }; } // Code doesn't really work if these aren't set. @@ -267,7 +273,7 @@ export function getKOChance( if (move.timesUsedWithMetronome === undefined) move.timesUsedWithMetronome = 1; if (damage[0] >= defender.maxHP() && move.timesUsed === 1 && move.timesUsedWithMetronome === 1) { - return {chance: 1, n: 1, text: 'guaranteed OHKO'}; + return { chance: 1, n: 1, text: 'guaranteed OHKO' }; } if (attacker.hasAbility('Neutralizing Gas') || defender.hasAbility('Neutralizing Gas')) { @@ -293,6 +299,63 @@ export function getKOChance( : ''; const afterTextNoHazards = eot.texts.length > 0 ? ' after ' + serializeText(eot.texts) : ''; + function roundChance(chance: number) { + // prevent displaying misleading 100% or 0% chances + return Math.max(Math.min(Math.round(chance * 1000), 999), 1) / 10; + } + + function KOChance( + chance: number | undefined, + chanceWithEot: number | undefined, + n: number, + multipleTurns = false, + ) { + // chance and chanceWithEot are calculated separately for OHKOs + // because the difference between KOing at start of turn is very important in some cases + // for 2HKOs and onward, only chanceWithEot is calculated, so chance will be set to 0 for the purposes of this function + // all this really does is skip straight to that last else if block + // using the number of hits we can determine the type of KO we are checking for + let KOTurnText = n === 1 ? 'OHKO' + : (multipleTurns ? `KO in ${n} turns` : `${n}HKO`) + let text = qualifier; + let returnChance = undefined; + if (chance === undefined || chanceWithEot === undefined) { + text += `possible ${KOTurnText}` + // not a KO + } else if (chance + chanceWithEot === 0) { + returnChance = 0; + text += 'not a KO'; + // if the move OHKOing is guaranteed even without end of turn damage + } else if (chance === 1) { + returnChance = chance; + text += `guaranteed OHKO${hazardsText}`; + } else if (chance > 0) { + returnChance = chanceWithEot; + // if the move OHKOing is possible, but eot damage guarantees the OHKO + // I have it so that the text specifies the chance of the OHKO without eot damage, because it might matter in some scenarios + // eg. if your opponent has a move that can OHKO you but you're faster, it might be important to get the OKKO before they can move + if (chanceWithEot === 1) { + text += `${roundChance(chance)}% chance to ${KOTurnText}${hazardsText} (guaranteed ${KOTurnText}${afterTextNoHazards})` + // if the move OHKOing is possible, and eot damage increases the odds of the KO + } else if (chanceWithEot > chance) { + text += `${roundChance(chance)}% chance to ${KOTurnText}${hazardsText} (${qualifier}${roundChance(chanceWithEot)}% to ${KOTurnText}${afterTextNoHazards})` + // if the move KOing is possible, and eot damage does not increase the odds of the KO + } else if (chance > 0) { + text += `${roundChance(chance)} to ${KOTurnText}${afterText}` + } + } else if (chance == 0) { + returnChance = chanceWithEot; + // if the move KOing is not possible, but eot damage guarantees the OHKO + if (chanceWithEot === 1) { + text += `guaranteed ${KOTurnText}${afterText}`; + // if the move KOing is not possible, but eot damage might KO + } else if (chanceWithEot > 0) { + text += `${roundChance(chanceWithEot)}% chance to ${KOTurnText}${afterText}`; + } + } + return { returnChance, n, text }; + } + if ((move.timesUsed === 1 && move.timesUsedWithMetronome === 1) || move.isZ) { // for calculating OHKOs, chance only considers the move itself and hazards // chanceWithEot considers end of turn effects as well, and the text that is displayed depends on both results @@ -302,45 +365,9 @@ export function getKOChance( const chanceWithEot = computeKOChance( damage, defender.curHP() - hazards.damage, eot.damage, 1, 1, defender.maxHP(), toxicCounter ); - if (chance === 1) { // if the move OHKOing is guaranteed even without end of turn damage - return { chance, n: 1, text: `guaranteed OHKO${hazardsText}` }; - } else if (chance > 0) { - if (chanceWithEot === 1) { // if the move OHKOing is possible, but eot damage guarantees the KO - return { - chanceWithEot, - n: 1, - // I have it so that the text specifies the chance of the OHKO without eot damage, because it might matter in some scenarios - // ie. if your opponent has a move that can OHKO you but you're faster, it might be important to get the KO before they can move - text: qualifier + Math.round(chance * 1000) / 10 + `% chance to OHKO${hazardsText} (guaranteed OHKO${afterTextNoHazards})`, - }; - } else if (chanceWithEot > chance) { // if the move OHKOing is possible, and eot damage increases the odds of the KO - return { - chanceWithEot, - n: 1, - text: qualifier + Math.round(chance * 1000) / 10 + `% chance to OHKO${hazardsText} (` + qualifier + Math.round(chanceWithEot * 1000) / 10 + `% to OHKO${afterTextNoHazards})`, - }; - } else if (chance > 0) { // if the move OHKOing is possible, and eot damage does not increase the odds of the KO - return { - chance, - n: 1, - text: qualifier + Math.round(chance * 1000) / 10 + `% chance to OHKO${afterText}`, - }; - } - } else if (chance == 0) { - if (chanceWithEot === 1) { // if the move OHKOing is not possible, but eot damage guarantees the OHKO - return { - chanceWithEot, - n: 1, - text: `guaranteed OHKO${afterText}`, - }; - } else if (chanceWithEot > 0) { // if the move OHKOing is not possible, but eot damage might KO - return { - chanceWithEot, - n: 1, - text: qualifier + Math.round(chanceWithEot * 1000) / 10 + `% chance to OHKO${afterText}`, - }; - } - } + + // checks if either chance is greater than 0 + if (chance + chanceWithEot > 0) return KOChance(chance, chanceWithEot, 1); // Parental Bond's combined first + second hit only is accurate for chance to OHKO, for // multihit KOs its only approximated. We should be doing squashMultihit here instead of @@ -355,15 +382,7 @@ export function getKOChance( const chance = computeKOChance( damage, defender.curHP() - hazards.damage, eot.damage, i, 1, defender.maxHP(), toxicCounter ); - if (chance === 1) { - return {chance, n: i, text: `${qualifier || 'guaranteed '}${i}HKO${afterText}`}; - } else if (chance > 0) { - return { - chance, - n: i, - text: qualifier + Math.round(chance * 1000) / 10 + `% chance to ${i}HKO${afterText}`, - }; - } + if (chance > 0) return KOChance(0, chance, i); } for (let i = 5; i <= 9; i++) { @@ -371,12 +390,13 @@ export function getKOChance( predictTotal(damage[0], eot.damage, i, 1, toxicCounter, defender.maxHP()) >= defender.curHP() - hazards.damage ) { - return {chance: 1, n: i, text: `${qualifier || 'guaranteed '}${i}HKO${afterText}`}; + return KOChance(0, 1, i); } else if ( predictTotal(damage[damage.length - 1], eot.damage, i, 1, toxicCounter, defender.maxHP()) >= defender.curHP() - hazards.damage ) { - return {n: i, text: qualifier + `possible ${i}HKO${afterText}`}; + // possible but no concrete chance + return KOChance(undefined, undefined, i); } } } else { @@ -388,22 +408,7 @@ export function getKOChance( defender.maxHP(), toxicCounter ); - if (chance === 1) { - return { - chance, - n: move.timesUsed, - text: `${qualifier || 'guaranteed '}KO in ${move.timesUsed} turns${afterText}`, - }; - } else if (chance > 0) { - return { - chance, - n: move.timesUsed, - text: - qualifier + - Math.round(chance * 1000) / 10 + - `% chance to ${move.timesUsed}HKO${afterText}`, - }; - } + if (chance > 0) return KOChance(0, chance, move.timesUsed, chance === 1); if (predictTotal( damage[0], @@ -415,11 +420,7 @@ export function getKOChance( ) >= defender.curHP() - hazards.damage ) { - return { - chance: 1, - n: move.timesUsed, - text: `${qualifier || 'guaranteed '}KO in ${move.timesUsed} turns${afterText}`, - }; + return KOChance(0, 1, move.timesUsed, true); } else if ( predictTotal( damage[damage.length - 1], @@ -431,15 +432,13 @@ export function getKOChance( ) >= defender.curHP() - hazards.damage ) { - return { - n: move.timesUsed, - text: qualifier + `possible KO in ${move.timesUsed} turns${afterText}`, - }; + // possible but no real idea + return KOChance(undefined, undefined, move.timesUsed, true); } - return {n: move.timesUsed, text: qualifier + 'not a KO'}; + return KOChance(0, 0, move.timesUsed); } - return {chance: 0, n: 0, text: ''}; + return { chance: 0, n: 0, text: '' }; } function combine(damage: Damage) { @@ -475,7 +474,7 @@ function getHazards(gen: Generation, defender: Pokemon, defenderSide: Side) { const texts: string[] = []; if (defender.hasItem('Heavy-Duty Boots')) { - return {damage, texts}; + return { damage, texts }; } if (defenderSide.isSR && !defender.hasAbility('Magic Guard', 'Mountaineer')) { const rockType = gen.types.get('rock' as ID)!; @@ -495,8 +494,8 @@ function getHazards(gen: Generation, defender: Pokemon, defenderSide: Side) { } if (!defender.hasType('Flying') && - !defender.hasAbility('Magic Guard', 'Levitate') && - !defender.hasItem('Air Balloon') + !defender.hasAbility('Magic Guard', 'Levitate') && + !defender.hasItem('Air Balloon') ) { if (defenderSide.spikes === 1) { damage += Math.floor(defender.maxHP() / 8); @@ -518,7 +517,7 @@ function getHazards(gen: Generation, defender: Pokemon, defenderSide: Side) { damage = 0; } - return {damage, texts}; + return { damage, texts }; } function getEndOfTurn( @@ -660,36 +659,36 @@ function getEndOfTurn( texts.push('Salt Cure'); } if (!defender.hasType('Fire') && !defender.hasAbility('Magic Guard') && - (move.named('Fire Pledge (Grass Pledge Boosted)', 'Grass Pledge (Fire Pledge Boosted)'))) { + (move.named('Fire Pledge (Grass Pledge Boosted)', 'Grass Pledge (Fire Pledge Boosted)'))) { damage -= Math.floor(defender.maxHP() / 8); texts.push('Sea of Fire damage'); } if (!defender.hasAbility('Magic Guard') && !defender.hasType('Grass') && - (field.defenderSide.vinelash || move.named('G-Max Vine Lash'))) { + (field.defenderSide.vinelash || move.named('G-Max Vine Lash'))) { damage -= Math.floor(defender.maxHP() / 6); texts.push('Vine Lash damage'); } if (!defender.hasAbility('Magic Guard') && !defender.hasType('Fire') && - (field.defenderSide.wildfire || move.named('G-Max Wildfire'))) { + (field.defenderSide.wildfire || move.named('G-Max Wildfire'))) { damage -= Math.floor(defender.maxHP() / 6); texts.push('Wildfire damage'); } if (!defender.hasAbility('Magic Guard') && !defender.hasType('Water') && - (field.defenderSide.cannonade || move.named('G-Max Cannonade'))) { + (field.defenderSide.cannonade || move.named('G-Max Cannonade'))) { damage -= Math.floor(defender.maxHP() / 6); texts.push('Cannonade damage'); } if (!defender.hasAbility('Magic Guard') && !defender.hasType('Rock') && - (field.defenderSide.volcalith || move.named('G-Max Volcalith'))) { + (field.defenderSide.volcalith || move.named('G-Max Volcalith'))) { damage -= Math.floor(defender.maxHP() / 6); texts.push('Volcalith damage'); } - return {damage, texts}; + return { damage, texts }; } function computeKOChance( @@ -722,7 +721,7 @@ function computeKOChance( } } } - + let sum = 0; let lastc = 0; for (let i = 0; i < n; i++) { @@ -760,10 +759,16 @@ function predictTotal( maxHP: number ) { let toxicDamage = 0; + // hits - 1 is used in this for loop, as well as in the total = ... calcs later + // the last turn of eot damage is calculated separately, since if the damage is less than 0 (healing) + // we want to exclude that from the calculations, since on the last turn the pokemon has been ko'd by the attack + // and should not be able to heal after fainting + let lastTurnEot = eot; if (toxicCounter > 0) { for (let i = 0; i < hits - 1; i++) { toxicDamage += Math.floor(((toxicCounter + i) * maxHP) / 16); } + lastTurnEot -= Math.floor(((toxicCounter + (hits - 1)) * maxHP) / 16); } let total = 0; if (hits > 1 && timesUsed === 1) { @@ -771,6 +776,8 @@ function predictTotal( } else { total = damage - eot * (hits - 1) + toxicDamage; } + // if the net eot health gain is negative for the last turn, include it in the total + if (lastTurnEot < 0) total -= lastTurnEot; return total; } @@ -785,82 +792,82 @@ function squashMultihit(gen: Generation, d: number[], hits: number, err = true) return r; } else if (d.length === 16) { switch (hits) { - case 2: - return [ - 2 * d[0], d[2] + d[3], d[4] + d[4], d[4] + d[5], d[5] + d[6], d[6] + d[6], - d[6] + d[7], d[7] + d[7], d[8] + d[8], d[8] + d[9], d[9] + d[9], d[9] + d[10], - d[10] + d[11], d[11] + d[11], d[12] + d[13], 2 * d[15], - ]; - case 3: - return [ - 3 * d[0], d[3] + d[3] + d[4], d[4] + d[4] + d[5], d[5] + d[5] + d[6], - d[5] + d[6] + d[6], d[6] + d[6] + d[7], d[6] + d[7] + d[7], d[7] + d[7] + d[8], - d[7] + d[8] + d[8], d[8] + d[8] + d[9], d[8] + d[9] + d[9], d[9] + d[9] + d[10], - d[9] + d[10] + d[10], d[10] + d[11] + d[11], d[11] + d[12] + d[12], 3 * d[15], - ]; - case 4: - return [ - 4 * d[0], 4 * d[4], d[4] + d[5] + d[5] + d[5], d[5] + d[5] + d[6] + d[6], - 4 * d[6], d[6] + d[6] + d[7] + d[7], 4 * d[7], d[7] + d[7] + d[7] + d[8], - d[7] + d[8] + d[8] + d[8], 4 * d[8], d[8] + d[8] + d[9] + d[9], 4 * d[9], - d[9] + d[9] + d[10] + d[10], d[10] + d[10] + d[10] + d[11], 4 * d[11], 4 * d[15], - ]; - case 5: - return [ - 5 * d[0], d[4] + d[4] + d[4] + d[5] + d[5], d[5] + d[5] + d[5] + d[5] + d[6], - d[5] + d[6] + d[6] + d[6] + d[6], d[6] + d[6] + d[6] + d[6] + d[7], - d[6] + d[6] + d[7] + d[7] + d[7], 5 * d[7], d[7] + d[7] + d[7] + d[8] + d[8], - d[7] + d[7] + d[8] + d[8] + d[8], 5 * d[8], d[8] + d[8] + d[8] + d[9] + d[9], - d[8] + d[9] + d[9] + d[9] + d[9], d[9] + d[9] + d[9] + d[9] + d[10], - d[9] + d[10] + d[10] + d[10] + d[10], d[10] + d[10] + d[11] + d[11] + d[11], 5 * d[15], - ]; - case 10: - return [ - 10 * d[0], 10 * d[4], 3 * d[4] + 7 * d[5], 5 * d[5] + 5 * d[6], 10 * d[6], - 5 * d[6] + 5 * d[7], 10 * d[7], 7 * d[7] + 3 * d[8], 3 * d[7] + 7 * d[8], 10 * d[8], - 5 * d[8] + 5 * d[9], 4 * d[9], 5 * d[9] + 5 * d[10], 7 * d[10] + 3 * d[11], 10 * d[11], - 10 * d[15], - ]; - default: - error(err, `Unexpected # of hits: ${hits}`); - return d; + case 2: + return [ + 2 * d[0], d[2] + d[3], d[4] + d[4], d[4] + d[5], d[5] + d[6], d[6] + d[6], + d[6] + d[7], d[7] + d[7], d[8] + d[8], d[8] + d[9], d[9] + d[9], d[9] + d[10], + d[10] + d[11], d[11] + d[11], d[12] + d[13], 2 * d[15], + ]; + case 3: + return [ + 3 * d[0], d[3] + d[3] + d[4], d[4] + d[4] + d[5], d[5] + d[5] + d[6], + d[5] + d[6] + d[6], d[6] + d[6] + d[7], d[6] + d[7] + d[7], d[7] + d[7] + d[8], + d[7] + d[8] + d[8], d[8] + d[8] + d[9], d[8] + d[9] + d[9], d[9] + d[9] + d[10], + d[9] + d[10] + d[10], d[10] + d[11] + d[11], d[11] + d[12] + d[12], 3 * d[15], + ]; + case 4: + return [ + 4 * d[0], 4 * d[4], d[4] + d[5] + d[5] + d[5], d[5] + d[5] + d[6] + d[6], + 4 * d[6], d[6] + d[6] + d[7] + d[7], 4 * d[7], d[7] + d[7] + d[7] + d[8], + d[7] + d[8] + d[8] + d[8], 4 * d[8], d[8] + d[8] + d[9] + d[9], 4 * d[9], + d[9] + d[9] + d[10] + d[10], d[10] + d[10] + d[10] + d[11], 4 * d[11], 4 * d[15], + ]; + case 5: + return [ + 5 * d[0], d[4] + d[4] + d[4] + d[5] + d[5], d[5] + d[5] + d[5] + d[5] + d[6], + d[5] + d[6] + d[6] + d[6] + d[6], d[6] + d[6] + d[6] + d[6] + d[7], + d[6] + d[6] + d[7] + d[7] + d[7], 5 * d[7], d[7] + d[7] + d[7] + d[8] + d[8], + d[7] + d[7] + d[8] + d[8] + d[8], 5 * d[8], d[8] + d[8] + d[8] + d[9] + d[9], + d[8] + d[9] + d[9] + d[9] + d[9], d[9] + d[9] + d[9] + d[9] + d[10], + d[9] + d[10] + d[10] + d[10] + d[10], d[10] + d[10] + d[11] + d[11] + d[11], 5 * d[15], + ]; + case 10: + return [ + 10 * d[0], 10 * d[4], 3 * d[4] + 7 * d[5], 5 * d[5] + 5 * d[6], 10 * d[6], + 5 * d[6] + 5 * d[7], 10 * d[7], 7 * d[7] + 3 * d[8], 3 * d[7] + 7 * d[8], 10 * d[8], + 5 * d[8] + 5 * d[9], 4 * d[9], 5 * d[9] + 5 * d[10], 7 * d[10] + 3 * d[11], 10 * d[11], + 10 * d[15], + ]; + default: + error(err, `Unexpected # of hits: ${hits}`); + return d; } } else if (d.length === 39) { switch (hits) { - case 2: - return [ - 2 * d[0], 2 * d[7], 2 * d[10], 2 * d[12], 2 * d[14], d[15] + d[16], - 2 * d[17], d[18] + d[19], d[19] + d[20], 2 * d[21], d[22] + d[23], - 2 * d[24], 2 * d[26], 2 * d[28], 2 * d[31], 2 * d[38], - ]; - case 3: - return [ - 3 * d[0], 3 * d[9], 3 * d[12], 3 * d[13], 3 * d[15], 3 * d[16], - 3 * d[17], 3 * d[18], 3 * d[20], 3 * d[21], 3 * d[22], 3 * d[23], - 3 * d[25], 3 * d[26], 3 * d[29], 3 * d[38], - ]; - case 4: - return [ - 4 * d[0], 2 * d[10] + 2 * d[11], 4 * d[13], 4 * d[14], 2 * d[15] + 2 * d[16], - 2 * d[16] + 2 * d[17], 2 * d[17] + 2 * d[18], 2 * d[18] + 2 * d[19], - 2 * d[19] + 2 * d[20], 2 * d[20] + 2 * d[21], 2 * d[21] + 2 * d[22], - 2 * d[22] + 2 * d[23], 4 * d[24], 4 * d[25], 2 * d[27] + 2 * d[28], 4 * d[38], - ]; - case 5: - return [ - 5 * d[0], 5 * d[11], 5 * d[13], 5 * d[15], 5 * d[16], 5 * d[17], - 5 * d[18], 5 * d[19], 5 * d[19], 5 * d[20], 5 * d[21], 5 * d[22], - 5 * d[23], 5 * d[25], 5 * d[27], 5 * d[38], - ]; - case 10: - return [ - 10 * d[0], 10 * d[11], 10 * d[13], 10 * d[15], 10 * d[16], 10 * d[17], - 10 * d[18], 10 * d[19], 10 * d[19], 10 * d[20], 10 * d[21], 10 * d[22], - 10 * d[23], 10 * d[25], 10 * d[27], 10 * d[38], - ]; - default: - error(err, `Unexpected # of hits: ${hits}`); - return d; + case 2: + return [ + 2 * d[0], 2 * d[7], 2 * d[10], 2 * d[12], 2 * d[14], d[15] + d[16], + 2 * d[17], d[18] + d[19], d[19] + d[20], 2 * d[21], d[22] + d[23], + 2 * d[24], 2 * d[26], 2 * d[28], 2 * d[31], 2 * d[38], + ]; + case 3: + return [ + 3 * d[0], 3 * d[9], 3 * d[12], 3 * d[13], 3 * d[15], 3 * d[16], + 3 * d[17], 3 * d[18], 3 * d[20], 3 * d[21], 3 * d[22], 3 * d[23], + 3 * d[25], 3 * d[26], 3 * d[29], 3 * d[38], + ]; + case 4: + return [ + 4 * d[0], 2 * d[10] + 2 * d[11], 4 * d[13], 4 * d[14], 2 * d[15] + 2 * d[16], + 2 * d[16] + 2 * d[17], 2 * d[17] + 2 * d[18], 2 * d[18] + 2 * d[19], + 2 * d[19] + 2 * d[20], 2 * d[20] + 2 * d[21], 2 * d[21] + 2 * d[22], + 2 * d[22] + 2 * d[23], 4 * d[24], 4 * d[25], 2 * d[27] + 2 * d[28], 4 * d[38], + ]; + case 5: + return [ + 5 * d[0], 5 * d[11], 5 * d[13], 5 * d[15], 5 * d[16], 5 * d[17], + 5 * d[18], 5 * d[19], 5 * d[19], 5 * d[20], 5 * d[21], 5 * d[22], + 5 * d[23], 5 * d[25], 5 * d[27], 5 * d[38], + ]; + case 10: + return [ + 10 * d[0], 10 * d[11], 10 * d[13], 10 * d[15], 10 * d[16], 10 * d[17], + 10 * d[18], 10 * d[19], 10 * d[19], 10 * d[20], 10 * d[21], 10 * d[22], + 10 * d[23], 10 * d[25], 10 * d[27], 10 * d[38], + ]; + default: + error(err, `Unexpected # of hits: ${hits}`); + return d; } } else if (d.length === 256) { if (hits > 1) {