Robolyst Robolyst
← FTC reference

FTC Scouting Metrics

Every number on a Robolyst team or event page, with the formula behind it. These are computed from the match results themselves, with no scouting sheets and no judgement calls, which means they are reproducible, and also that they inherit every quirk of the schedule they came from.

All of them are computed per event, over that event's qualification matches only. Playoff matches are excluded: alliances are chosen there rather than assigned, so the independence the fit relies on is gone.

How accurate is any of this?

measured, not asserted

The predictions on match pages are tested by replaying finished events the way the season actually arrived: for every qualification match the ratings are refit on only the matches played before it, then the prediction is scored against what happened. Nothing below is the model grading its own homework on matches it had already seen.

How a win probability is produced
μ = Σi∈red ri − Σi∈blue ri
 
σ²alliance = c̄² + Σi∈alliance u · c̄² / ki
 
σmargin = c · √( σ²red + σ²blue ) c = 2, u = 1
 
P(red wins) = Φ( μ / σmargin )

A team with no rating is stood in for by the event mean rather than by zero, and a team with no Consistency contributes a default 30-point spread. c is the one knob here (the backtest re-derives it), and it exists because OPR is a linear model of a game that is not linear.

ri
team i’s rating: OPRc when every team in the match has one, OPR otherwise. The two alliances are never put on different bases.
mean Consistency of that alliance’s teams
ki
qualification matches team i has counted, so a two-match read is quoted wider
u
how much of a rating’s own standard error to carry: 1, the textbook σ/√k reading
c
flat spread calibration, 2: set by the backtest, not by theory
Φ
standard normal CDF (Abramowitz–Stegun approximation, no dependency)
Picks the winner
70%
of decided matches
Brier score
0.191
0.25 = a coin flip
Margin error
±31
points, typical
Calibration
8pp
worst bucket gap
How those four are scored
Brier = (1/N) Σn ( pn − on )² skill = 1 − Brier / 0.25
 
accuracy = |{ n decided : (pn > 0.5) = on }| / |{ n decided }|
 
gapb = mean( pn ∈ bucket b ) − mean( on ∈ bucket b )
 
margin error = (1/N) Σn | μn − actual marginn |
pn
the probability of a red win that was predicted for match n
on
what happened: 1 red win, 0 blue win, 0.5 tie
N
predictions scored — 846 of them, all out of sample
bucket b
one of ten equal slices of predicted probability: 0–10%, 10–20%, … 90–100%
decided
ties are dropped from accuracy rather than counted as half a win

Calibration matters more than accuracy. A model can pick winners well and still be systematically overconfident about every one of them. When these predictions say 70%, red wins close to 70% of the time. The worst bucket is off by 8 points. A margin error of ±31 also means a predicted gap much smaller than that is not really a prediction at all.

How fast a rating settles down

How closely a team's OPRc partway through an event matches where they finish. This is why a number from the first few matches is worth so much less than the same number on Sunday.

  • 12 matches in 0.85
  • 16 matches in 0.92
  • 20 matches in 0.94
  • 28 matches in 0.95
  • 34 matches in 0.97

From 846 out-of-sample predictions across 150 events in the 2025–26 season, measured 2026-08-09. Margin bias is -1.0 points, so the model does not quietly favour red or blue.

Strength

higher is better · shown by default

Schedule-adjusted quality: how strong this team looks accounting for who they faced.

How it is computed

Clean OPR + (average opponent OPR − event average OPR).

Strengthi = CleanOPRi + ( oppOPRi − OPR̄ )
 
oppOPRi = (1/mi) Σr∈Mi mean{ OPRj : j ∈ Or }
 
OPR̄ = (1/T) Σj OPRj
Mi, mi
the rows team i appears in, and how many of them there are
Or, Pr,i
row r's opponents, and row r's teams excluding i
OPR̄
the event's mean OPR, taken over all T teams
oppOPRi
mean of per-match opponent means: each match counts once, however many robots were on the other side
How to read it

Quality adjusted for who a team actually had to beat. A team that drew the hardest schedule at the event gets credited for it; one that never faced a strong alliance gets discounted.

Watch out

The adjustment is relative to this event only. It says nothing about how the event compares to any other.

Consistency

lower is better · shown by default

Std. dev. of the alliance's score versus its expected total across this team's matches (lower = steadier). Reflects alliance performance, not the team in isolation.

How it is computed

