Decision Wheel Fairness Audit: Equal Slices and One Uniform Draw

Spin a decision wheel a few times, one option keeps winning, and everyone accuses the wheel of cheating. So we audited our own decision wheel the way you would audit a coin, by reading the code that produces each result. The short version is that the geometry is fair by construction, the animation cannot bias it, and the remaining ways to cheat all involve the option list.

How one spin works, step by step

The spin pipeline in the component runs in a fixed order.

1. The spin button checks two guards. It does nothing while a spin is running, and it requires at least 2 options on the list.

2. The code draws a random offset, a uniform value between 0 and 2 pi radians, which is any position on the circle with equal likelihood.

3. It draws a spin count, 5 full turns plus a random fraction of 5 more, so every spin travels between 5 and 10 full rotations.

4. It adds the current rest angle, the random turns, and the random offset into a single target angle.

5. The wheel animates toward that target over exactly 4000 milliseconds, decelerating on a quartic ease out curve.

6. When the animation ends, the winner is read back from the final angle using geometry, by computing which slice covers the fixed pointer position.

That last step matters. The winner is not chosen first and animated second. The angle is chosen, and the label under the pointer falls out of the math.

The core of the readback, exactly as shipped:


const slice = (2 * Math.PI) / n;
const norm = ((current % (Math.PI * 2)) + Math.PI * 2) % (Math.PI * 2);
const offset = (Math.PI / 2 - norm + Math.PI * 2) % (Math.PI * 2);
const idx = Math.floor(offset / slice) % n;

Why each option wins with probability one in n

The wheel divides the full circle into n equal slices, each spanning exactly 2 pi over n radians. The pointer is a fixed point on the rim. A rotation lands uniformly at random because the only random input, the offset, is uniform over the full circle.

Two details close the argument.

First, the easing curve evaluated at the end equals exactly 1, so the final drawn angle equals the computed target with no rounding gap between what you see and what the code reads.

Second, the previous rest angle and the whole number of turns both cancel out modulo 2 pi. Whatever angle the wheel stopped at last time, the new uniform offset washes it out. History cannot influence the next result.

With n options, every option holds exactly 1 slice, so each spin gives every option a probability of exactly 1 in n. With the default 5 options, that is 1 in 5 per spin.

Where real bias can still enter

The math is fair. The list is where things tilt.

Duplicate entries are the weighting mechanism. Add Pizza twice and leave 3 other options on the wheel, and Pizza now holds 2 of 6 slices, a probability of 2 in 6 instead of 1 in 5. Nothing stops you, and the wheel stays perfectly fair to slices while becoming unfair to choices. If you want weights, this is the honest way to build them. If you do not, check the list for duplicates.

The randomness source is Math.random, the browser general purpose generator. It is uniform for practical purposes, but it is not cryptographic. For lunch decisions it is beyond adequate. For a prize draw where someone could have an incentive to predict or influence results, use a draw tool built on a cryptographic source.

Colors are positional, not owned. Slices are painted from a fixed palette of 10 colors by list position. Delete an item and every option below it shifts to the previous color. Players who learned that their answer is the red one can be looking at a different red option after any edit.

Run the audit yourself

1. Load the wheel with 5 distinct options and remove any duplicates.

2. Spin 50 times, recording each winner. The counter in the options panel caps the list at 20 entries, so a tally sheet on paper is faster than expanding the list.

3. Compare each option's count against 10, which is the expected average for 50 spins at 1 in 5 each.

4. Expect spread. In 50 spins, counts of 7 to 13 are ordinary variation, and only a repeated run with the same lopsided pattern suggests a problem worth reporting.

5. Repeat once after shuffling the list order. A genuine wheel fault follows the mechanism, a rigged list follows the labels.

Tool limits that affect the result

The list holds between 2 and 20 options, each capped at 30 characters. On the wheel face, labels longer than 14 characters are cut to 12 plus an ellipsis, so two long options that share their first 12 characters look identical on the canvas even though the winner banner prints the full text. The reset button restores the first 3 default options rather than emptying the wheel, which surprises people who expected a blank slate.

Checklist before you accept a result

  • [ ] No option appears twice unless you intend to weight it.
  • [ ] The list was not edited between spins, since deletion recolors and reshuffles positions.
  • [ ] Long labels are distinguishable past their first 12 characters.
  • [ ] The stakes do not require a cryptographic draw.
  • [ ] A suspicious streak got retested with a fresh 50 spin tally.

The audit verdict is that the mechanism gives every slice exactly even odds, and every lever for bias lives in the list you type. Load your own options into the [decision wheel](/en/decision-wheel), run the 50 spin tally, and bring us your counts if any option breaks the 7 to 13 band twice in a row.