Files
horse/zenno/src/routes/doc/fans/fans.ts
T
zephyr 16a5433594
ci/woodpecker/push/horsebot Pipeline was successful
ci/woodpecker/push/zenno Pipeline failed
zenno/doc/fans: new data coverage and fix off-by-ones
2026-10-18 19:31:05 -04:00

74 lines
2.6 KiB
TypeScript

const placementPms = [1000, 400, 250, 150, 100, 90, 80, 70, 60, 50, 40, 30, 25, 20, 15, 10, 10, 10] as const;
/**
* Calculate base fans for a given placement in a race.
* @param winBase Base fans for a win, as displayed in the race menu
* @param finishOrder Placement of the trainee, with winner being 1
* @returns Base fans for the race and placement together
*/
export function baseFans(winBase: number, finishOrder: number): number {
return Math.round(winBase * placementPms[finishOrder - 1] / 1000);
}
const margins = [
[0.75, 0.0, 0.09],
[2.5, 0.02, 0.06],
[4, 0.04, 0.04],
[6, 0.06, 0.02],
[Infinity, 0.09, 0.0],
] as const;
/**
* Calculate fans gained from a career race due to the margin of the finish.
* @param baseFans Base fans from the race and finish order
* @param finishOrder Placement of the trainee, with winner being 1
* @param margin Margin in lengths between the trainee and second if a win, or the winner and trainee if a loss
* @returns Fans gained due to finish margin
*/
export function marginFans(baseFans: number, finishOrder: number, margin: number): number {
if (finishOrder > 5) {
return 0;
}
const [, wb, lb] = margins.find(([m]) => margin <= m)!;
const b = finishOrder === 1 ? wb : lb;
return Math.floor(baseFans * b);
}
const skills = [
[0, 0.0],
[3, 0.02],
[6, 0.03],
[9, 0.05],
[Infinity, 0.08],
] as const;
/**
* Calculate the fans gained from a career race due to skills activated.
* @param baseFans Base fans from the race and finish order
* @param skillsActivated Number of skills the trainee activated during the race
* @returns Fans gained due to skill activations
*/
export function skillFans(baseFans: number, skillsActivated: number): number {
const [, b] = skills.find(([n]) => skillsActivated <= n)!;
return Math.floor(baseFans * b);
}
/**
* Calculate fan gain from a career race.
* @param baseFans Base fans from the race and finish order
* @param marginFans Fans gained due to finish margin
* @param skillFans Fans gained due to skill activations
* @param fanBonus Total fan bonus from support cards
* @param itemBonus Fan bonus from items (MANT glowsticks)
* @returns Total fans gained
*/
export function gained(baseFans: number, marginFans: number, skillFans: number, fanBonus: number, itemBonus?: number): number {
const f = baseFans + marginFans + skillFans;
const s = Math.fround(f * Math.fround(1 + fanBonus / 100));
const p = Math.exp((4 - Math.floor(Math.log10(s))) * Math.LN10);
// Add half a significant figure to account for precision.
const d = Math.floor(Math.round(s * p + 5e-6) / p + 5e-6);
const b = itemBonus ?? 0;
return Math.floor(d * (1 + b / 100));
}