Explainer
How a Possession Simulator Thinks
The correlation between teammates is never programmed in. It falls out of five players fighting over one finite pile of possessions.
Most projection tools do the same thing: they regress to a summary number — a points average, maybe a variance — and then, if they want two players' outcomes to move together, they staple a hand-tuned correlation matrix on top. That last step is where they quietly go wrong, because a correlation you type in by hand is a correlation you can get backwards.
The possession engine here works the opposite way. It never stores a teammate-correlation matrix at all. Instead it simulates the mechanism: a game is a chain of possessions, and each possession is used by exactly one of the five offensive players on the floor. The on-court five are sampled from real stint minutes with a 6.0 minutes-per-game rotation floor; the ball routes to a user in proportion to his usage raised to the 1.25 power, so primaries get fed more, exactly as they do in life. That player draws a turnover, a foul, or a shot; he scores or misses into an offensive-rebound loop; and then the possession is spent. The other four cannot use it. It is gone.
That single constraint — one finite pile of possessions, one user per trip — is the whole trick. Because a shot my teammate takes is a shot I cannot, our scoring is mechanically anti-correlated. Nobody tuned that. It is a consequence of the sampling.
Here is the receipt that makes it worth caring about. Read the teammate points-to-points correlation off the simulated games and compare it to real box scores:
- New possession sim: rho about -0.10, emergent, nothing imposed.
- Realized box scores: rho about -0.10, what actually happens.
- A prior in-house simulator that did impose a matrix: rho +0.65 — wrong sign and wrong magnitude.
The module src/sim/sgp_from_sim.py measures the emergent value at -0.104. So the old approach did not just miss the number; it got the direction of the relationship inverted, insisting teammates rise and fall together when in fact they trade off. Getting a joint quantity right by construction is the entire reason to simulate the mechanism instead of the summary — a fitted matrix can be fit to the wrong thing, and this one was.
The joint structure is not even a separate object. It is just the block of per-simulation realizations the chain already produced, so pricing any combination of outcomes is a boolean mask over the sims: the fraction of runs where every leg lands. Same-player legs correlate positive; teammate scoring legs correlate negative; the mispricing direction is explicit rather than assumed. Honest scope: the joint-calibration harness grades the sim's joint against the realized joint at roughly 50/50 lines to isolate the correlation, and it beats an independence model where correlation matters — but no same-game-parlay dollar edge is claimed, because there is no real parlay-price capture on disk to grade against.
The engine also grades itself. A cross-sport heatmap reshapes each sim's own walk-forward validation into per-game-state cells and ranks the worst ones. The worst named NBA cell is the third quarter at a near-even margin, and rather than paper over it, the builder cross-references the knowledge ledger and flags it as an un-probed mechanism gap. Tennis and soccer, which only simulate whole matches, get one honest whole-match cell with a coverage-gap note instead of fabricated buckets.
That is the hire signal, not the simulator itself: a generative model whose emergent joint behavior lands on reality by construction, which then decomposes its forecast quality by game state, names the cells it handles worst, and refuses to convert a validated structure into a betting claim it did not earn.
Sources
docs/evidence/possession-simulator.mdwebapp/public/data/ask/system-honesty.jsonNext explainer
What Calibration Means, and Why We Grade Ourselves Against the Market →