Projection Model
How the projection model is built, and what has been tried
The Models page describes what is running. This page is the research log behind it: what worked, what did not, what is known to be broken, and what is still untested. The failures are listed as prominently as the successes — eight of the nine team-context ideas tested here came back null, and keeping that visible is what stops them being rebuilt.
One model, four switches
What used to be three separate projection models were never three models. They were one pipeline with four settings, duplicated three times in code. They are now a single configurable path — the settings below are the only thing that differs between them.
| Setting | What it controls | Options |
|---|---|---|
| Recency window | Which view of recent form feeds the rate step. | Standard 10-game · per-category half-lives · none (ignore the season in progress entirely, for a preseason read) |
| Absence scope | Which teammate absences shift a player’s production. | None · any current absence · only absences lasting the rest of the season |
| Age curve | Whether an age/development term is applied. | On / off |
| Schedule adjustment | Opponent strength and rest. | On / off — currently inert, no forward schedule data exists |
Combining them was deliberately a restructure, not a redesign: the merged pipeline was required to reproduce all three previous models exactly, and does — identical to the last decimal across 18,112 projections each. An accuracy check would have let a mis-copied setting pass as noise; an equality check cannot. Adding a new part is now a setting rather than a fourth copy of the code.
What a projection is made of
Three questions, asked in order. Each is a separate model, and keeping them apart is deliberate — a player who is excellent but fragile, one who is durable but buried on the bench, and one who gets the ball constantly but does little with it are three different problems, and a single blended number hides which one you are looking at.
| Factor | The question | What it covers | Status |
|---|---|---|---|
| Availability | Can he play? | How many of the games in the window he is actually available for. Depends on his own history alone — nothing about his team. | Validated |
| Opportunity | How much does he get to do? | Minutes on court, and how many chances those minutes carry — shots, trips to the line, rebounding chances, and since 2026-08-05 minutes actually holding the ball. This is where teammates matter: a team has only so many minutes, so many shots and so much time with the ball, so opportunity is shared and has to be settled for the whole roster at once. | Validated |
| Ability | What does he do with it? | His conversion — points per scoring chance, rebounds per rebounding chance, and so on. A property of the player, largely independent of how much opportunity he is given. | Validated |
The projection is these three multiplied together: how often he plays, times how many chances he gets, times what he does with them. That separation is what makes the parts individually testable — the opportunity blend, for instance, improves the middle factor and leaves the other two untouched, so its effect can be measured in isolation.
The order matters, but it is shallower than it looks. Availability runs first and depends on nothing else — and running it across the whole roster is what tells the next step who is available, so there is no separate teammate model. Opportunity comes next, and is the only step where players are coupled to each other, because minutes and shots are shared. Ability is independent of both and is computed alongside them rather than after. That independence is measured, not assumed — every category’s season total sits within a narrow band around the minutes total, which is what a cleanly separable ability layer looks like.
A team plays exactly 240 player-minutes per game. A roster’s per-game averages sum to far more than that, because nobody plays every game and different players fill the same slots on different nights. Weighted by expected availability, teams with a fully covered roster now land at 99.2% of that budget, and every category sits within a narrow band around the minutes figure — which is the check that the three factors are consistent with each other.
What works
Shipped, live, and validated against a real baseline on data held out from fitting.
| Mechanism | Status | What it does | Measured result |
|---|---|---|---|
| Games played (availability) | Live | How many of the horizon’s games a player actually suits up for. | 43% more accurate than assuming everyone plays every game, over a full season. Over short windows nothing beats that assumption, so the model deliberately does not try — about 87% of rostered players really do play all of their next five games. |
| Team-crowding minutes | Live | When a roster collectively expects more minutes than exist, takes the excess back down the depth chart. | 5.5% better on over-committed rosters and 10.6% better for players who changed teams, across 2,934 player-seasons. Held in 7 of 8 seasons, with no part of the rotation made worse. |
| Opportunity blend | Live | Shrinks a player’s pre-season opportunity estimate toward his 3-year baseline instead of copying last season outright. | 6 of 7 categories better on the rate itself; 4 of 4 publishable categories better end-to-end, but only by 0.33% once multiplied through minutes and efficiency. |
| Shot-attempt opportunity (FGA) | Live | Projects how many shots a player gets, from who else on the roster competes for them. Now published as its own number, not buried inside the FG% record. | 4.25% better than assuming last season repeats, winning all 20 held-out splits. It is worth about three quarters of everything the points projection has gained. Validated by rebuilding the same term from rebounding — a skill nobody competes with a shooter for — which cannot beat the baseline at all. |
| Free-throw-attempt opportunity (FTA) | Live | Projects trips to the line from foul-drawing history, how often a player attacks the rim, and his projected shots. | Built on the finding that free throws are crowded by teammates who DRAW FOULS, not by teammates who shoot — those are different rosters. The rebound-built placebo is worse than having no crowding term at all, which is the cleanest such result in the model. Correct on 20 of 28 players across three named roster shake-ups. |
| Rest-of-season projections | Live | The same model run to the end of the season — next-N-games where N is the team’s remaining games. | New. Replaces a placeholder where the site showed next-10-games numbers under a rest-of-season label. |
| Season-ending absence exception | Live | When a teammate is out for the season (not just a few weeks), his minutes and production genuinely transfer. | Reuses the already-validated teammate-absence numbers. Injury records do not go back far enough to test the exception itself; verified against a real current case. |
What did not work
Tested and deliberately not shipped. Three of ten team-context mechanisms have survived a properly controlled test — the ratio is the most useful thing on this page. Several of these looked strong before the control was applied: the pecking-order rank fixed a famous case, and the arrival/departure split produced textbook opposite signs at high significance. Both were measuring something else.
| Idea | The hypothesis | Why it was rejected |
|---|---|---|
| Proportional roster rescaling | Shrink every player on a crowded roster by the same percentage. | Contradicted by the data. The back of a rotation loses roughly five times as many minutes as the stars do, so an even cut is the wrong shape entirely. |
| Short-horizon availability | Predict what fraction of the next 5–15 games a player appears in. | Loses badly to simply assuming he plays. The outcome is close to all-or-nothing, so the most likely answer is "all of them" and no fractional estimate can beat it. |
| Age as a durability signal | Older players miss more games. | Essentially no relationship (correlation −0.014), and non-monotonic — 34-year-olds looked worse than 36-year-olds, because fragile veterans stop being rostered. Made predictions worse even when fitted directly on the data it was tested on. |
| RAPM rebounding pilot | Lineup-level plus/minus to explain rebounding. | No usable signal. |
| Roster continuity | Teams returning more of last year’s roster behave more predictably. | No usable signal. |
| Team-change-aware blending | Players who changed teams should trust last season less than players who stayed. | Splitting the blend by whether a player moved was worse than one setting for everyone — it hurt assists, turnovers and made 3s. |
| Reacting to current injuries | When a teammate is out, bump the remaining players’ projected minutes. | The effect is real but not predictable. When a 30-minute starter actually sits, his minutes do redistribute and they add up — teammates gain about 16 minutes and the bench about 15. But half of all absences last exactly one game and 64% last two or fewer, so applying the bump across the next 5–15 games assumes an absence that has usually already ended. Measured over 2,632 real teammate-out situations, the best weight to put on the adjustment is zero: it adds about 5 minutes where the true average change is −0.04. The fix is not to shrink it but to gate it on absences known to last, which is what the season-ending exception already does. |
| Pecking order as a ranking term | Rank players on a star composite (volume, playmaking, efficiency) and use the change in rank. | Falsified by ranking the same rosters on five RANDOM keys, which scored identically. A rank term mostly measures how much the roster changed, not where a player sits in it — the sort key was decorative. It fixed one famous case and went 16/28 on the full rosters, a coin flip. |
| Roster churn and arrivals/departures | Measure how much shooting arrived and departed and split the two. | Both looked strong — the arrival and departure terms even came out with correctly opposite signs and high significance. Both were minutes turnover in disguise: the departure term correlates 0.986 with the raw minutes that left, and stripping the minutes out drops it below doing nothing. Adding a minutes-shaped term to a per-minute rate charges the same roster change twice. |
| A shooting ceiling that fills up | A player caps out, so freed shots cascade to whoever has room. | No wall exists — the top of the distribution is if anything fatter than a normal curve. Absorption is an inverted U in prior volume: the mid-volume players absorb most, and the LOWEST-volume players absorb least despite having the most room. So it is standing AND room, not room alone. Neither a cap-and-cascade allocation nor the inverted U as a fitted term beat the simpler version. |
| Legacy DARKO-style rating | A single overall value rating plus per-category predictions. | Worse than "just use last season" on 20 of 21 tests, and occasionally produced impossible values including a negative shooting percentage. Dormant. |
Known problems
Measured, documented in the code, and not hidden. Some are genuine defects, some are accepted limits, and one — the scoring crowding that had never actually run — was found only because the numbers were re-derived rather than trusted.
| Area | Status | Detail |
|---|---|---|
| Teammate-absence attribution | Known broken | When a player misses a game his minutes should reappear elsewhere on the roster. Only 62% of them do — and some cases imply a team collectively played fewer minutes, which is impossible. Cause: each teammate pair is measured in isolation, with no account of several players being out at once, and anyone who only plays when the star sits is excluded outright. Used as a weak directional hint, never as a minutes budget. |
| Rotation-size ceiling | No-op | Intended to cap a player’s minutes based on how deep his team’s rotation is. In practice the cap lands between 38.9 and 40 minutes while the highest projection in the league is 38.0, so it has never once bound. Documented rather than removed — the underlying statistic is useful elsewhere. |
| Crowding under-corrects | Accepted | About 11% of players with a real role leave the league each year. Real teams absorb part of a squeeze by shedding players, which a model that only adjusts players it can see cannot capture. The adjustment is therefore a floor on the true effect, deliberately not scaled up. |
| Availability assumes he plays at all | Known broken | The games-played model answers "how many games given he plays at all", not "how many games". Roughly half of chronically unavailable players do not appear the following season, and every rate the model is calibrated on was measured only on the ones who did — so it over-projects fragile players by about 21 games and healthy ones by about 8. Note that most of those who vanish left the league entirely rather than being injured all year, so the unconditional figure is not the right answer for a contracted starter either; the fix is to separate "stays rostered" from "healthy enough to appear", and neither has been measured. |
| Efficiency is smoothed by the wrong constant | Partly fixed | Every category smooths a player’s efficiency toward a prior by an amount meant to reflect how noisy that efficiency is. Those amounts were measured on how fast the CATEGORY settles rather than the EFFICIENCY inside it. Measured properly, on disjoint halves of each season, most categories land within about 2.5x — the pipeline’s own units conversion absorbs much of the error. Assists was the worst at 11.3x under-smoothed, and was rebuilt on 2026-08-05: it now smooths by 340 shot-creating passes rather than five games, and toward the league rather than toward the player’s own career average, which turned out to be worse than a flat league number because it smooths a noisy estimate toward another noisy estimate of the same player. Made threes (25x under-smoothed) and FG% (3.8x over-smoothed) are still open, both because their constants are never rescaled. |
| Settling fast is not the same as staying put | Open | The method above measures how quickly a rate settles WITHIN one season. That is not the same question as how well it predicts the NEXT season, and for one quantity the two answers differ by a factor of forty. Creation skill is measured almost perfectly inside a season yet only about half of it carries to the following year — smoothing it by the within-season figure made the projection measurably worse. Caught while building that model; the assists efficiency constant above was measured the same way and has not been re-checked against the same test. |
| Scoring crowding was silently doing nothing | Fixed | The adjustment that lowers a scorer’s projection when his team signs another scorer had never actually run. A single unresolvable player produced a "not a number" value, which spread across his team and then the whole league; because that is not the same as a missing value, every safety check passed and the model quietly returned the uncrowded answer for everybody. Found and fixed on 2026-08-04. It had been recorded as working for months. |
| Points total drifts up 1.5% | Open | The rebuilt scoring model raises the league-wide points total by about 1.5%. A model that reallocates production between players should leave the total roughly where it was, so this is an unexplained level shift rather than redistribution. Published deliberately, with the cause still being traced — the leading suspect is that free-throw percentages are lopsided, so smoothing lifts poor shooters more than it lowers good ones. |
| Who absorbs freed shots is unpredictable | Accepted | When a high-volume shooter arrives, the model correctly moves every teammate down — on one such roster it moved all eleven the right way and cut the error by 28%. When one LEAVES, someone takes those shots and nothing tested identifies who: ten different formulations have failed. The clearest case is a player who went from 13.5 to 20.3 shots per 36 minutes on a roster change, whom every version moves by essentially nothing. Crowding is forecastable at roughly twice the strength of freeing, so a projection for a player whose team lost a shooter deserves less trust than one whose team gained one. |
| Roster coverage | Accepted | Projections cover about 17 players per team; real teams use around 22 across a season. Season totals land at roughly 87% of reality as a result — and teams whose roster is fully covered reach 99.2%, which is what confirms this is coverage rather than a modelling error. |
Still to test
Open questions, and whether they can be answered today or need the season to start.
| Question | Status | What it would take |
|---|---|---|
| Season-ending exception | Testable now | Whether the exception for absences that last the rest of the season earns its place. Same backtest approach that settled event-awareness, narrowed to long absences — which is the one case where the mechanism should work. |
| Which efficiency estimate to use | Contradictory | Estimating a player’s efficiency from his own game history, weighted so recent games count more, is 8-9% better at predicting that efficiency than using last season alone. But it is 0.09% WORSE at predicting points. Two explanations were chased and eliminated. The remaining hypothesis is that when opportunity and efficiency both come from the same season their errors partly cancel. Unresolved, and worth 0.09%. |
| Blocks decomposition | Never verified | Every other category’s opportunity and efficiency have been checked to multiply back exactly to the stat. Blocks is the one that never has, because the contested-shot data behind it is too sparse at game level to test. |
| Opponent strength | Not started | Coded but inert. Needs a real forward schedule with home/away, which this dataset does not yet carry. |
| Everything, in-season | Waiting | No games of the current season have been played, so every projection is currently a cold-start extrapolation from history. The first genuine out-of-sample read comes once about 15 games are played. |
How things get accepted
Every mechanism is measured against the simplest honest alternative — usually “assume nothing changes from last season” — on seasons held out from fitting. If it does not beat that baseline it does not ship, per category and per time horizon. A change validated on one ingredient is not assumed to improve the final projection: the opportunity blend beat its baseline by about 5% on the raw rate but only 0.33% once multiplied through into a published number, and that distinction is why several ideas that looked promising are in the rejected list above.