Population standard deviation of a team’s match residuals (each residual being the alliance’s penalty-free score minus the sum of its members’ Clean OPRs), scaled up by sqrt(rows / (rows − teams)) to undo the shrinkage the least-squares fit itself introduces.

er = ( finalr − foulopp(r) ) − Σj ∈ row r CleanOPRj
 
Consistencyi = √( (1/mi) Σr∈Mi (er − ēi)² ) · √( R / (R − T) )

Undefined (shown as —) when a team has fewer than two rows, or when R ≤ T and the fit has no degrees of freedom left to measure spread with.

R, T
alliance-side rows (two per qualification match, minus any empty alliance) and teams at the event
Mi, mi
the rows team i appears in, and how many of them there are
er
row r’s residual: what the alliance actually scored minus what the fit expected
i
mean residual across team i’s own rows
√(R/(R−T))
degrees-of-freedom correction: undoes the shrinkage a fit with T parameters puts on R residuals
How to read it

How far the alliance’s actual results strayed from what the model expected, match to match. Lower is steadier.

Watch out

It measures the alliance, not the team alone: a team with chaotic partners looks inconsistent. A value near zero at a tiny event means the fit was degenerate, not that the team was flawless, which is why season leaderboards drop zeros.

Total Luck

higher is better · shown by default

Combined partner + schedule luck.

How it is computed

Partner luck + schedule luck.

TotalLucki = PartnerLucki + ScheduleLucki
 
= ( partnerOPRi − OPR̄ ) + ( OPR̄ − oppOPRi )
 
= partnerOPRi − oppOPRi

The event average cancels, so total luck is just how much stronger a team’s partners were than its opponents.

partnerOPRi
mean partner OPR across team i’s matches
oppOPRi
mean opponent OPR across the same matches
How to read it

The whole schedule effect in one number. Two teams with the same record and very different total luck did not have the same event.

Watch out

Large positive or negative values are common at small events with short schedules.

OPRc

higher is better · expert column

Constrained offensive contribution from auto + teleop scoring (non-negative).

How it is computed

Non-negative least squares is run once per scoring section of the game (auto, teleop, endgame, and whatever else the season breaks out), over every qualification match at the event. OPRc is the sum of a team’s section estimates.

for each scoring section s:
x(s) = argmin ‖A x − b(s)‖² subject to x ≥ 0
b(s)r = points row r scored in section s
 
OPRci = Σs x(s)i

The constrained solve has no closed form. It is run as Lawson–Hanson active-set NNLS: the passive set is re-solved with the ridge normal equations shown under OPR (λ = 1e-12), and columns that would go negative are pushed back to zero and held there.

A
design matrix, R×T. A[r][i] = 1 when team i played on alliance-side r, otherwise 0
R, T
alliance-side rows (two per qualification match, minus any empty alliance) and teams at the event
x(s)
the non-negative ratings for section s: one solve per section, summed at the end
How to read it

A constrained offensive contribution: the points a team is estimated to put on the board itself. Because each section is solved separately and forced non-negative, a team can never be credited with negative scoring, and the split shows where their points come from.

Watch out

Only available for seasons where FIRST publishes per-section score details. Events without them fall back to OPR.

Clean OPR

higher is better · expert column

OPR computed with penalty points removed.

How it is computed

The same least squares as OPR, fit against final score minus the opponent’s committed fouls instead of the raw final.

(ATA + λI) x = ATb
 
br = finalr − foulopp(r)
 
CleanOPRi = xi
A
design matrix, R×T. A[r][i] = 1 when team i played on alliance-side r, otherwise 0
finalr
the alliance's final score on row r, as the scorekeeper published it
opp(r)
the opposing alliance in the same match as row r
foulopp(r)
penalty points the OPPOSING alliance committed: the points that were added to row r’s final
λ
ridge term, 1e-6: present for conditioning only, not as a real prior
How to read it

OPR with the penalty points stripped out. Use it when comparing teams across events where officiating differed.

Watch out

An alliance’s own `foul` value is points it gave away to the other side, which is why the opponent’s fouls, not its own, are what get subtracted here.

OPR

higher is better · expert column

Offensive Power Rating: estimated points a team adds to its alliance score.

How it is computed

Ordinary least squares over the event’s qualification matches. Each alliance-match is one equation (the sum of its teams’ ratings should equal that alliance’s final score), and the whole system is solved at once with a tiny ridge term (λ = 1e-6) for conditioning.

x = argmin ‖A x − b‖² + λ‖x‖²
 
solved as (ATA + λI) x = ATb
 
