The range every database shows you
Big blind facing a button open, single-raised pots · 9.93M decisions, 12.0% of them with visible cards
- A hand history is a biased camera, not a missing one. Cards are recorded at showdown, and reaching showdown depends on the action you took — so the sample is censored by the very thing you want to measure.
- The fix is to divide, not to filter. Every hand we saw stands in for 1/p hands, where p is the measured probability we would have seen it at all. Folds get no divisor — they are recovered as the gap between arrivals and plays.
- It lands within 1.7 points. Across six spots and 39.9M decisions, the reconstructed fold/call/raise is never more than 1.7 points from the true one. The raw showdown read misses by up to 87.
- One divisor per action is not enough. Inside a single raise bucket, showdown propensity runs from 6.0% to 21.4%. A flat divisor inflates the premiums 1.6× and shrinks the bluffs to 0.46×. The second-generation divisor corrects the shape and keeps the level.
- We show you the sample size of the model, not of the database. A cell built from three showdowns says three, however many million hands sit behind it.
What a hand history actually records
Open any hand you have ever downloaded. Your own two cards are there. Everyone else’s are there only if the hand went to showdown — that is what the format is. So a database of ninety-five million hands does not contain ninety-five million observed ranges. It contains a sample, and the sample is chosen by the outcome.
That would be tolerable if the censoring were random. It is the opposite of random. In our headline spot, a big blind who calls ends up with cards on the record 29.4% of the time; one who 3-bets, 16.6%; one who folds, 0.002%. The probability of being observed is a direct function of the action being studied. Counting up the hands you can see does not give you a noisy range — it gives you a confidently wrong one, and it will not improve with more data.
The fix is a divisor, not a filter
If a hand had probability p of ending up visible, then each one you actually see stands in for 1/p that happened. That is the whole idea — inverse-probability weighting, the same correction survey statisticians use for non-response. In our vocabulary:
de-biased count(combo, action) = observed showdown count ÷ WTSD(action), where WTSD is P(the hand reaches showdown | somebody took this action here), measured on the same population, in the same spot, over the same filters — with the visibility condition dropped so that both the seen and the unseen contribute to the ratio.
Folds get no divisor at all, and that is deliberate: at 2 visible folds in 100,000, any ratio you built from them would be noise wearing a decimal point. Instead, folds are what is left over. We count how often the population arrives at the decision, subtract everything we reconstructed them playing, and the remainder is the fold mass. Nothing is fitted, and nothing is assumed about how they fold.
Six spots, checked against the answer
This is testable, because the aggregate we are reconstructing is something we can also simply count. Every decision is in the database with its action; only the cards are missing. So we can run the model on the censored sample and then check it against the uncensored total it never looked at.
Reconstructed versus real, six spots
Fold / call / raise, in percent · NL25–NL1000 6-max cash, all populations · 39.9M decisions
| Spot | Decisions | Cards seen | Showdown-only | De-biased | Truth | Max err |
|---|---|---|---|---|---|---|
| BB vs BTN open | 9.93M | 12.0% | 0.0 / 86.9 / 13.1 | 53.8 / 36.2 / 9.9 | 55.0 / 35.5 / 9.5 | 1.2 |
| BB vs CO open | 7.93M | 11.5% | 0.0 / 87.4 / 12.6 | 58.4 / 34.5 / 7.0 | 59.5 / 33.8 / 6.8 | 1.1 |
| SB vs BTN open | 12.91M | 5.4% | 0.8 / 63.2 / 36.0 | 77.5 / 10.6 / 11.9 | 76.9 / 11.4 / 11.7 | 0.8 |
| BB vs SB open | 7.39M | 14.0% | 0.0 / 84.3 / 15.7 | 46.4 / 40.3 / 13.3 | 48.1 / 39.3 / 12.6 | 1.7 |
| BTN vs BB 3-bet | 1.12M | 16.3% | 1.1 / 77.1 / 21.8 | 56.3 / 35.5 / 8.2 | 57.3 / 34.6 / 8.1 | 1.0 |
| CO vs BB 3-bet | 0.60M | 20.9% | 0.9 / 77.0 / 22.2 | 47.5 / 43.5 / 9.0 | 48.6 / 42.5 / 8.9 | 1.1 |
Note the third row. In SB vs BTN only 5.4% of decisions leave a card behind, and the raw read claims the small blind folds 0.8% of the time. The reconstruction says 77.5; the truth is 76.9. Thin visibility is not the problem — unmodelled visibility is.
Where the range comes from three streets deep
The divisor gives us how often the population played each hand. To turn that into frequencies we also need the denominator: how often they arrived at the decision holding it. At a first decision that is easy — every combo is dealt equally, so arrivals are uniform across all 1,326. Deeper it is not: facing a 4-bet, you are only there because you 3-bet, and you only 3-bet some hands.
We used to borrow those arrival weights from a solved reference, and it produced a specific, ugly bug: a hand the reference never 3-bets got zero arrivals, so a population that does 3-bet it — and then folds to the 4-bet — vanished from the grid entirely. The off-script part of the range, which is exactly the part worth studying, was the part we could not draw.
So the arrival weights now come from the population itself, recursively: your reach at facing a 4-bet is the de-biased count of how often the population 3-bet that hand at the previous node, which is itself reconstructed the same way, down to the uniform base case. No solver appears anywhere in that chain. K4s now shows up facing a 4-bet at a 94–100% fold — which is the honest answer, and one no showdown-filtered database can produce, because that hand never sees a showdown.
One divisor per action was not enough
Here is where the first version was wrong, and it is worth being precise about it. Using a single WTSD per action assumes every hand in a raising range reaches showdown at the same rate. Aces and a bluff do not. We can measure the spread directly on a solved population where every hand is visible by construction:
Showdown propensity inside one action bucket
P(reaches showdown) for hands the big blind 3-bets over a 2.5x button open · 43 classes, 20,747 decisions · bucket mean 13.1%
The second-generation divisor splits the problem in two. The level — is this population loose or tight, does this pool go to showdown a lot — stays measured on the population, where it belongs and where version one already had it right. The shape — which hands inside the bucket are showdown-prone — is borrowed from the solved reference as a ratio to its own bucket mean, clipped to [0.25, 4] so a thin sample on a rare hand cannot swing a cell. A hand the reference never plays falls back to a ratio of 1, which is exactly version one. Nothing else changes: the arrival chain, the fold subtraction, and the aggregate calibration are identical, and the mode is carried through the whole recursion so a range and the range that feeds it are never built two different ways.
The parts we refuse to fake
A model that fills 169 cells from a censored sample can very easily become a machine for laundering three observations into a confident-looking grid. The guards are as much of the design as the estimator:
- The sample count you see is the model’s, not the database’s. A grid built on 3 visible showdowns says 3, even when 2,930 hands were played at that node. The amplification factor is shown next to it.
- Cells are marked, not dropped. Below five raw showdown observations a cell desaturates instead of disappearing. There is no minimum-sample filter anywhere: a thin cell is information about the pool, and hiding it is a lie of omission.
- No fabricated defaults. If the divisor cannot be measured, the mass is dropped and said so. If a node has zero population rows, the grid comes back empty rather than inheriting its parent’s hands — a real bug we shipped once, which painted 106,193 hands across 159 cells out of nothing.
- The one place we override the data is bounded and declared. Hands the reference plays with probability above 0.999 or below 0.001 are snapped to pure — AA is not folding, and no amount of showdown noise should suggest it is. Everything strictly between those bounds is the population’s own number, untouched.
- The grid can be read two ways. Normalized answers “given that you are here with this hand, how is it played?”. Weighted scales every cell by how often the population actually arrives holding it — so a range visibly narrows as the 3-bets and 4-bets go in, and a rare bluff reads as a sliver rather than as a full-height cell.
Why not just do something simpler
There are two simpler things to do, and both are done. The first is to filter to showdown hands and present what is left as a range. That is the top bar of the first figure: it is not a rough answer, it is a wrong one, off by up to 87 points, and the error does not shrink with sample size because it is bias, not variance. The second is to skip the population entirely and show a solver’s range instead. That is a fine thing to show — we show it too, on the other half of the same screen — but it answers a different question. It is what a perfect opponent would do, not what this pool does, and the entire value of population data is the gap between the two.
What we wanted was the third thing: every hand class, at every node, including the ones that never reach showdown, expressed as the population’s own frequencies, with the sample size of the inference visible on the cell. The measurement above is the argument that it works. The same population data drives the rest of the platform — see also Board Textures, Measured, where the same 1.24 billion actions draw the flop map.
Data table — visibility and showdown rate by action, all six spots
| Spot | Action | Decisions | Reached showdown | Cards visible |
|---|---|---|---|---|
| BB vs BTN open | Fold | 5,463,235 | 0.26% | 0.00% |
| BB vs BTN open | Call | 3,524,122 | 28.80% | 29.42% |
| BB vs BTN open | Raise | 943,363 | 15.84% | 16.55% |
| BB vs CO open | Fold | 4,718,240 | 0.11% | 0.00% |
| BB vs CO open | Call | 2,678,373 | 29.05% | 29.72% |
| BB vs CO open | Raise | 537,890 | 20.57% | 21.37% |
| SB vs BTN open | Fold | 9,931,316 | 11.87% | 0.05% |
| SB vs BTN open | Call | 1,473,459 | 32.02% | 29.78% |
| SB vs BTN open | Raise | 1,508,399 | 16.23% | 16.58% |
| BB vs SB open | Fold | 3,552,067 | 0.26% | 0.00% |
| BB vs SB open | Call | 2,905,740 | 29.33% | 30.05% |
| BB vs SB open | Raise | 934,014 | 16.55% | 17.43% |
| BTN vs BB 3-bet | Fold | 641,186 | 0.00% | 0.32% |
| BTN vs BB 3-bet | Call | 387,129 | 35.35% | 36.25% |
| BTN vs BB 3-bet | Raise | 90,507 | 42.94% | 43.77% |
| CO vs BB 3-bet | Fold | 293,338 | 0.00% | 0.37% |
| CO vs BB 3-bet | Call | 256,027 | 36.97% | 37.88% |
| CO vs BB 3-bet | Raise | 53,664 | 51.44% | 52.09% |