Every table has a player who swears the dice are out to get them. Three years of logged rolls let us actually check.
The short version: the dice are fair to three decimal places. The long version is more interesting.
It is the most common complaint at any D&D table, and it always feels true. Someone fumbles a critical save, drops to the floor, and declares the dice cursed. The table nods. A legend is born.
But a feeling is not evidence. Across three active campaigns, every digital roll has been logged to a database since 2023 — over five thousand of them. That turns an article of faith into a testable hypothesis: are the dice actually unfair to anyone, or does it just feel that way?
Every roll made on D&D Beyond is captured by its game-log API, pulled with a lightweight sync script, and upserted into a Postgres database on Supabase. One in-game attack often becomes two rows — a to-hit and a damage roll — linked by a shared roll ID. The raw die faces are preserved in a JSON column, which is what makes a fairness audit possible at all.
| Campaign | Span | Total rolls |
|---|---|---|
| Pacts & Power | Jan 2023 – Jun 2026 | 3,108 |
| Ashfall Britannia | Nov 2025 – Jun 2026 | 1,129 |
| Sky Is The Limit | Aug 2025 – Jun 2026 | 1,108 |
A roll archive is not a tidy dataset. Three decisions did most of the work, and each one matters for the result.
The fairness question is about a single fair die. So the analysis keeps only single-d20 rolls used for attacks, checks, saves, and raw d20s — the rolls that should follow a clean uniform distribution.
Rolling two d20s and keeping the higher (advantage) or lower (disadvantage) is not a uniform distribution. Advantage skews high; disadvantage skews low. Folding those rolls into the base rate would manufacture a bias that the die itself never had. Excluding them is the difference between measuring the dice and measuring the rules.
A handful of legacy rows stored the die values as a double-encoded JSON string rather than an array; DM-controlled monsters appear in the log as their own “characters”; and a few early rows have no recorded values at all. Each was filtered explicitly rather than silently averaged over.
WITH d20 AS ( SELECT (individual_values->>0)::int AS face FROM ddb_rolls WHERE dice_notation LIKE '1d20%' -- single die only AND jsonb_typeof(individual_values) = 'array' -- skip legacy/null AND jsonb_array_length(individual_values) = 1 -- no adv/disadv AND roll_type IN ('to hit','check','save','roll') ) SELECT face, COUNT(*) FROM d20 GROUP BY face ORDER BY face;
The full extraction query. After filtering, 3,246 clean single-d20 rolls remained for analysis.
If a d20 is fair, all twenty faces should land roughly equally often — about 162 times each across 3,246 rolls. Here is what actually happened.
Every bar hugs the expected line. There is no slope, no dead zone, no favored face.
Chi-square goodness-of-fit: χ² = 6.69 on 19 degrees of freedom, p = 0.996. A value that high means the observed spread is indistinguishable from pure chance — we cannot reject the hypothesis that the die is fair.
Mean face: 10.566 (a fair d20 expects 10.5). Standard deviation: 5.767, against a theoretical 5.766 — a match to the third decimal.
The headline crits: natural 20s landed 4.90% of the time, natural 1s 4.96% — both within a whisker of the expected 5%.
So at the level of the whole table, there is no curse and no blessing. The random number generator is doing its job. Which raises the obvious objection…
Fair overall doesn’t mean fair for everyone. So we split the rolls by character — every player and DM-run creature with at least 30 single-d20 rolls — and looked at how often each one fumbled a natural 1.
Two characters clear a naive significance test — one suspiciously unlucky, one suspiciously lucky. Hold that thought.
Run a binomial test on each character and two names cross the usual p < 0.05 line:
| Character | Rolls | Nat 1s | Rate | p-value | Verdict |
|---|---|---|---|---|---|
| Sanis Reylana | 297 | 26 | 8.8% | 0.007 | looks cursed |
| Ogre | 178 | 2 | 1.1% | 0.014 | looks blessed |
| Everyone else (17) | — | — | — | >0.10 | unremarkable |
Case closed? Not quite. There’s a trap in testing nineteen characters two ways each — that’s 38 separate significance tests. At a 5% threshold, you expect about 1.9 of them to flag by pure luck, even if nothing is wrong. We found exactly two. That is the number randomness predicts.
Adjust the threshold for 38 tests (Bonferroni: p < 0.0013) and neither result survives. Sanis’ bad luck and the Ogre’s good luck are exactly the kind of noise you get for free when you look at enough dice. Nobody is statistically cursed.
This is the quiet lesson hiding in a joke dataset: search hard enough through random data and you will always find something that looks significant. The discipline is knowing it isn’t.
The dice are fair. The bias is human.
We remember the natural 1 that dropped us at 1 HP and forget the forty quiet sevens — and that is the curse worth believing in.