Amphora.RandomComparisons compares random number generators against each other and against controls that are bad in known ways. It is not a unit test, because nothing here has a right answer. It is run by hand, to pick between algorithms and to catch a mistake in one. Run Bin\Release\Amphora.RandomComparisons.exe, which runs as 64-bit since it takes billions of draws. Options: --quick One eighth of the work, about 20 seconds. --seed N Starts every generator from this seed. Rerun with another before believing a SUSPECT. --picture PATH Where the hyperplane picture goes. The default is beside the exe. --no-picture skips it. --self-check Checks the checks. Compares the statistics with values computed independently in Python, and runs a quick battery that every control must fail in the way it is known to fail. --calibrate N Runs the checks on the named generator under N seeds, and tests that each check's probabilities are evenly spread, as they must be if it measures what it claims. --raw NAME Writes the named subject's 64-bit words to standard output forever, for a tester such as PractRand that reads a stream. PractRand is not vendored. TEXT Runs only the subjects with TEXT in their name. Subjects. The generators in Amphora: xoshiro256++, xoshiro128++, SFC64 and SplitMix64. Each also started as sixteen streams from adjacent seeds, and the xoshiros as sixteen streams made by Jump(), with the sixteen's words taken in turn. And the controls, each bad in one way: RANDU (planes in three dimensions), MINSTD (a lattice), LCG64 (its low bits cycle), xorshift64 (linear over GF(2)), xoshiro256+ (its low bits are linear, which the ++ hides), and System.Random (poor seeding). All of them but System.Random live in Amphora's Random folder. Every check runs on the high end of each 64-bit word and on the low end, because generators differ most in their low bits. A generator whose output is narrower than 64 bits has it at the top, and only the high end is checked. The checks: bit balance How often each of the 64 bits is set. serial Counts every combination of 1 to 8 consecutive draws. Planes and lattices leave most combinations empty. birthday spacings Marsaglia's. The gaps between sorted birthdays repeat more often if points lie on a lattice. binary rank The rank over GF(2) of 32x32 and 64x64 matrices of draws. Linear generators come out wrong. linear complexity The Berlekamp-Massey length of the shortest register that makes a bit position. A generator that is linear in a bit needs a register no longer than its state. first draws The first draws of consecutive seeds, as when a program seeds many generators from a counter. recovery How many draws a state with one bit set takes to look random. This is information only. bounded draws Amphora's NextUInt32(bound) and the like, against a plain remainder, which must fail. range draws NextDouble and NextFloat over a range, against scaling a draw from [0, 1) into it (minimum + (maximum - minimum) * u) and against interpolating (minimum * (1 - u) + maximum * u), which must fail. Amphora draws evenly from the grid of the range's coarsest spacing, so each draw lands in a cell of that grid and the cell number modulo 16 must be uniform. The quick ways round onto a grid that doesn't line up with u's, so some cells come up more than others, by half again in a range like [1, 2). They also return the excluded maximum for some ranges when u is as large as it gets, which is checked over 406 ranges. shaped draws Amphora's event waits and counts, bell curves, points in a circle, dice and picks. A draw over a continuous range is passed through its exact cumulative distribution, which makes a fair draw uniform, and that is tested in 256 equal parts. A point in a disc or ring is tested in a 16 by 16 grid of equal-area cells. Counts, dice, shuffles and samples are compared with their exact probabilities, found by enumeration. The curves are worked out with System.Math, so they share no arithmetic with Amphora's own. Each is also run against a tempting wrong version, which must fail: a uniform draw where an exponential belongs, a chance of span over mean, a count off by one, swapped shape numbers, a distance that is the radius times a draw, a point in a square scaled to the circle, 3d6 as one draw from 3 to 18, and a shuffle that swaps each place with any place. Each check gives the chance that a good generator lands as far from the expected result, on either side. Under 1e-6 is a FAIL. Under 1e-3 is SUSPECT, and a few of those among a hundred checks are chance. Where a check reports the worst of several tests, the chance is adjusted for having looked at several. The hyperplane picture takes consecutive draws three at a time as points in a cube and plots the points in a thin slice of it. RANDU's 15 planes show as stripes. A good generator shows noise. What it showed (2026-10-16, seed 20261016, full size, 64-bit, 8 logical processors). No real generator failed or was suspect on anything: 35 checks each for xoshiro256++, xoshiro128++, SFC64 and SplitMix64, and 12 each for their sixteen-stream variants and the two jumped ones. The lowest probability among them was 0.003, SFC64 on adjacent seeds, serial test on the low bits, which is about what the smallest of 212 checks looks like. Every control failed in the way it was chosen for. That includes the range draws, where scaling a draw into the range and interpolating each failed all 12 of its grid checks, with the busiest residue 1.03 to 1.75 times its share, and each returned the excluded maximum for 18 to 29 of 406 ranges. Amphora's range draws passed all 12 and returned it for none. RANDU 7 of 8. The serial test sees its planes (chi-square 38 million on 262143 df), and no 32x32 matrix of its draws reaches rank 31. MINSTD 6 of 8. Birthday spacings (2^32 days: 5.83 equal gaps against 4) and matrix rank. LCG64 8 of 16. Its lowest 16 bits cycle through every value equally, which the serial test calls too good to be true (chi-square 0.0), and the lowest bit has linear complexity 2. xorshift64 8 of 16. Linear complexity is exactly 64, its state size, and all 64x64 matrices have rank 64. xoshiro256+ 1 of 16. The lowest bit has linear complexity exactly 256, its state size. Everything else passes. System.Random 4 of 16. The plain stream passes at this size. Consecutive seeds do not. The hyperplane picture shows RANDU's planes as stripes and MINSTD's lattice as a fine diagonal hatch. Everything else is noise there, including the controls that fail only at the bit level. The shaped draws (2026-10-17, same seed and size). Each of the four generators ran 48 more checks, 83 in all, and none failed or was suspect. The lowest probability among the new ones was 0.0097, Chance(0.3) on xoshiro128++. All 20 wrong versions failed on every generator at p < 1e-300. The weakest was the shuffle that swaps each place with any place, at chi-square 31 thousand on 23 degrees of freedom. The exact probabilities of the dice were checked against brute force in --self-check, and --calibrate under 120 seeds at one eighth size found every new check uniform, with the lowest uniformity probability 0.026. Starting from a state with a single bit set, the draws before the bits set look fair: SplitMix64 0, xoshiro128++ 5, SFC64 7, xoshiro256++ 16. Seeding through SplitMix64 never makes such a state, so this matters only to a program that sets the state by hand. What this can't show. The checks use up to a few hundred million draws each. A flaw that PractRand or TestU01's BigCrush finds only after terabytes is out of reach, which is what --raw is for. What these do establish is that the four generators in Amphora have no flaw of the kinds above that matter at the scale a game draws at, and that each of those flaws is something the checks would have seen. What is known about the checks. --calibrate under 120 seeds each, at one eighth size, found every check uniform for all four generators (probabilities from 0.05 to 0.96 on the uniformity test). The birthday spacings use 2^28 days with 2048 birthdays and 2^32 days with 4096, since at smaller calendars the Poisson model is off by a percent or two, enough to raise false alarms at these sample sizes. The linear complexity probabilities are NIST's. The rank probabilities are exact. --calibrate found that the worst-of-several checks needed their adjustment, and that every other check is uniform under good generators.