use Test;
use lib 'lib';
use Noise::Simplex::Native;
require Noise::Simplex;

# 3D parity check vs the pure-Raku original.
#
# Strict bit-identicality is *not* achievable for 3D, even in
# principle. The original 3D pipeline computes in arbitrary-precision
# Rat (its $f3 = 1/3 and $g3 = 1/6 constants are Rat literals, and
# Int - Rat - Rat - ... stays Rat all the way through the floor / X0
# / x0 derivations). A C double-precision impl accumulates ~1-ULP
# rounding error at every step.
#
# Most of the time that just shows up as ULP-level wobble in the final
# result. But the simplex-corner selection (six branches keyed on
# `x0 >= y0`, `y0 >= z0`, `x0 >= z0`) is brittle: at coordinates where
# x0, y0, or z0 are exactly equal in Rat (e.g. on simplex diagonals
# like x = y, or x = z, or x ≈ y ≈ z up to a few ULPs), Raku's exact
# comparison and C's accumulated-error comparison can disagree. They
# then pick *different* simplex corners and produce results that
# differ by O(0.001) — not ULP-small.
#
# So we characterize the actual behaviour:
#   - median  absolute diff is ULP-small (covers the typical case)
#   - mean    absolute diff is well below the noise scale
#   - maximum absolute diff is bounded (catches algorithmic bugs;
#     0.05 is generous given expected corner-flip divergences are
#     < 0.01 and the noise itself is in [-1, 1])

my $OrigSimplex = ::("Noise::Simplex::EXPORT::DEFAULT::Simplex");

my @seeds = 1, 12345, 54321, 0, 2 ** 31 - 1;

my @samples;
for ^16 -> $i {
    for ^16 -> $j {
        for ^4 -> $k {
            @samples.push: (
                ($i - 8)  * 0.125e0,
                ($j - 8)  * 0.0625e0,
                ($k - 2)  * 0.25e0,
            );
        }
    }
}

plan @seeds.elems;

for @seeds -> $seed {
    subtest "seed $seed" => {
        plan 3;
        my $orig = $OrigSimplex.new(:$seed);
        my $nat  = Noise::Simplex::Native::Simplex.new(:$seed);
        my &n3d-orig = $orig.create-noise3d;
        my &n3d-nat  = $nat.create-noise3d;

        my @diffs;
        for @samples -> ($x, $y, $z) {
            my num64 $a = n3d-orig($x, $y, $z).Num;
            my num64 $b = n3d-nat($x, $y, $z);
            @diffs.push: ($a - $b).abs.Num;
        }

        my @sorted = @diffs.sort;
        my $median = @sorted[@sorted.elems div 2];
        my $mean   = @diffs.sum / @diffs.elems;
        my $max    = @sorted[*-1];

        ok $median < 1e-10,
           "median diff $median < 1e-10 (ULP-level for the typical sample)";
        ok $mean   < 1e-3,
           "mean diff $mean < 1e-3 (corner-flip outliers averaged in)";
        ok $max    < 0.05,
           "max diff $max < 0.05 (bounds corner-flip divergence)";
    }
}

done-testing;