br = finalr OPRi = xi
A
design matrix, R×T. A[r][i] = 1 when team i played on alliance-side r, otherwise 0
R, T
alliance-side rows (two per qualification match, minus any empty alliance) and teams at the event
x
the T ratings being solved for, one per team
finalr
the alliance's final score on row r, as the scorekeeper published it
λ
ridge term, 1e-6: present for conditioning only, not as a real prior
How to read it

The classic offensive power rating: how many points a team is estimated to add to whatever alliance they are on. It is the baseline every other number here is built from.

Watch out

OPR is fit to the final score, penalties included. Points an opponent handed over as fouls land in it, which is what Clean OPR removes.

True OPRc

higher is better · expert column

Penalty-free own-earned contribution (NNLS on auto + teleop).

How it is computed

A single non-negative least squares fit against the penalty-free total (the sum of all scoring sections) rather than section by section.

btruer = Σs b(s)r
 
TrueOPRci = [ argmin ‖A x − btrue‖² , x ≥ 0 ]i

Same NNLS solver as OPRc, one solve instead of one per section. That single difference is why the two do not add up to each other.

A
design matrix, R×T. A[r][i] = 1 when team i played on alliance-side r, otherwise 0
btruer
row r's scoring sections added up: the alliance's score with every penalty point excluded
x ≥ 0
the non-negativity constraint, applied once to the total rather than per section
How to read it

The cleanest available read on what a team earned themselves: no penalty points, no negative values, no credit for what a partner did.

Watch out

True OPRc and OPRc are fit differently and will not agree. Because the non-negative floor is applied once per section rather than once overall, OPRc is systematically the higher of the two. That gap is a property of the method, not a signal about the event. A gap far wider than usual does point at section fits disagreeing, which happens at short or noisy events.

OPRc Strength

higher is better · expert column

Strength computed from the constrained OPRc estimator.

How it is computed

True OPRc + (average opponent OPRc − event average OPRc).

OPRcStrengthi = TrueOPRci + ( oppOPRci − OPRc̄ )
 
oppOPRci = (1/mi) Σr∈Mi mean{ OPRcj : j ∈ Or }
 
OPRc̄ = (1/T) Σj OPRcj

Identical to Strength with every OPR replaced by its constrained counterpart: the same code path runs a second time with OPRc standing in for OPR and True OPRc standing in for Clean OPR.

Mi, mi
the rows team i appears in, and how many of them there are
Or, Pr,i
row r's opponents, and row r's teams excluding i
OPRc̄
the event's mean OPRc, over all T teams
How to read it

The same schedule adjustment as Strength, but built on the constrained estimator, so penalties and negative contributions never enter it.

Watch out

Needs score details, like every OPRc number.

Reliability

higher is better · expert column

Consistency score from 0 to 1 derived from scoring variability (higher = more consistent).

How it is computed

1 / (1 + consistency / scale), clamped to 0–1, where scale is the event’s average Clean OPR (floored at 1).

Reliabilityi = clamp( 1 / (1 + Consistencyi / S) , 0 , 1 )
 
S = max( (1/T) Σj CleanOPRj , 1 )
S
the event's mean Clean OPR, floored at 1 so a near-zero-scoring event cannot divide by nothing
clamp(v,0,1)
holds the result inside 0–1
How to read it

Consistency rescaled so it can be compared across events with different scoring levels. Higher is steadier.

Watch out

Undefined for a team with fewer than two qualification matches.

Partner Luck

higher is better · expert column

Record effect attributable to alliance-partner quality.

How it is computed

Average partner OPR − event average OPR.

PartnerLucki = partnerOPRi − OPR̄
 
partnerOPRi = (1/mi) Σr∈Mi mean{ OPRj : j ∈ Pr,i }
Mi, mi
the rows team i appears in, and how many of them there are
Or, Pr,i
row r's opponents, and row r's teams excluding i
OPR̄
the event's mean OPR, taken over all T teams
How to read it

How much better or worse than average this team’s alliance partners were. Positive means the draw helped.

Watch out

A rating of the schedule, not of the team. It belongs beside a record, not inside it.

Schedule Luck

higher is better · expert column

Record effect attributable to schedule difficulty.

How it is computed

Event average OPR − average opponent OPR.

ScheduleLucki = OPR̄ − oppOPRi
 
