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

# 2D output must be bit-for-bit equal to the pure-Raku Noise::Simplex
# for the same seed and same (x, y) — *when inputs are exactly
# representable in IEEE 754 double*. The 2D Raku pipeline does run
# entirely in Num once $sqrt3 forces Num, BUT additions like
# `-3.3 + -4.7` in Raku stay Rat-exact (= -8) before promotion, while
# the same expression with Num operands loses precision at the first
# add. Native coerces inputs to Num at the boundary, so to make the
# pipelines comparable we use Num-representable coords here (powers-
# of-2 fractions). With those, the contract is exact.
#
# Test coords that aren't exact doubles (e.g. 0.1, 0.3) may diverge
# by ≤1 ULP on the same algorithmic path; that is documented behaviour
# and not a regression.

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

# Reinterpret an IEEE 754 Num as its 64 raw bits, for bit-identity
# assertions that don't get fooled by NaN, -0.0, etc.
sub bits(num64 $n --> uint64) {
    my $buf = CArray[num64].new(0e0);
    $buf[0] = $n;
    nativecast(CArray[uint64], $buf)[0];
}

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

# 64x64 grid of Num-representable coordinates. Step size 1/8 (exactly
# representable) covers many simplex cells in both quadrants without
# triggering Rat-vs-Num precision drift in the original.
my @samples;
for ^64 -> $i {
    for ^64 -> $j {
        @samples.push: (($i - 32) * 0.125e0, ($j - 32) * 0.0625e0);
    }
}

plan @seeds.elems;

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

        my $mismatches = 0;
        my $first-mismatch;
        for @samples -> ($x, $y) {
            my num64 $a = n2d-orig($x, $y).Num;
            my num64 $b = n2d-nat($x, $y);
            unless bits($a) == bits($b) {
                $mismatches++;
                $first-mismatch //= "($x, $y): orig=$a ({bits($a).fmt('%016x')}), "
                                  ~ "nat=$b ({bits($b).fmt('%016x')})";
            }
        }
        is $mismatches, 0,
           "all { @samples.elems } 2D samples bit-identical "
           ~ ($first-mismatch // '');
    }
}

done-testing;
