Rocket League · Expected Goals
Process Over Outcome
Problem:
I’ve been playing Rocket League since the open beta, and as I developed my analytical skill set, I wondered whether tools existed to gauge performance. Established analytics mainly focused on descriptive statistics like goals, boost stolen, and time spent in the opposing half.
Very few seemed to measure underlying performance the way advanced sports analysis does. Just showing what happened doesn’t answer whether the underlying process matches the outcome. The questions I kept coming back to were: What does an ugly win look like from a performance standpoint? What about games where you outshot your opponent and still lost? Is it really your teammate’s fault you’re not an elite player? Eventually, after researching soccer analytics, I began looking into expected goals (xG) models and asked:
Could I develop my own xG model for Rocket League?
Methodology:
Unfortunately, Rocket League doesn’t provide clean, analysis-ready telemetry data. However, players can save replay files of every game and publicly available software can transcribe those files into frame-by-frame positional data. The catch is the resulting outputs don’t have clean labels like touch, cross, pass, or dribble. Instead, we have to define them from scratch. Even shots, although tracked, can be unreliable since the event can fire well after the strike.
With the help of Codex, I built and iterated a parser to turn that raw telemetry into the events needed for analysis. At a high level, the parser identifies when a player sends the ball toward goal with enough speed to reach the goal area within a short time window. It then groups repeated observations and immediate same-team continuations into a single shot. Thus preventing a dribble toward net from becoming five or six shots, or an immediate teammate finish from creating both a miss for the original player and a separate shot for the scorer. These replays come primarily from my own 2v2 games.
Training xG:
Once I had a shot definition, I added features describing the state of the game when each shot was taken. These included things like whether a defender is between the ball and goal, which way the attacking cars are facing, shot distance, and angle. I fed these features and others into XGBoost and validated the model out of sample, with AUC, Log Loss, Brier Score, and a calibration curve.
AUC: Does the model successfully rank dangerous shots above less dangerous shots?
Brier / Log Loss: Are the probabilities themselves any good?
Calibration: When the model says 30%, do roughly 30% actually go in?
How closely do predictions match reality?
Hover or focus a point to compare expected and actual conversion.
Actual conversion Perfect calibration
The dashed line represents a perfectly calibrated model. On out-of-sample data, my xG predictions closely track actual scoring rates, with modest deviations at the lowest and highest probabilities.
Results:
Once I had the model built and calibrated, I turned to visualization. I chose an HTML/JS-based dashboard because it gave me the flexibility to build bespoke visualizations inspired by sports analytics. I again leveraged GPT/Codex to build and iterate the front end while I focused on QA, interpretation, and design.
I started at the shot level, borrowing from the xG maps commonly used in soccer:
Where are chances most dangerous?
Average xG by location · Attacking toward the top
This map might seem obvious in hindsight, but it helped me see how some fundamental ideas from soccer translate directly to Rocket League. Don’t try to get cute and clear out centrally. Most of the time, your best bet is a sideways clear from the center of the goal out to the corners. Maybe blindingly obvious, but sometimes the value of data is helping us make the best choice more consistently.
From there, I could map the xG of an entire game:
The chances behind the score
Me / Player 2 Player 3 / Player 4
Larger circles = higher xG · Filled = goal · Outlined = miss
Our team attacks toward the top; opponents attack toward the bottom.
The map shows chances captured by the xG model, so its goal count may differ from the scoreboard. Some goals, such as own goals or unusual bounces, may fall outside the model’s shot definition.
These visuals helped me understand where shots came from and how dangerous they were. But individual shots only tell part of the story. By aggregating xG across a game, I could start thinking about performance in terms of chance creation and allowance rather than just the scoreboard.
Game Stats:
Match performance
The same game shown above.
PoP (Period over Period)
Comparing performance over time
Values per game
While the game-level view tells us what happened in a single match, aggregating those metrics reveals longer-term patterns. This view lets me compare performance across heaters, coolers, and larger samples. For example, my overall win rate is ~54%, enough to climb the rankings slowly.
Moving Beyond xG
xG describes danger at the moment of a shot however it fails to account for how the shot came to be. Therefore, I realized I wanted something resembling soccer’s threat or dangerous-touch models where actions are scored based off the likelihood of a resulting goal. Given the chaotic nature of Rocket League possession, I decided to simplify by measuring impact directly before shots.
Using the final shot’s xG, I could give credit to players who help create the chance. A solid pass or a strong solo play received credit based on the xG of the resulting shot. On the defensive side, the opposite is true: a key miss or poor touch that leads to a chance the other way gets attributed as debit.
The cleanest way to think of debit is like reviewing blunders in chess. For example, do most debits occur due to overcommits, giveaways to attackers, or missed challenges?
The dashboard contains several views for inspecting both credits and debits. The heat map shows where each event occurred and I can click on it to see a short in-browser replay. I also built both metrics into the PoP and longitudinal performance views to include them in the larger-sample analysis. For a full-fidelity view, the maps also show the timestamp and replay metadata, so I can find the same action in the original in-game replay.
Where do I create or concede danger?
Credit Debit
Larger circles = greater impact · Select a circle to watch the action.
Our team attacks toward the top.
Watch the action
Me / Player 2 in blue · Player 3 / Player 4 in orange
Shadows and vertical trails show height above the field.
So what?
If you’ve gotten this far you might think: neat, cool tool, but does it help you rank up or improve? My honest answer is yes, albeit modestly, and it unfortunately also resulted in some analysis paralysis.
The dashboard helped me establish benchmarks that I can use to adjust my play. For example, if I’m tracking behind xG norms, I may try to attack slightly more the next few games.
The data also show that teammates are relatively consistent over larger samples, suggesting that short-term teammate variance tends to wash out over time.
One of the most frustrating things in Rocket League is losing and being bewildered as to why. Did you get a bad run of teammates? Ironic given the previous paragraph, I’m aware, but it happens. Are you bleeding giveaways and overcommits? Are you playing too defensively? The metrics can provide plausible explanations. If xG is falling, maybe the offense is deteriorating. If debits skyrocket, maybe I’m playing too loose in possession.
The system separates process from outcome. After a losing streak, I don’t need to panic or adjust if the dashboard shows the underlying process is sound. Similarly, during a win streak, I can see whether I’m getting lucky through stronger-than-expected conversion or getting buoyed by teammates, since that happens too!
Limitations:
For someone analytically minded and experiment-driven like myself, the dashboard can result in too much variation in play. Sometimes I’d test one theory, then look at the data as if it could tell me whether I’d solved Rocket League. I experimented with questions like: What happens when I play more aggressively or defensively? What about positioning ahead of or behind my teammate? The possibilities become endless.
As for the core metric, xG can be a bit circular. It measures the thing it’s telling you to do. “Create more xG” as a recommendation to improve is tautological. It’s essentially telling me to score more goals, as if I’m not trying. It doesn’t demonstrate how.
Perhaps the biggest issue is contextual performance. Rocket League uses a Matchmaking Rating (MMR) to match players of similar skill. MMR is adaptive, meaning the competitive environment moves with you. Winning puts you in matches with better players and losing does the opposite. To make it concrete, imagine I’m creating positive expected goal difference (xGD) per game and ranking up. My xGD may decrease because I’m facing better players rather than because my play has fallen off a cliff. Unfortunately, the replay data doesn’t include MMR, and I haven’t found a clean way to integrate it.
Despite these limitations, the project lets me track my performance beyond the scoreboard. It’s an environment where I can test both gameplay and new ways to look at sports-adjacent data. Just don’t tweak your play endlessly hoping to solve the game. If anything, the results point toward something less dramatic: make small adjustments, preserve what works, and adapt as the competition improves.