oppOPRi = (1/mi) Σr∈Mi mean{ OPRj : j ∈ Or }
Mi, mi
the rows team i appears in, and how many of them there are
Or, Pr,i
row r's opponents, and row r's teams excluding i
OPR̄
the event's mean OPR, taken over all T teams
How to read it

How much weaker than average the opponents were. Positive means an easier road.

Watch out

Same caveat: this describes what the team was dealt, not how they played.

CCWM

higher is better · retired: still computed, no longer tabled

Calculated Contribution to Winning Margin (offense minus defense allowed).

How it is computed

Least squares fit against score margin (own final − opponent final).

(ATA + λI) x = ATb
 
br = finalr − finalopp(r)
 
CCWMi = xi
A
design matrix, R×T. A[r][i] = 1 when team i played on alliance-side r, otherwise 0
finalr
the alliance's final score on row r, as the scorekeeper published it
opp(r)
the opposing alliance in the same match as row r
λ
ridge term, 1e-6: present for conditioning only, not as a real prior
How to read it

Calculated contribution to winning margin: offense and defense together. A team that scores modestly but shuts opponents down can out-CCWM a pure scorer.

Watch out

In games with little defensive interaction, CCWM is mostly OPR minus noise and adds nothing.

Sched-Adj OPR

higher is better · retired: still computed, no longer tabled

OPR adjusted for the difficulty of the matches actually played.

How it is computed

OPR + (average opponent OPR − event average OPR).

SchedAdjOPRi = OPRi + ( oppOPRi − OPR̄ )

Strength with gross OPR as the base instead of Clean OPR. The adjustment term is the same one.

oppOPRi
mean opponent OPR across team i’s matches, defined exactly as under Strength
OPR̄
the event's mean OPR, taken over all T teams
How to read it

OPR corrected for schedule difficulty, using gross OPR on both sides of the adjustment.

Watch out

Still penalty-inclusive, since it is built on OPR rather than Clean OPR.

Rank, RP, TBP and QP

published by FIRST · shown by default

These four are the only numbers on the Rankings tab that are not computed here. They come straight from the FIRST scorekeeper. They are what actually decided alliance selection at the event, whatever the estimates above say.

Rank
A team's finishing position after qualification matches. Rank 1 picks first at alliance selection. It is decided by ranking points, with the tiebreakers below settling teams that finish level, and it counts only qualification matches; playoff results never move it.
RP — Ranking Points
The primary sort since the 2021-22 season. Each qualification match hands an alliance a small number of RP for hitting the objectives the season's game manual names. Winning is one of them, but so are the game-specific thresholds, which is why a team can rank well without an unbeaten record. Both teams on an alliance always receive the same RP, so RP rewards the alliance, not the robot.
TBP — Tie Breaker Points
The first tiebreaker between teams on equal RP. What it counts is set per season by the game manual: in DECODE it is total autonomous points across the team's qualification matches, with a second tiebreaker on endgame points behind it. Like RP it is credited to the whole alliance, so a team carried by strong partners banks their points too.
QP — Qualifying Points
The pre-2021-22 system, which older events on this site still display. Teams got 2 QP for a win and 1 for a tie, and ties on QP were broken by Ranking Points, which then meant the losing alliance's score in each match. The columns hold the same two API fields either way, so an event from 2019 is labelled QP and RP rather than RP and TBP.
The arithmetic
rank = position after ordering by ( RP , TBP ) descending
 
2021-22 → RP = ( Σ ranking points earned ) / ( qualification matches played )
TBP = set per season; in DECODE, Σ autonomous points
 
→ 2020-21 QP = 2 · wins + 1 · ties
RP = Σ ( losing alliance’s score in that match )

None of this is computed here: the two sort orders are stored exactly as FIRST published them. The arithmetic above is the game manual’s, and the division by matches played from 2021-22 on is why RP arrives fractional.

RP, TBP
the scorekeeper’s two sort orders. The labels swapped in 2021-22; the two API fields behind them never did, which is why a 2019 event shows QP and RP in the same two columns.
Σ
over that team’s qualification matches only; playoff results never enter it
Watch out

Rank is an alliance result read through a schedule. Two teams of equal ability can finish twenty places apart on partner draw alone, which is exactly the gap OPR and Strength exist to close. Read them together: a low rank with a high OPRc is usually a good robot that drew badly.

Where to see them

Every event's Rankings tab carries the full table for that event, every team's Stats tab carries their numbers event by event, and the world rankings aggregate them into a season standing. The record book uses raw scores rather than any of these, because a record should be a thing that happened, not a thing a model estimated.