Technical documentation
Methodology
This page describes the main public methods, their data sources, their limits and my statistical choices. Results remain indicators to interpret in the context of a match.
The data
Game data comes from the official Riot Games API. Its availability and level of detail vary by match and Riot service:
- •
match-v5: full data for every game (timeline, gold, damage, objectives, events) - •
spectator-v5: lobby information when Riot provides it - •
league-v4: ranked ladders, LP, ranks
10
Riot servers
173
analyzed (173 curated)
Every
patch
100%
Riot API
Honesty about the sample: LegendsTracker is an independent project. My database holds roughly 800 to 1,200 games per champion per patch, where sites like LoLalytics or U.GG aggregate 500,000+. I compensate with statistical methods suited to that scale (Bayesian smoothing), but my winrates are naturally more volatile. I would rather be transparent than claim a precision I don't have.
Player DNA: 35 features × 13 archetypes
The Player DNA is an exploratory profile. It summarizes up to 35 indicators from a recent ranked-match window, grouped into 6 areas: Combat, Macro, Mechanics, Mental, Team and Tempo. It can suggest proximity to archetypes, but describes trends rather than personality, overall skill or the cause of a specific action.
Indicators are normalized to surface relative differences and suggest trends to revisit. A signature, block or archetype is not a verdict: it can change with the sample, role played and data available.
Transparency: current limits of the Player DNA
The profile may be displayed as an exploratory reference, but it does not guide a detailed analysis when confidence, history or rank × role normalization is not sufficiently reliable. This prevents an incomplete signal from becoming overly assertive personalized advice.
How a metric becomes a score
Every raw metric (CS/min, vision, damage…) is converted into a 0-100 score through linear interpolation against a rank-calibrated benchmark, then adjusted by a role multiplier. This is the machinery that powers the detailed match grade (next section) and the Player DNA z-scoring.
score = ((value - lower_bound) / (upper_bound - lower_bound)) × 100The score is then clamped between 0 and 100.
Benchmarks by rank
The bounds are calibrated on real data extracted on March 23, 2026 from my database (830k+ ranked games, EUW1). Manual refresh on major patches: a weekly scheduler is on the roadmap:
| Rank | CS/min | KDA | KP% | DPM | Gold/min | Deaths/game |
|---|---|---|---|---|---|---|
| Iron | 4.9 | 2.4 | 37% | 544 | 350 | 7.5 |
| Bronze | 5.8 | 3.4 | 45% | 698 | 401 | 6.5 |
| Silver | 6.6 | 3.5 | 46% | 809 | 434 | 6.4 |
| Gold | 6.8 | 3.5 | 46% | 739 | 428 | 6.4 |
| Platinum | 7.1 | 3.5 | 46% | 763 | 438 | 6.4 |
| Emerald | 7.6 | 3.6 | 47% | 774 | 454 | 6.2 |
| Diamond | 7.6 | 3.6 | 48% | 760 | 452 | 6.0 |
| Master | 7.7 | 3.6 | 48% | 731 | 448 | 5.8 |
| Grandmaster | 7.9 | 3.9 | 49% | 762 | 457 | 5.5 |
| Challenger | 8.1 | 4.2 | 50% | 798 | 466 | 5.3 |
Role multipliers
Benchmarks are adjusted per role to compare a support and an ADC fairly:
| Role | CS | Vision | DPM | Objectives | KP |
|---|---|---|---|---|---|
| Support | ×0.25 | ×2.0 | ×0.55 | ×0.4 | ×1.30 |
| Jungle | ×0.75 | ×1.3 | ×0.70 | ×1.5 | ×1.15 |
| Top | ×1.0 | ×0.8 | ×0.90 | ×0.8 | ×0.90 |
| Mid | ×1.0 | ×0.9 | ×1.0 | ×0.7 | ×1.05 |
| Bot (ADC) | ×1.1 | ×0.8 | ×1.1 | ×0.9 | ×1.10 |
Grading system
Every match gets an overall grade (S, A, B, C, D) based on 6 weighted categories. The grade is computed relative to your rank, not in absolute terms: a B in Diamond is far better than an A in Bronze. Since July 2026, the scales are calibrated per role on real ranked games: a median performance for your role scores ~50, and an S is the top ~2% of performances: reachable in all five roles, support included.
The 6 categories
| Category | Weight | Main metrics |
|---|---|---|
| Teamfight | 25% | DPM, kill participation, damage share* |
| Laning | 20% | CS@15, gold diff@15, XP diff |
| Survival | 18% | Deaths (inverted), KDA, low-death bonus |
| Income | 13% | Gold/min, CS/min |
| Vision | 12% | Vision score, wards destroyed |
| Objectives | 12% | Objective damage/min, tower kills |
* The damage share is role-adjusted: the reference 8-35% range is multiplied by the role played (UTILITY × 0.45 → 3.6-15.75% range, BOTTOM × 1.15 → 9.2-40.25%, etc.). Before this fix, a tank support at 10% damage share was capped around 20/100 on this component (25% of Teamfight) no matter what: now a support at 12% scores the same as an ADC at 28%.
⚠️ These ranges are reachable targets per role, not an additive breakdown: the per-role medians don't sum to 100%. A team's damage share sums to 100% by construction (5 players split 100% of the damage), but each role has a different envelope for what it's expected to weigh individually.
Category weights per role
The 6 weights above are the laner weights (Top / Jungle / Mid / ADC). A support structurally has neither income (gold/CS) nor objective last-hits: judging them on that tanks their grade no matter what. The UTILITY role therefore redistributes those weights toward what a support actually controls.
| Category | Laners | Support |
|---|---|---|
| Teamfight | 25% | 30% |
| Laning | 20% | 15% |
| Survival | 18% | 22% |
| Income | 13% | 4% |
| Vision | 12% | 24% |
| Objectives | 12% | 5% |
Support objectives, 2026-07-15 revision (community feedback): grading a support on objective damage structurally doomed them (measured median: 16/100). The category now grades participation: takedowns (kill or assist) on epic objectives: drakes, herald, grubs, baron: for 75%, and towers for 25%. Being in the right place counts, not hitting the drake. Leaving plate gold to your ADC remains the right play. Laners are still graded on objective damage + towers (0.65/0.35).
How to read a grade (post-match)
Since 2026-05-20, every sub-grade (Laning / Combat / Survival / Objectives / Vision / Farm) in the match summary is clickable. Opening the detail shows you:
- Every sub-metric with your value vs the benchmark for your rank + role and a ▲ / ─ / ▼ status.
- The sub-metric's normalized 0-100 score (colored mini-bar) and its weight % in the category grade.
- A ● marker highlights benchmarks adjusted to your role when an appropriate calibration is available.
- An “X weighs the most in this grade” bubble pointing at the sub-metric responsible when the grade drags down: handy to know what to work on first.
The general rule: a score under 50 / 100 on a heavily weighted sub-metric almost always explains a C or D grade. Conversely, an S / A on the category is never “luck”: there are at least two sub-metrics above 70.
Grade thresholds
Percentile by rank (interpolated)
The raw 0-100 score is converted into a percentile against players of the same rank via stepwise linear interpolation. That avoids discrete jumps: a 79 score is no longer abruptly at 78% and an 80 at 90%, the function lerps between anchors for a continuous progression.
anchors: 0→5, 20→12, 30→22, 40→35, 50→50, 60→65, 70→78, 80→90, 90→97, 100→99Impact map (impact within the game)
The Impact map (impactScore, 0-100) measures your real weight in that specific game, not against your rank. It is role-aware:
impactScore = KP_eff × 0.55 + objective_participation × 0.45For UTILITY, objective participation (a share of team damage: a support does 5-15% of it no matter what) is replaced by vision and presence, which actually measure their presence on the map:
impactScore (UTILITY) = KP_eff × 0.40 + vision_share × 0.30 + assist_density × 0.20 + objective_participation × 0.10Effective KP: weighted by kill volume (Discord feedback 2026-05-30). Raw KP is a % of the team's total kills: 67% on 3 kills (= 2 takedowns) weighed as much as 67% on 26. For scoring only, I apply a volume factor KP_eff = KP × clamp(team_kills / 10; 0.3; 1). Concretely 67%×0.3 ≈ 20 finally lands below 31%×1 = 31: in a 3-26 stomp, the enemy support at 31% / 26 kills comes out on top. Raw KP is still displayed as-is (67%): only its internal weight changes.
MVP / LVP
The MVP badge goes to the player with the best globalScore (0-100) among the 10 players. The formula is role-aware:
globalScore = impactScore×0.38 + min(KDA, 8)×7.5 + DPM/maxDPM×18 + Vision/maxVision×12 + CS/maxCS×10 + Gold/maxGold×10 - Deaths×1.5 + 4For UTILITY, the formula drops CS/Gold (irrelevant for the role) and redistributes toward vision and impact:
globalScore (UTILITY) = impactScore×0.45 + min(KDA, 8)×7.5 + Vision/maxVision×18 + DPM/maxDPM×6 - Deaths×1.5 + 4The KDA contribution is capped at 8: without that cap, a Perfect KDA (e.g. 6/0/28 → KDA 34) saturated the score at 100 and killed MVP discrimination between Perfect-KDA players. impactScore itself is role-aware (see Impact map above, volume-weighted KP included).
Carry: every role in its own way
- Damage Carry: laners who carry through kills. Kill share ≥ 50% of the team, ≥ 5 kills, in a win.
- Engage Carry: supports who carry through engage/peel/vision. UTILITY + win + KP ≥ 65% + ≤ 3 deaths + ≥ 1.5 vision/min. Before 2026-05-09, the generic "Carry" badge structurally excluded supports.
“God of X” game badges
Every game also hands out “best of the game” badges on a given metric. Since 2026-05-30, the “God of X” badges require absolute floors on top of being the best: so a 1/3/1 in a stomp doesn't grab “Support God” just for being the only support on their team:
- Support God: best
supportScoreof the game, and ≥ 10 assists and ≥ 25 vision. - Vision God: best vision score, and ≥ 25 total and ≥ 1.4/min (a long game with mediocre vision no longer cuts it).
- CS God: best CS/min among laners, ≥ 7 (supports excluded).
Deliberate choice: I keep recognition in defeat. A huge support game, or genuinely elite vision (44 vision in 18 min = 2.4/min), deserves its badge even on the losing team: the wards you placed exist whether you win or not. The supportScore that ranks the “Support God” combines KP, vision, assists minus a per-death penalty: a 0.6 KDA no longer edges out a clean support.
Role Signature: every role has its own KPIs
Overall grades give an average, but a splitpushing Top and a carry ADC don't share the same responsibilities. The Role Signature is a composite indicator specific to each role, built from available metrics. It helps review a match and does not cover every responsibility of a role.
Formula: score = Σ(kpi_grade × kpi_weight) with S=95, A=82, B=68, C=52, D=30. The result is a /100 score reflecting what your role is supposed to do.
The 5 signatures
Side pressure + TF impact
Tempo · Ganks · Objectives
Damage · Priority · Roams
Damage · Farming · Survival
Vision · Peel · Presence
Support: sub-archetype weighting: the 5 UTILITY weights shown above are the default set. Since May 12, the UTILITY role applies 5 distinct weight sets based on the champion's sub-archetype (enchanter, tank, catcher, mage, or default): a catcher Leona is judged mostly on her KP and roams, an enchanter Soraka mostly on her vision and peel.
Concrete example: an ADC at 30% dmg share gets an A (28%+ threshold), and since that weighs 35% of their score, it directly impacts the overall grade. A support with the same stat also gets an A but it weighs 0% in their role (not a KPI): their vision/min counts 30% instead. Every role is judged on what truly belongs to it.
Death analysis: contextual and role-aware
Deaths are tagged and scored from Riot timeline events (available positions, proximity, wards, objectives, HP, gold difference). The 0-100 score and categories are review heuristics: they do not prove intent, a team call or the exact cause of a death.
The 13 detected tags
teamfightDeath in a 3v3+, allies nearby
good_tradePositive trade (enemy kills > ally kills ±10s)
objective_tradeDeath for a secured objective
gankedKiller/assist from another lane (jungler, roam, TP)
solo_killedKilled in a 1v1 (0 assists, 1 nearby enemy)
caught_outAlone against 2+ enemies, outside a teamfight
overextendEnemy territory + 0 nearby allies
no_vision0 allied wards placed in the 90s window
facecheckBush/river without a ward, enemies nearby
pre_objectiveAlone, < 2 min before a major spawn
dive_gone_wrongNear a tower + enemy territory
repeated_deathSame killer 2+ times
low_resource_deathHP < 30% 30s earlier: should have backed
Quality score
Base at 50. Each tag applies a bonus/penalty:
score = 50 + bonuses(tags) - penalties(tags) + phase_modifier(role, phase) + role_bonus| Tag | Points | Extra condition |
|---|---|---|
| objective_trade | +30 | |
| good_trade | +25 | |
| teamfight | +15 | |
| objectiveSecuredNearby | +20 | context bonus |
| nearbyAllies ≥ 3 | +10 | context bonus |
| caught_out | -25 | |
| pre_objective | -25 | |
| overextend | -20 | |
| facecheck | -20 | |
| dive_gone_wrong | -20 | |
| no_vision | -15 | |
| repeated_death | -15 | |
| low_resource_death | -15 |
Contextual modifiers
Game phase
- Early (< 14 min):
caught_out -5,solo_killed -3,objective_trade +5: lane mistakes impact the whole game - Mid (14-24 min): neutral
- Late (≥ 25 min):
caught_out -8,teamfight +8,objective_trade +8: one solo mistake can lose the game
Role bonuses
- Support: if a low-HP ally is nearby,
+10(peel attempt recognized): nocaught_out/solo_killedtag - Jungle: no overextend in late game (invading = the job), early caught_out
+3 - ADC: no overextend in late game side lanes (splitpushing = the job), late
+3
Ward detection fix (April 2026): Riot API WARD_PLACED events don't always include the position. I now distinguish confirmed nearby wards (known position + within the 3000 radius) from vision effort (total allied wards placed in the 90s window, position or not). The no_vision tag only fires if zero wards were placed: no more false positives unfairly costing -15 pts.
Lifetime-aware pre-objective vision fix (May 2026): previously, the Wards before objectives metric only counted wards placed within the [drake−2min; drake] window. A Control Ward placed 5 minutes before the drake (typical jungler anticipation) was never counted → the coaching AI wrongly blamed the player for “not warding the drake”. Now, every ward gets a computed lifetime window: Control Ward = ∞ (until killed/replaced), Yellow Trinket = 120s, Sight Ward = 150s, Blue Trinket = 60s. WARD_KILL events are matched by (wardType, position ±150 units) to close the window earlier if the ward was destroyed. The signal sent to the AI Coach goes from a raw counter to qualitative coverage: “X/Y objectives with ≥1 active ward in river/enemy territory 60s before the spawn”.
Progression plan: deterministic and based on your Player DNA
The 14-day progression plan is generated server-side from your Player DNA, without a generative AI call. It relies on available signals, your role and measurable objectives: it remains a working reference, not a complete diagnosis.
How it's generated
1. The server uses the available recent ranked-game window to calculate Player DNA.
2. It prioritizes your block (the signal to work on), gaps with your archetype prototype, and signals that apply to your role.
3. Only weaknesses that map to a measurable in-game metric become objectives; non-measurable signals are skipped rather than invented.
4. Targets use the available rank and role references, then adapt to context when it is known.
5. The number of objectives and required games adapts to the coaching tier so the plan remains achievable.
6. The daily focus alternates among the categories of the objectives that were actually selected.
Player DNA is used now: the generator already starts from the block, prototype gaps and role when the signal is sufficient. A weakness without a reliable proxy does not become an automatic objective.
Examples of measurable objectives
Each objective uses an extractable metric from match data, a target and hand-written action ideas. Here are examples:
Improve your CS/min
cs_per_minIncrease vision score / min
vision_per_minReduce your deaths / game
deaths_per_gameImprove your KDA
kdaShow up to team events
kill_participationPresence at objective fights
kp_objectivesIncrease your DPM
damage_per_minPlace more wards
wards_per_gameBuy more control wards
control_wardsIncrease your damage share
damage_shareContribute more to neutral objectives
objective_damageOptimize your gold/min
gold_per_minObjective evaluation
When a compatible match and the necessary data are available, the system can evaluate each objective using explicit rules:
for (match of last20RankedMatches) {
if (extractMetric(match, obj.target.metric) >= obj.target.value) {
obj.qualifying_matches.push(match.id)
}
}
if (obj.qualifying_matches.length >= obj.target.games_required) {
obj.completed = true
}Validation appears after available data are refreshed. A single value does not replace the context of a match or sustained improvement.
How are objectives defined? Thresholds use explicit rules and the available data. They are work references; they do not by themselves summarize skill level or the quality of a match.
Skill tree
The Progression tab also shows a micro-skill tree (recall timing, survival, objective focus, wave management, vision…). Scores use available timelines and stats over a recent window; they remain reading indicators, not an exhaustive measurement of every in-game decision.
Transparency rule: if a skill is not measurable on your recent games (missing timeline, missing data), it's shown as “unavailable”: never padded with a neutral grade that would fake it. The tree's overall grade only averages the skills that were actually evaluated.
Tier List: Bayesian + Wilson score
With a sample of ~1,000 games per champion, raw winrate is too volatile. A champion at 60% winrate over 80 games isn't necessarily better than one at 52% over 3,000 games.
The small-sample problem
With little data, rarely played champions show extreme winrates (40% or 60%) through pure statistical variance. I combine two complementary estimators: Bayesian smoothing to estimate the true WR, and the Wilson score interval (95% lower bound) to penalize small-sample uncertainty.
1. Bayesian winrate formula
bayesianWR = (games × winrate + C × prior) / (games + C)C = 150: smoothing constant (the bigger C, the harder the estimate is pulled toward the prior)
prior = 50%: prior winrate (expected average)
games: number of games on the champion
winrate: observed raw winrate
Concrete example: A champion at 60% WR over 50 games: bayesianWR = (50 × 60 + 150 × 50) / (50 + 150) = 52.5%. Over 500 games: (500 × 60 + 150 × 50) / 650 = 57.7%. The bigger the sample, the less the smoothing matters.
2. Wilson score interval (95% lower bound)
The Bayesian estimates a central point but says nothing about uncertainty. Two champions can share the same bayesianWR with radically different confidence intervals. The Wilson score gives the lower bound of the 95% CI: a champion with 50 games (σ ≈ 7%) mechanically ends up below a champion with 1000 games (σ ≈ 1.6%).
wilsonLower = (p̂ + z²/2n − z·√(p̂(1−p̂)/n + z²/4n²)) / (1 + z²/n)with z = 1.96 (95% CI), p̂ = observed winrate as a fraction, n = games.
3. Final score
Mix of 70% Bayesian (global stability) + 30% Wilson (penalizes uncertainty) + popularity bonus:
score = (bayesianWR × 0.7 + wilsonLower × 0.3) + max(log₂(games / avgGames) × 3, −3)The bonus rewards popular champions (meta-validated by volume) with no positive cap. The penalty has been floored at −3 points since 2026-05-20: the Bayesian regression already handles small-sample reliability: beyond that, it would double-punish viable pocket picks (a champion at 200 games in a pool averaging 800 took −6, about a full tier on top of the regression).
The Bayesian/Wilson mix stabilizes tiers from patch to patch: “lucky outlier” champions climb less.
Tier assignment
Champions are sorted by descending score, then split by percentile:
Small pools. “Top 5% / 15%” means nothing on a tiny pool: a pool of 5 ranked champions or fewer (typically 24-48h after a patch) gets neither S+ nor S: the best caps at A until the pool reaches 6 champions. I don't manufacture an elite tier out of merely being 1st among a handful of champions.
The minimum threshold is adaptive: between 10 and 50 games to be included, based on the pool's average volume. On a freshly released patch (little data) the threshold drops toward 10 so the list doesn't empty out, then climbs back toward 50 as collection catches up: Bayesian/Wilson pulls small samples toward the 50% prior anyway.
Theoretical rank estimation
The theoretical rank is an experimental estimate based on available performance indicators. It does not say which rank you should be at and may be unavailable when the sample or calibration cannot support a sufficiently reliable reading.
Algorithm
Applicable indicators are normalized against the available corpus. A relative value alone is not a complete measure of skill.
The estimate aggregates only observable metrics relevant to the role; signals without a sufficiently reliable source and playstyle traits are excluded.
Selected metrics are weighted to produce a relative trend. This weighting is a product heuristic, not proof of skill or potential.
Tier offset = round(aggregated z ÷ 0.55), clamped to ±3 tiers. 0.55σ ≈ one step (calibrated on the empirical gap between tier means on mechanical features).
Theoretical rank = current rank + offset, clamped between Iron and Challenger.
Weighting (sum = 1.0)
cs_efficiencyMechanics15%vision_per_minMacro10%objective_priorityMacro10%damage_per_goldMechanics10%multi_kill_rateMechanics8%tower_pressureMacro6%anti_gank_awarenessMacro6%kp_dmg_balanceTeam6%snowball_conversionOutcomes5%tilt_resistanceMental4%post_death_recoveryMental4%comeback_rateMental4%hourly_consistencyMental3%comeback_contributionOutcomes3%role_expectation_matchTeam2%death_evennessTeam2%wave_managementMacro (laners)2%Confidence score
confidence = min(100, 35 + sampleFactor × 50 + min(1, |aggregated z| ÷ 1.5) × 15)The estimate is hidden when history or signal is insufficient. This confidence score describes the strength of the available signal; it does not guarantee that the estimate reflects a real rank.
Session indicators: 7 signals
This tool reviews your recent session (consecutive matches less than 90 minutes apart) using seven heuristic rules. It does not detect or diagnose tilt or mental state.
Lose streak
Number of consecutive losses
Thresholds: 2 = low, 3 = medium, 4 = high, 5+ = critical
CS decline
CS/min drop between the 1st and 2nd half of the session
Thresholds: 10-15% = low, 15-20% = medium, 20-30% = high, 30%+ = critical
Deaths rising
Rising average deaths
Thresholds: 1.5-2 = low, 2-3 = medium, 3-4 = high, 4+ = critical
Champion switching
Unique champions / games played ratio
Thresholds: 80-90% = medium, 90%+ (5+ games) = high
Queue rage
Fast requeue after a loss (< 2 min)
Thresholds: 1 = medium, 2+ = high, 3+ = critical
Session winrate
Overall session winrate
Thresholds: 15-35% = medium, ≤15% = high, 0% = critical
Shorter games
Dropping game length (earlier FFs)
Thresholds: > 3 min drop = medium, > 6 min = high
Score calculation
tiltScore = Σ(severity_weight) + long_session_bonusWeights: low = 8, medium = 15, high = 22, critical = 30
Session bonus: linear adjustment from four games. It describes duration and a sequence of games, not measured mental fatigue.
Badge system
31 badges across 6 active categories, each with 4 progressive rarity levels. Thresholds are fixed and absolute (not rank-relative), but some badges are adjusted by main role to avoid comparing different responsibilities.
Combat
KDA, participation, damage and multi-kills
Farm
CS/min, gold/min and farming consistency
Vision
Vision score, wards placed and destroyed
Survival
Survival and getting back into the game
Objectives
Towers and neutral objectives
Progression
Streaks, winrate and main champion
Each badge has 4 tiers: Bronze → Silver → Gold → Legendary. Progress toward the next tier is shown as a percentage.
Team context
The system highlights unusual performances or situations from the available statistics. These are heuristic signals: they cannot establish a player's intention, AFK status or smurf status.
Unusually low activity
- •Time played below 60% of the match duration
- •A game over 15 minutes with very little gold/min and damage
- •A game over 15 minutes with few resources, little damage and few deaths; thresholds account for the support role
Very low performance
- •At least 10 deaths, a low team-damage share and a very low KDA
- •Many deaths per minute with low impact and KDA
Dominant opposing performance
- •KDA, damage per minute and participations far above the lobby; this signal describes a performance, not the account's actual level
- •Exceptionally high KDA, kills and damage per minute relative to the match
Individual perf vs team
These indicators put individual contributions in context against the team result. They help compare metrics, but cannot assign a win, loss or carry to one person with certainty.
Why 500,000 damage means nothing
Raw numbers (total damage, total gold, total CS) are dependent on game duration and the team's tempo. An ADC at 500k damage over 40 min is worse than an ADC at 300k over 22 min. And a carry at 35% damage share in a loss often played better than a teammate at 40% in a 25-3 win.
Rule: a metric is only comparable if it's put in context. Either normalized per minute (DPM, GPM, CSPM), per team share (damage share, kill participation), or against an adjusted benchmark (role + rank).
Damage share > Damage total
The damage share is your slice of the team's damage. It removes the “longer game = more damage” effect and surfaces your relative contribution.
damage_share = your_damage / Σ(team_damage) × 100| Player | Raw damage | Duration | DPM | Damage share | Verdict |
|---|---|---|---|---|---|
| ADC A (loss) | 120,000 | 25 min | 4,800 | 38% | Carry ✓ |
| ADC B (win) | 180,000 | 45 min | 4,000 | 22% | Mid |
Player B has more raw damage; this comparison illustrates a relative indicator, not a complete judgment of gameplay quality.
Role normalization
Every metric is scaled by the role's expectations. A support at 800 DPM is excellent; an ADC at 800 DPM is a red flag. I use benchmark multipliers to fairly compare roles that aren't playing the same game.
| Role | Expected DPM | Target damage share | Vision/min | CS/min |
|---|---|---|---|---|
| TOP | ~600 | 22-26% | 1.2 | 7.5 |
| JUNGLE | ~550 | 20-25% | 1.4 | 5.5 |
| MID | ~750 | 25-30% | 1.2 | 7.8 |
| ADC | ~800 | 28-34% | 1.1 | 8.5 |
| SUPPORT | ~350 | 10-15% | 3.5 | 0.8 |
Your adjusted score = raw_metric / role_benchmark × 100. A support at 3.8 vision/min scores 109% on that axis: even with 0 CS.
Rank normalization
Within a single role, expectations vary wildly between Bronze and Master. I apply per-rank benchmarks so an Iron player isn't compared to a Diamond. The same 7.5 CS/min is an A+ in Bronze and a C in Master.
adjusted_score = raw_metric × (Platinum_benchmark / your_rank_benchmark)Platinum serves as the reference rank (middle of the distribution). Benchmarks combine a stable baseline and, when available, newer data; the displayed patch and corpus remain the context for interpretation.
Game context (stomp vs close)
A 10/2/5 KDA in a 30-5 stomp isn't as impressive as a 6/3/12 in a 40-35 close game. I compute a closeness factor to weight performances:
closeness = 1 - |blue_kills - red_kills| / max(blue_kills, red_kills)Stomp 30-5
closeness ≈ 0.17
Perfs toned down (easy carry)
Close 42-38
closeness ≈ 0.90
Perfs valued up (clutch)
This factor is only applied to the final grade (S/A/B/C/D), not to the axis scoring itself: to avoid punishing someone who performs in a stomp.
Who carried? The attribution algorithm
The Carry badge and the individual MVP rank are computed from a composite score: not KDA alone:
carry_score = damage_share × 0.40 + KP × 0.25 + gold_share × 0.15 + obj_damage_share × 0.20- •Damage share (40%): what you did to the enemies
- •Kill Participation (25%): your teamfight presence
- •Gold share (15%): you were the one snowballing economically
- •Objective damage share (20%): you brought down the turrets/drakes/baron
KDA doesn't enter carry_score directly. A carry at 15/0/8 with 20% damage share and 40% KP didn't carry: they just farmed without pressure. The real carry is the one who absorbs the resources AND converts them into objective pressure.
What I CANNOT fix
- △Macro decisions (rotations, calls, voice shot-calling): invisible in the Riot API
- △Teamfight positioning (front vs back): per-frame XY coordinates aren't available
- △Focus targets (who focused whom): aggregated only, not per target
- △The champion factor (a Yuumi will always have less damage share): compensated by Role Signature
For these blind spots, I lean on the AI coaching, which reads the full timeline and adds context: where deterministic formulas stop.
Theorycraft Engine
The Theorycraft is an interactive build simulator (not an optimizer) that computes, from a combo you define, a damage estimate honoring curated champion passives, AP/AD/HP scalings, and the main item effects (spellblade, on-hit, procs, burn). It doesn't reproduce a meta database like Mobalytics: it recomputes from official Riot formulas across the covered scope.
Important: it's a lab, not an absolute truth. The engine does not search for the optimal build: it prices out the sequence you feed it. Item recommendations are hand-curated presets plus a heuristic fallback scoring: useful to compare options, but not a global optimization. League is too complex (rune ↔ item interactions, unique passives, spike thresholds, matchups, power curves) for a single score to capture “the” ideal build.
Hand-curated data
- ✓173 curated champions in
champion-spells.ts: every spell (Q/W/E/R) + passive with its AD/AP/HP ratios, its triggers (on_hit, on_3_hit, after_ability), and its special notes - ✓84 items in
item-effects.tswith their passive effects (Sheen spellblade, on-hit Nashor/Titanic/BotRK, %HP Kraken, Liandry burn, etc.) - ✓173 curated meta builds in
champion-builds.ts: 173/173 of the roster, including Yunara and Zaahen, with per-item reasons and a kit summary - ✓17 keystones with damage computation (Conqueror stacks, Electrocute, Dark Harvest, etc.) and 45 minor runes with inline FR descriptions
Build suggestions (4 priority levels)
These are starting-point suggestions, not proven optimization. The goal is to hand you a coherent build to load in one click, which you can then tweak by hand.
Step-by-step combo calculator
The combo engine calculateCombo (unified engine, shared between the page and the recommendations) simulates an action sequence (Q W E R A C: or a preset like Burst / All-in / Execute) and estimates step by step:
- →Enemy HP recomputed after each step (lets %HP items scale dynamically: BotRK 8% current HP, Liandry burn 2% max HP/s refreshed between spells: no stacking)
- →Mitigation through effective armor or magic resist (after flat + percent penetration)
- →On-hit procs tracked per auto: Nashor's, Titanic, BotRK, Wit's End, Terminus, Muramana
- →Sheen cooldown 1.5s: Trinity/Lich Bane/Iceborn don't proc twice in a row
- →Elder Dragon execute true damage if enemy HP < 20%
- →Specific empowers (Karma Mantra): the post-R Q (Soulflare) detonates a bonus scaled to R rank (W/E Mantra = utility, no damage)
Acknowledged limits: not every unique passive is modeled identically to the game (some conditional effects, heals, shields, CC, and rune ↔ item interactions are simplified or ignored). The engine gives a faithful order of magnitude to compare options, not a frame-perfect reproduction of the League client.
Official Riot quadratic formula
All per-level stats use the official Riot formula:
Stat(n) = Base + Growth × (n - 1) × (0.7025 + 0.0175 × (n - 1))The quadratic coefficient is what gives a champion a non-linear growth curve between level 1 and 18. I use exactly this formula for HP/Mana/AD/Armor/MR, which guarantees the computed stats match the LoL client to the decimal.
Enemy target: champion or jungle monster
To test your build against a real target, you can pick:
- 🎯Any champion: through the picker shared with the main pick, with configurable level and enemy items
- 🐲12 modeled jungle monsters: Drake / Elder / Baron / Herald / Void Grub / Raptor / Krug / Wolves / Gromp / Blue / Red / Scuttle: their HP/Armor/MR stats use the available patch data to simulate an objective combo
- 🛡️4 class presets (Tank / Bruiser / Squishy / Dummy) for quick setup
The HP% slider simulates a low-HP enemy to test executes (e.g. Collector < 5%, Darius R < 20% + 5 bleed stacks, Garen R true damage on the marked villain).
Contextual buffs
- 🐉6 dragon types (Infernal, Mountain, Ocean, Cloud, Chemtech, Hextech) with 0-4 stacks + SOUL at 4
- 👑Baron Nashor: +AD, +AP, +empowered siege minions
- 🐲Elder Dragon: execute burn true damage on enemies < 20% HP
- ⏱Game time: enables time-based scalings (Gathering Storm, Overgrowth, Kindred marks, Smolder Q stacks)
Current accuracy: modeled vs approximated
I would rather be transparent than claim frame-perfect accuracy. Fidelity varies with the champion, patch, combo and mechanics involved; here is what the engine computes, approximates or ignores.
Modeled exactly
- ✓Aggregated stats (AD/AP/HP/mana/armor/MR/AS/crit/AH/lethality/MS) from items + runes + objective buffs + Riot quadratic level scaling
- ✓Official mitigation order: flat reduction → % reduction → bonus pen → total pen → lethality (applied 1:1, no per-level scaling)
- ✓Crit: base 200% crit damage, Infinity Edge +30% (flat). The Auto action applies the build's average crit (1 + crit% × bonus), the Crit action forces the critical
- ✓Sustain: lifesteal AA-only, omnivamp on all damage (ranged abilities ×0.33), Conqueror heal fully-stacked (melee 9% / ranged 8%)
- ✓Conqueror / Lethal Tempo stacks dynamic per step
- ✓Item synergies: Muramana (bonus mana → AD), Bloodmail (max HP → AD), Rabadon (×1.35 total AP), Seraph (mana → AP), Warmog (+10% bonus HP), IE crit, Sheen family (1.5s CD)
- ✓On-hit per auto: Nashor's, Titanic, BotRK, Wit's End, Terminus, Muramana, Kraken (3rd hit: 140→310 by level, up to +50% based on missing HP)
- ✓Dynamic Black Cleaver shred: stacks tracked per hit (phys auto/spell + phys procs), effective armor refreshed between actions. Cap 6 stacks (30% reduction) (round 2)
- ✓Grievous Wounds: 7 items detected. Mortal Reminder, Morellonomicon, Executioner's Calling, Bramble Vest, Chempunk Chainsword, Thornmail, Oblivion Orb. −40% sustain when the enemy builds one of them (round 2)
- ✓Magic damage amplification: Liandry's Torment (+6% max stacks) + Abyssal Mask (+12% aura) applied to post-mitigation magic damage, multiplicative across items (round 2)
- ✓Lifeline shields folded into EHP: Sterak's Gage (100 + 80% bonus AD, phys + magic), Maw of Malmortius (200 + 150% bonus AD, magic), Kaenic Rookern (18% max HP, magic). Assumed active (defensive scenario) (round 3)
Approximated (limited impact)
- ≈Liandry's amp: assumes max stacks reached (real ramp 6s, applicable to most combos)
- ≈Abyssal Mask: assumes the aura is active (enemy within <700u). No range check
- ≈Black Cleaver: stack added POST-hit, so the very first hit doesn't benefit from the shred. Slightly undervalued for very short combos (1-2 hits)
- ≈Grievous Wounds: assumes mutual contact (the enemy hits you at least once to apply the debuff). No temporal tracking
- ≈DoTs (Liandry burn 3s, Sunfire 1s, Blackfire stacking): total value computed but without a tick-by-tick timeline
- ≈Champion spells: 173/173 covered in the curated scope. Kit data and interactions are reviewed with available patches; some conditional effects can remain approximate between revisions.
Not modeled (coming)
- ✗Animation / cast time / windup: combos assumed instant cast. Overestimates sustained DPS for champions with long animations (Veigar W, Brand R, etc.)
- ✗Tenacity / slow resist recognized as buffs but not applied to CC duration or MS
- ✗Projectile travel time: ranged autos assumed instant
- ✗Combat-over-time simulation / 3s/6s/18s DPS timeline: instant computation only for now
- ✗Auto counter-build vs enemy archetype (full AP → suggests Maw automatically, etc.)
The theorycraft isn't a direct coaching tool: it's a laboratory. It gives you a realistic order of magnitude behind your build ideas (“what if I go full crit Yasuo?”, “how hard do I hit a Drake with this gear?”, “Mejai's Veigar at 25 stacks, what does that damage look like?”). It doesn't replace game experience: a build that “numbers well” against a dummy can be weak in game because of tempo, matchup or spikes. Combine it with the AI coaching and the Progression plan to close the full loop: data → theory → practice.
Estimated win probability
The win probability is a post-match estimate produced from snapshots of real games. When an exploitable timeline is available, it shows the estimated state at key minutes; it does not predict a live match or prove why a game turned.
What the model looks at
- •Economy and state gaps:
gold_diff,level_diff,kill_diff,tower_diff - •Objective control: dragons, heralds, barons, elders, void grubs
- •Recent momentum:
*_per_minand*_delta_5mfeatures to tell whether the game is accelerating or flipping
Minutes served in the product
The pipeline supports minutes 5, 10, 15, 20 and 25. In the product, I currently serve the 10 / 15 / 20 / 25 snapshots when they exist in the match timeline.
10 min
product snapshot
15 min
product snapshot
20 min
product snapshot
25 min
product snapshot
Probabilistic quality
I separate the model's raw probability from the probability served to the player. The latter is calibrated through isotonic regression to better reflect observed reality.
served_proba = isotonic_calibrator(raw_xgboost_proba)Concretely, it prevents an overconfident model from showing 95% where history says more like 82%. I favor a useful, honest probability over a spectacular score.
Explainability
To explain the score, I use the XGBoost contributions (pred_contribs=True):
- ✓Local top contributors: why this specific match tips toward blue or red
- ✓Global feature effects: which features weigh the most on average at a given minute
- ✓AI reading: an LLM layer then translates these signals into natural language without replacing the ML score
Pipeline robustness
- •Split by
match_idto avoid data leakage between snapshots of the same game - •Evaluation per patch and on a recent window to check the model holds up over time
- •Drift monitoring on feature and probability distributions
- •Bounded hyperparameter tuning to avoid opportunistic overfitting
Transparency and limits
What I do well
- ✓The main methods and limitations are publicly documented on this page
- ✓Bayesian smoothing compensates for my small sample in a mathematically proven way
- ✓Comparisons are hidden or made more cautious when context or sample size is insufficient
- ✓Player DNA (35 indicators × 13 archetypes) remains an exploratory profile, never a truth about a player
- ✓The displayed patch, corpus and volume provide context for interpretation
What I could improve
- △The sample has grown (median ~15,000 games per champion on the current patch) but it is still concentrated on EUW and on the tiers I collect: rare champions, secondary roles and the first days of a patch remain less reliable
- △Benchmarks combine a stable baseline and newer data when available; finer calibration by patch, role and volume would further improve accuracy
- △Player DNA and rank/role comparisons are not reliable in every context; coaching ignores them when that happens
- △The Theorycraft is a simulator, not an optimizer: it prices out the combos you feed it, but doesn't “find” the optimal build (too many factors to model in League). Some unique passives and rune/item interactions are simplified or missing
- △The Tilt Detector relies on heuristics (defined rules), not machine learning
- △Some Riot API metrics aren't available (precise XY position, clicks, camera)
My philosophy
LegendsTracker would rather be honest and useful than claim artificial precision. Tools aim to provide trends worth reviewing and actionable ideas, while flagging missing data and uncertainty.
Last updated: July 25, 2026 · Methods and public pages synchronized with the available features, their limits and the data considered · Grades recalibrated per role on real data (July 15 revision) · Measured references used by the coach: 1v1 and 2v2 matchups, runes, item and skill orders · Tier list with sample threshold · Post-match win probability at available snapshots · Theorycraft as a simulator · Riot Games API data
LegendsTracker isn't endorsed by Riot Games and doesn't reflect the views or opinions of Riot Games.