Rectrix Caedere Rectrix Caedere
Field Study No. 01 · Roll Data

Do the Dice Hate You?

Every table has a player who swears the dice are out to get them. Three years of logged rolls let us actually check.

3,246
Rolls audited
p = 0.996
χ² fairness test
4.90%
Nat 20 (exp. 5%)
4.96%
Nat 1 (exp. 5%)

The short version: the dice are fair to three decimal places. The long version is more interesting.

01 · The Question

“I never roll above a 7.”

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?

02 · The Dataset

Where the numbers come from

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.

D&D Beyond
Game-log API, per campaign
Sync Script
Token auth, paginate, de-dupe
Supabase
Postgres, one row per roll
SQL + Python
Extract, test, visualize
CampaignSpanTotal rolls
Pacts & PowerJan 2023 – Jun 20263,108
Ashfall BritanniaNov 2025 – Jun 20261,129
Sky Is The LimitAug 2025 – Jun 20261,108
03 · Methodology

Cleaning a messy real-world log

A roll archive is not a tidy dataset. Three decisions did most of the work, and each one matters for the result.

1 · Only true 1d20 rolls count

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.

2 · Advantage and disadvantage are excluded — deliberately

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.

3 · Known data quirks, handled

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.

04 · The Verdict

The dice are fair. Boringly fair.

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.

d20 Face Distribution
Observed count per face vs. the expected value for a fair die (162.3)
Observed rolls Natural 20 Natural 1 Expected (fair die)

Every bar hugs the expected line. There is no slope, no dead zone, no favored face.

The numbers behind the flat line

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…

05 · The Rebuttal

“Fine — but I’m still cursed.”

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.

Natural 1 Rate by Character
Share of rolls that came up a 1, sorted high to low. Expected: 5%.

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:

CharacterRollsNat 1sRatep-valueVerdict
Sanis Reylana297268.8%0.007looks cursed
Ogre17821.1%0.014looks blessed
Everyone else (17)>0.10unremarkable

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.

The multiple-comparisons correction

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.

06 · Limitations

What this can’t see

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.