How this works
Pick a die size (d4, d6, d8, d10, d12, d20, or d100) and how many to roll, then tap roll. Each die is rolled independently using `crypto.getRandomValues` with rejection sampling to guarantee a uniform distribution — so a d20 result of 17 is exactly as likely as a result of 1, every roll. The widget shows each individual die result so you can verify the rolls (handy for D&D where the GM might ask "what did you roll on the d20 specifically?"), plus the sum, which is what most game systems actually use. Up to 10 dice at once covers most common cases (3d6 stat rolls, 4d6 drop-lowest character generation, 2d20 advantage/disadvantage in 5e, 1d100 percentile checks, and so on).
The formula
N is the number of dice (1–10 in the widget). S is the number of sides per die (one of 4, 6, 8, 10, 12, 20, 100). Rejection sampling matters because 2^32 doesn't divide evenly into S for most die sizes — using `byte mod S` directly would give some results a slightly higher probability than others. Throwing away bytes in the small "remainder" zone above the largest multiple of S keeps the distribution exactly uniform. The probability of rejection is at most S/2^32 per attempt, so in practice the wait time is invisible.
Example calculation
- D&D ability score: roll 3d6 → results [4, 6, 2] → total = 12.
- 5e advantage: roll 2d20 → [11, 18] → take the higher = 18.
- Percentile check: 1d100 → 73. Used for "did the trap go off?" (target ≤ 80 = trap fires).
Frequently asked questions
Why does the d10 give 1–10 and not 0–9?
Most game systems treat the d10 as 1–10. The 0–9 reading is specific to percentile dice — when you roll two d10s where one is the tens digit and one is the units, a 0 face on each represents 0, and 00 + 0 reads as 100. We use the 1–10 convention because it works for the broad case (2d10 stats, damage rolls in d10-based systems, etc.) and for percentile rolls we have a dedicated d100 option that returns 1–100 directly so you don't need to combine two dice.
Can I roll multiple different dice at once (like 1d20 + 4d6)?
Not in one tap with this widget — it rolls one die type at a time. For 1d20 + 4d6 you'd roll the d20, jot the result, then switch the type to d6 and the count to 4 and roll again. A future "complex roll" mode could parse standard dice notation ("1d20+4d6+3") in one go, but right now the simpler UI keeps the common cases (single die type, fixed count, optional sum) easy to grasp at a glance.
How is "fair" different from Math.random()?
For most game purposes Math.random() is fair enough — modern browser implementations are good. Two reasons we use crypto.getRandomValues anyway: (1) it removes any "is the RNG biased?" question users might have, and (2) it lets us guarantee unpredictability for use cases where someone might care (e.g., picking lottery numbers, drawing tournament seeds). The cost is essentially zero — we're reading a few bytes per roll.
Does the widget remember rolls between sessions?
No. Each page load starts with no rolls, and refreshing or navigating away clears the result. This is intentional — the widget is for quick, throwaway rolls; if you need a persistent log (campaign tracker, statistical analysis) a paper notebook or a dedicated VTT does it better. Privacy bonus: nothing is sent to a server, so no one can see what you rolled.