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.70×Impact + 0.15×Supravietuire + 0.15×Eficienta
Se aplica o functie de stretch (exponent 0.65) pentru a pastra diferente clare fara a impinge excesiv notele spre 1 sau 10:
Rating = 1.0 + 9.0 × stretch(Composite) + Bonusuri
Fiecare metrica se compara cu toti participantii meciului prin ranking, de la cel mai bun la cel mai slab. Damping-ul trage capetele clasamentului catre 0.5 in meciurile cu putini jucatori, pentru a evita extremele. Asta inseamna ca un meci de 3v3 nu va produce rating-uri la fel de polarizate ca un 5v5.
Bonusurile si penalizarile sunt dezactivate temporar. Regulile sunt pastrate mai jos pentru reactivare ulterioara.
Tabul Last 10 Games foloseste ultimele 10 meciuri. Tabul Overall foloseste ultimele maximum 100 de meciuri per player, cu minimum 10 meciuri pentru eligibilitate.
// ── 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.65);
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,
damage: p.damage,
gold: p.gold,
deaths: p.deaths,
damage_taken: p.damage_taken,
self_mitigated: p.self_mitigated,
}));
// ── 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 rGold = rankNormalize(raw.map(r => r.gold));
const rHealing = rankNormalize(raw.map(r => r.support_metric));
// ═══ 1. IMPACT (70%) ═══
const rolePerf = Math.min(1.2, Math.max(
r.damage_share * 2.5, // Damage dealer
r.tank_share * 2.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 (15%) ═══
const efficiency = Math.max(rDmgPerGold[i], rCcPerGold[i]);
// ═══ Composite ═══
let composite = 0.70 * impact + 0.15 * survival + 0.15 * efficiency;
// ═══ Bonusuri & Penalizari ═══
const highestDmg = Math.max(...participants.map(p => p.damage));
const lowerDamage = participants.map(p => p.damage).filter(d => d < highestDmg);
const secondHighestDmg = lowerDamage.length ? Math.max(...lowerDamage) : null;
const rank1Damage = p.damage === highestDmg;
const fewestDeaths = p.deaths === Math.min(...participants.map(p => p.deaths));
const mostTakenMitigated = p.damage_taken + p.self_mitigated === Math.max(
...participants.map(p => p.damage_taken + p.self_mitigated)
);
const topGold = rGold[i] === Math.max(...rGold);
const bottomDamage = p.damage === Math.min(...participants.map(p => p.damage));
const mostDeaths = p.deaths === Math.max(...participants.map(p => p.deaths));
const isHealer = rHealing[i] >= 0.75;
const isFrontline = r.tank_share >= 0.30;
const isProtected = isHealer || isFrontline;
// ── Bonusuri dezactivate temporar ──
// if (rank1Damage && r.kp >= 0.65) bonus += 0.60; // The Carry
// if (rank1Damage && secondHighestDmg !== null
// && highestDmg > secondHighestDmg * 1.4) bonus += 0.30; // Hard Carry / 1v9
// if (fewestDeaths && r.kp > 0.50) bonus += 0.40; // Smart Player
// if (mostTakenMitigated && r.kp > 0.40) bonus += 0.40; // The Frontline
// ── Penalizari dezactivate temporar ──
// if (r.kp < 0.45 && bottomDamage && !isProtected) bonus -= 1.50; // Passenger
// if (topGold && r.damage_share < 0.35 && !isProtected) bonus -= 0.70; // Gold Sink
// if (mostDeaths && r.kp < 0.80 && r.damage_share < 0.20 && !isProtected) bonus -= 0.50; // Dead Weight
// ═══ Final Rating (1-10) ═══
rating = compositeToRating(composite, bonus);