From 00c97639dcf2a75f4751f83f03b281f84abe79f1 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sun, 4 Oct 2026 03:18:44 +0200 Subject: [PATCH] NoiseBoundTests: FlowVariation's float32 allowance pinned by its rule Co-Authored-By: Claude Sonnet 5.5 --- .../tests/CultMath.Tests/NoiseBoundTests.cs | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/packages/cultmath/tests/CultMath.Tests/NoiseBoundTests.cs b/packages/cultmath/tests/CultMath.Tests/NoiseBoundTests.cs index 7bb5c357..e98879ac 100644 --- a/packages/cultmath/tests/CultMath.Tests/NoiseBoundTests.cs +++ b/packages/cultmath/tests/CultMath.Tests/NoiseBoundTests.cs @@ -773,6 +773,45 @@ public sealed class NoiseBoundTests Assert.True(beyond > 0, "no warped point needs rho: the check cannot tell a centred warp without it"); } + /// + /// FlowVariation's width, pinned both ways: over 2,000 seeded balls (centres in [-2000, 2000]^3, radius + /// log-uniform in [1e-3, 500]) it equals min(sqrt(u^2 + v^2), 2) A, A = FlowAmplitude, with each of u and v + /// the reach of iv_snoise_ball(float2) at the flow plane's argument (plus its offset) plus the float32 + /// allowance 2^-14 + L2 2^-20 (|q|_1 + r FlowFrequency), to 0.3% (the float32 sum n + e rounds at 6e-8, against an allowance of 6e-5). The enclosure test sees the + /// allowance only where a float32 flow differs from the exact one by more than the bound's slack, which + /// on the CPU is rare (CentredWarpStaysEnclosed); this is the test that sees it. + /// + [Fact] + public void FlowVariationCarriesItsAllowance() + { + var noise = new WarpedNoise { F0 = 1.0f / FogField.NoiseScale, Warp = FogField.AetheriaWarp }; + var amplitude = noise.Warp * 2.0 / noise.Period; + var random = new System.Random(0xF10E); + double Reach(float2 q, double r) + { + var n = snoise(q); + var e = SNOISE2_LIPSCHITZ * r; + return Math.Max(Math.Min(e, 1.0 - n), Math.Min(e, n + 1.0)); + } + + for (var b = 0; b < 2000; b++) + { + var centre = new float3(Uniform(random, -2000.0f, 2000.0f), Uniform(random, -2000.0f, 2000.0f), Uniform(random, -2000.0f, 2000.0f)); + var radius = LogUniform(random, 1.0e-3f, 500.0f); + var rq = (double)radius * noise.FlowFrequency; + double Component(float2 offset) + { + var q = new float2(centre.x, centre.z) * noise.FlowFrequency + offset; + return Reach(q, rq) + Math.ScaleB(1.0, -14) + SNOISE2_LIPSCHITZ * Math.ScaleB(Math.Abs((double)q.x) + Math.Abs((double)q.y) + rq, -20); + } + + var (u, v) = (Component(new float2(17.0f, 3.0f)), Component(new float2(-5.0f, 41.0f))); + var expected = Math.Min(Math.Sqrt(u * u + v * v), 2.0) * amplitude; + var observed = noise.FlowVariation(centre, radius); + Assert.True(Math.Abs(observed - expected) <= 3.0e-3 * expected, $"FlowVariation({centre}, {radius:R}) = {observed:R}, the documented rule gives {expected:R}"); + } + } + /// /// The centred warp over an affine slice (FogField.Slice, the owner AffineBound reads): over 3,000 /// seeded tile slices (the deep well, the shallow wells and inside the fog; N in {1, 4, 8, 16}; a