import type { PlayerStats } from './leaderboard.js'; export const EFFICIENCY_MIN_GAMES = 5; export const EFFICIENCY_MIN_KILLS = 100; export const EFFICIENCY_MIN_DOWNS = 5; export const efficiencyMetrics = [ { key: 'headshotRate', label: 'Headshots / kills', format: 'percent', description: `Total headshots divided by total kills in ended games. Ranking requires at least ${EFFICIENCY_MIN_GAMES} games and ${EFFICIENCY_MIN_KILLS} kills.` }, { key: 'killsPerDown', label: 'Kills per down', format: 'number', description: `Total kills divided by total downs in ended games. Ranking requires at least ${EFFICIENCY_MIN_GAMES} games and ${EFFICIENCY_MIN_DOWNS} downs.` }, { key: 'averageKills', label: 'Average kills', format: 'number', description: `Total kills divided by games played, including games the player left before they ended. Ranking requires at least ${EFFICIENCY_MIN_GAMES} ended games.` }, { key: 'averageRounds', label: 'Average rounds', format: 'number', description: `Total observed rounds divided by games played, including games the player left before they ended. Ranking requires at least ${EFFICIENCY_MIN_GAMES} ended games.` }, { key: 'completionRate', label: 'Completion rate', format: 'percent', description: `Games with the player in the final report divided by games played. Only ended games count, including games the player left early. Ranking requires at least ${EFFICIENCY_MIN_GAMES} games.` } ] as const; export type EfficiencyMetric = (typeof efficiencyMetrics)[number]['key']; const metrics = new Set(efficiencyMetrics.map((metric) => metric.key)); export function isEfficiencyMetric(metric: string): metric is EfficiencyMetric { return metrics.has(metric); } function sample(stats: PlayerStats, metric: EfficiencyMetric): [number, number] { switch (metric) { case 'headshotRate': return [stats.headshots, stats.kills]; case 'killsPerDown': return [stats.kills, stats.downs]; case 'averageKills': return [stats.kills, stats.gamesPlayed]; case 'averageRounds': return [stats.roundsSurvived, stats.gamesPlayed]; case 'completionRate': return [stats.gamesCompleted, stats.gamesPlayed]; } } /** Calculate from aggregate counters; qualification never changes a recorded value. */ export function efficiencyValue(stats: PlayerStats, metric: EfficiencyMetric): number | null { const [numerator, denominator] = sample(stats, metric); return denominator === 0 ? null : numerator / denominator; } export function efficiencyEligible(stats: PlayerStats, metric: EfficiencyMetric): boolean { if (stats.gamesPlayed < EFFICIENCY_MIN_GAMES) return false; if (metric === 'headshotRate') return stats.kills >= EFFICIENCY_MIN_KILLS; if (metric === 'killsPerDown') return stats.downs >= EFFICIENCY_MIN_DOWNS; return true; } export function efficiencyRequirement(metric: EfficiencyMetric): string { const games = `At least ${EFFICIENCY_MIN_GAMES} ended games`; if (metric === 'headshotRate') return `${games} and ${EFFICIENCY_MIN_KILLS} kills`; if (metric === 'killsPerDown') return `${games} and ${EFFICIENCY_MIN_DOWNS} downs`; return games; } const number = new Intl.NumberFormat('en-US'); const count = (value: number, singular: string, plural = `${singular}s`) => `${number.format(value)} ${value === 1 ? singular : plural}`; export function efficiencySample(stats: PlayerStats, metric: EfficiencyMetric): string { switch (metric) { case 'headshotRate': return `${count(stats.headshots, 'headshot')} / ${count(stats.kills, 'kill')}`; case 'killsPerDown': return `${count(stats.kills, 'kill')} / ${count(stats.downs, 'down')}`; case 'averageKills': return `${count(stats.kills, 'kill')} / ${count(stats.gamesPlayed, 'ended game')}`; case 'averageRounds': return `${count(stats.roundsSurvived, 'observed round')} / ${count(stats.gamesPlayed, 'ended game')}`; case 'completionRate': return `${count(stats.gamesCompleted, 'finish', 'finishes')} / ${count(stats.gamesPlayed, 'ended game')}`; } } const decimal = new Intl.NumberFormat('en-US', { maximumFractionDigits: 2 }); export function formatEfficiencyValue(value: number | null, metric: EfficiencyMetric): string { if (value === null) return '—'; const percent = metric === 'headshotRate' || metric === 'completionRate'; return `${decimal.format(value * (percent ? 100 : 1))}${percent ? '%' : ''}`; }