Rating-ul se calculeaza din datele raw ale meciului, comparat cu toti participantii prin rank-normalizare. Formula e role-agnostic — tank, support, ADC sau fighter sunt evaluate echitabil. Nu exista bonus/penalizare pentru victorie/infrangere — scopul e masurarea contributiei individuale pentru echilibrarea echipelor.
Composite = 0.60×Impact + 0.15×Supravietuire + 0.25×Eficienta
Se aplica o functie de stretch (exponent 0.35) pentru a mari diferentele intre jucatori:
Rating = 1.0 + 9.0 × stretch(Composite) + Bonusuri
Fiecare metrica se compara cu toti participantii meciului prin ranking (cel mai bun = 1.0, cel mai slab = 0.0). In meciuri mici (2-4 jucatori) se aplica damping care trage scorurile catre 0.5 pentru a evita extremele. Asta inseamna ca un meci de 3v3 nu va produce rating-uri la fel de polarizate ca un 5v5.
Se aplica dupa calculul compozit. Unele sunt scalate cu numarul de jucatori (meciuri mici = penalizari mai blande).
Rating-ul overall este media aritmetica a ultimelor 10 meciuri, calculata in timp real din datele raw. La rating egal, se departajeaza dupa Win Rate (L10).
// ── Rank-normalizare ──
function rankNormalize(values) {
const n = values.length;
if (n <= 1) return values.map(() => 0.5);
const indexed = values.map((v, i) => ({ v, i }));
indexed.sort((a, b) => b.v - a.v);
const scores = new Array(n);
// damping: N=2 → 0.4, N=3 → 0.6, N=5 → 0.8, N>=8 → ~1.0
const damping = Math.min(1, 1 - 2 / (n + 1));
for (let rank = 0; rank < n; rank++) {
const raw = 1 - (rank / (n - 1));
scores[indexed[rank].i] = 0.5 + (raw - 0.5) * damping;
}
return scores;
}
// ── Stretch + Rating ──
function stretchFromCenter(value, exp) {
if (value < 0.5) return 0.5 - Math.pow((0.5 - value) / 0.5, exp) * 0.5;
return 0.5 + Math.pow((value - 0.5) / 0.5, exp) * 0.5;
}
function compositeToRating(composite, bonusAdj) {
const stretched = stretchFromCenter(Math.max(0, Math.min(1, composite)), 0.35);
return Math.max(1.0, Math.min(10.0, Math.round((1.0 + 9.0 * stretched + bonusAdj) * 100) / 100));
}
// ── Raw metrics per participant ──
const raw = participants.map(p => ({
kp: Math.min(1, (p.kills + p.assists) / teamKills),
damage_share: p.damage / teamDamage,
tank_share: (p.damage_taken + p.self_mitigated) / teamTakenMit,
support_metric: p.healing + p.cc_score * 100,
ka_share: (p.kills + p.assists) / teamKA,
kda: (p.kills + p.assists) / (p.deaths + 1),
damage_per_gold: p.damage / p.gold,
cc_per_gold: p.cc_score / p.gold,
}));
// ── Rank-normalize all metrics ──
const rSupport = rankNormalize(raw.map(r => r.support_metric));
const rKda = rankNormalize(raw.map(r => r.kda));
const rDmgPerGold = rankNormalize(raw.map(r => r.damage_per_gold));
const rCcPerGold = rankNormalize(raw.map(r => r.cc_per_gold));
const rDamage = rankNormalize(raw.map(r => r.damage));
const rGold = rankNormalize(raw.map(r => r.gold));
const rDeaths = rankNormalize(raw.map(r => -r.deaths));
const rKp = rankNormalize(raw.map(r => r.kp));
const rHealing = rankNormalize(raw.map(r => r.support_metric));
const rDamageTaken = rankNormalize(raw.map(r => r.damage_taken + r.self_mitigated));
// ═══ 1. IMPACT (60%) ═══
const rolePerf = Math.min(1, Math.max(
r.damage_share * 5, // Damage dealer
r.tank_share * 5, // Tank
rSupport[i] // Support (rank-normalized)
));
const impact = 0.40 * r.kp + 0.35 * rolePerf + 0.25 * r.ka_share;
// ═══ 2. SURVIVAL (15%) ═══
const survival = rKda[i];
// ═══ 3. EFFICIENCY (25%) ═══
const efficiency = Math.max(rDmgPerGold[i], rCcPerGold[i]);
// ═══ Composite ═══
let composite = 0.60 * impact + 0.15 * survival + 0.25 * efficiency;
// ═══ Bonusuri & Penalizari ═══
const bScale = Math.min(1, (n - 1) / 9);
const isHealer = rHealing[i] >= 0.75; // top 2 support
if (rDamage[i] >= 0.99 && rKp[i] >= 0.99) bonus += 0.60; // The Carry
if (rDeaths[i] >= 0.99 && r.kp > 0.50) bonus += 0.40; // Smart Player
if (rDamageTaken[i] >= 0.99 && r.kp > 0.40) bonus += 0.40; // The Frontline
if (r.kp < 0.25 && rDamage[i] <= 0.15 && !isHealer) bonus -= 1.50; // The Passenger
if (rGold[i] >= 0.99 && rDamage[i] <= 0.25 && !isHealer) bonus -= 0.70; // Gold Sink
if (rDeaths[i] <= 0.15 && r.kp < 0.35) bonus -= 0.50; // Dead Weight
// ═══ Final Rating (1-10) ═══
rating = compositeToRating(composite, bonus);