diff --git a/packages/cultmath/tests/CultMath.Tests/NoiseBoundTests.cs b/packages/cultmath/tests/CultMath.Tests/NoiseBoundTests.cs index 74edd778..b1c39d14 100644 --- a/packages/cultmath/tests/CultMath.Tests/NoiseBoundTests.cs +++ b/packages/cultmath/tests/CultMath.Tests/NoiseBoundTests.cs @@ -995,4 +995,202 @@ public sealed class NoiseBoundTests return Compose(d, fade, noise); } } + + // One cell's transmittance: the density at the cell's mid-depth, over the ray's length through it. + private static float Integrate(Scenario field, Tile tile, float2 slope, float z0, float z1, bool lod, Counts counts) => + exp(-(field.Density(tile, slope, (z0 + z1) * 0.5f, lod, counts) * field.Extinction) * ((z1 - z0) * sqrt(slope.x * slope.x + slope.y * slope.y + 1.0f))); + + /// + /// The tile pre-pass on cell-index ranges: probe the slice of the range; provably empty skips it and + /// doubles the next range; otherwise a range of more than one cell halves, and a single cell is dense. + /// It starts with the whole grid, runs to the grid's end (a pre-pass knows no ray's transmittance) and + /// yields the dense cells in order. Its probes are counted once per tile. + /// + private static List MarchTile(Scenario field, Tile tile, Counts probes) + { + var grid = field.Grid; + var cells = grid.Length - 1; + var dense = new List(); + var j = 0; + var span = cells; + while (j < cells) + { + var end = Math.Min(j + span, cells); + if (field.Bound(tile, grid[j], grid[end], gated: true, probes).y <= 0.0f) + { + j = end; + span = Math.Min(span * 2, cells); + } + else if (end - j > 1) + { + span = (end - j + 1) / 2; + } + else + { + dense.Add(j); + j++; + } + } + + return dense; + } + + // The footprint-aware march, the void's shipped one: steps of LodStep(z) inside the unmasked runs only, + // octave weights applied. + private static float MarchLod(VoidField field, Tile tile, float2 slope, List cells, Counts counts, ref long steps) + { + var grid = field.Grid; + var transmittance = 1.0f; + var k = 0; + while (k < cells.Count && transmittance >= 0.02f) + { + var z = grid[cells[k]]; + var end = cells[k] + 1; + for (k++; k < cells.Count && cells[k] == end; k++) + end++; + while (z < grid[end] && transmittance >= 0.02f) + { + var next = min(z + field.LodStep(z), grid[end]); + transmittance *= Integrate(field, tile, slope, z, next, lod: true, counts); + steps++; + z = next; + } + } + + return transmittance; + } + + internal sealed class RunStats + { + public readonly Counts Dense = new(); + public readonly Counts Masked = new(); + public readonly Counts Probes = new(); + public readonly Counts Lod = new(); + public long Rays; + public long Tiles; + public long DenseSteps; + public long MaskedSteps; + public long LodSteps; + public long Unfinished; + public float MaxDifference; + public float MaxLodDifference; + + public double PerRay(double value) => value / Rays; + + // Dense cost over the tile march's: each ray's own dense cells plus the tile's probes, shared. + public double Ratio => Dense.Cost / (Masked.Cost + Probes.Cost); + + // What no amortization can beat: the dense cost over the cost of the dense cells alone. + public double Ceiling => Dense.Cost / Masked.Cost; + + public double LodRatio => Dense.Cost / (Lod.Cost + Probes.Cost); + } + + /// + /// Draws `tiles` tiles, runs the pre-pass once per tile, and marches each of the N^2 rays (one per pixel, + /// jittered inside it) densely over every cell and masked over the tile's dense cells, both with the + /// early-out at 0.02 and the same Integrate; with lod, the footprint-aware march as well. + /// + private static RunStats Run(Scenario field, int n, int tiles, int seed, bool lod = false) + { + var random = new System.Random(seed); + var stats = new RunStats(); + var grid = field.Grid; + for (var t = 0; t < tiles; t++) + { + var tile = field.DrawTile(random, n); + var cells = MarchTile(field, tile, stats.Probes); + stats.Tiles++; + for (var j = 0; j < n; j++) + for (var i = 0; i < n; i++) + { + var slope = tile.PixelSlope(i, j, random.NextSingle(), random.NextSingle()); + var dense = 1.0f; + for (var c = 0; c < grid.Length - 1 && dense >= 0.02f; c++, stats.DenseSteps++) + dense *= Integrate(field, tile, slope, grid[c], grid[c + 1], lod: false, stats.Dense); + var masked = 1.0f; + for (var c = 0; c < cells.Count && masked >= 0.02f; c++, stats.MaskedSteps++) + masked *= Integrate(field, tile, slope, grid[cells[c]], grid[cells[c] + 1], lod: false, stats.Masked); + stats.Rays++; + stats.MaxDifference = MathF.Max(stats.MaxDifference, MathF.Abs(dense - masked)); + if (dense >= 0.02f) + stats.Unfinished++; + if (lod) + { + var marched = MarchLod((VoidField)field, tile, slope, cells, stats.Lod, ref stats.LodSteps); + stats.MaxLodDifference = MathF.Max(stats.MaxLodDifference, MathF.Abs(dense - marched)); + } + } + } + + return stats; + } + + private void Report(string label, RunStats s) => output.WriteLine( + $"IV-REPORT {label}: dense snoise/ray {s.PerRay(s.Dense.Snoise):F2} env/ray {s.PerRay(s.Dense.Envelope):F2} (samples/ray {s.PerRay(s.DenseSteps):F2}); " + + $"tile snoise/ray {s.PerRay(s.Masked.Snoise + (double)s.Probes.Snoise):F2} env/ray {s.PerRay(s.Masked.Envelope + (double)s.Probes.Envelope):F2} " + + $"(probes/tile env {(double)s.Probes.Envelope / s.Tiles:F1} snoise {(double)s.Probes.Snoise / s.Tiles:F2}; dense cells/ray {s.PerRay(s.MaskedSteps):F2}); " + + $"cost ratio {s.Ratio:F2}x, ceiling {s.Ceiling:F2}x; max |dT| {s.MaxDifference:R}; unfinished rays {s.Unfinished}"); + + private void ReportVoid(string label, RunStats s) => output.WriteLine( + $"IV-REPORT {label}: steps/px ref {s.PerRay(s.DenseSteps):F2}, masked {s.PerRay(s.MaskedSteps):F2}, LOD {s.PerRay(s.LodSteps):F2} ({(double)s.DenseSteps / s.LodSteps:F2}x fewer); " + + $"snoise/px ref {s.PerRay(s.Dense.Snoise):F2}, masked {s.PerRay(s.Masked.Snoise):F2}, LOD {s.PerRay(s.Lod.Snoise):F2}; " + + $"cost/px ref {s.PerRay(s.Dense.Cost):F2}, masked {s.PerRay(s.Masked.Cost + s.Probes.Cost):F2}, LOD {s.PerRay(s.Lod.Cost + s.Probes.Cost):F2} incl. probes (ref/LOD {s.LodRatio:F2}x, ref/masked {s.Ratio:F2}x); " + + $"probes/tile env {(double)s.Probes.Envelope / s.Tiles:F1} snoise {(double)s.Probes.Snoise / s.Tiles:F2}; max |dT| masked {s.MaxDifference:R}, LOD {s.MaxLodDifference:R}; unfinished rays {s.Unfinished}"); + + /// + /// The proving ground (docs/cultmath-interval-ground-cut.md, "Pass 3"). Scenarios (a) height fog, + /// (b) inside the fog, (c) Aetheria's wells and (d) r1's uniform slab, each over 64 tiles at + /// N in {1, 4, 8, 16} and warp in {0, D}: the tile march integrates exactly the dense march's cells, so + /// the transmittance agrees within 1e-3 (a difference would be a skipped cell that was not empty), and + /// its combined cost must be at most half the dense march's for (a) and (c) at N = 8. (e) the void: the + /// grid over hollow radius, camera offset and ramp width (32 tiles at N = 8) and the shipped point + /// (200 tiles), three marches each; at the shipped point the masked fixed-step march agrees with the + /// reference exactly, the footprint-aware march within 0.02, and it must take at most half the + /// reference's steps per pixel. A shortfall in (a), (c) or (e) skips naming saving-2x-scenarios; the + /// fields, the grids and the envelope cost are not tuned toward it. + /// + [Fact] + public void IntervalSkipHalvesEvaluations() + { + var ratios = new Dictionary(); + var scenarios = new (string Key, float Warp, Func Make)[] + { + ("a", FogField.AetheriaWarp, FogField.HeightFog), + ("b", FogField.AetheriaWarp, FogField.InsideFog), + ("c", FogField.AetheriaWarp, FogField.Wells), + ("d", SlabField.SlabWarp, SlabField.Create), + }; + foreach (var (key, warp, make) in scenarios) + foreach (var n in new[] { 1, 4, 8, 16 }) + foreach (var w in new[] { 0.0f, warp }) + { + var field = make(w); + var stats = Run(field, n, 64, 0x5A7E + n); + Report($"{field.Name} N={n} warp={w:R}", stats); + Assert.True(stats.MaxDifference <= 1.0e-3f, $"{field.Name} N={n} warp={w:R}: transmittance differs by {stats.MaxDifference:R}"); + if (key == "d") + output.WriteLine($"IV-REPORT (d) N={n} warp={w:R}: ungated dense snoise/ray {3.0 * stats.PerRay(stats.DenseSteps):F2} (three per dense sample, as r1 counted)"); + if (n == 8) + ratios[key] = Math.Min(ratios.GetValueOrDefault(key, double.MaxValue), stats.Ratio); + } + + foreach (var rh in VoidField.HollowRadii) + foreach (var offset in VoidField.CameraOffsets) + foreach (var ramp in VoidField.Ramps) + { + var field = new VoidField(rh, ramp, offset.Fraction, offset.Name); + ReportVoid($"{field.Name} N=8", Run(field, 8, 32, 0x701D, lod: true)); + } + + var shipped = VoidField.Shipped(); + var headline = Run(shipped, 8, 200, 0x5417, lod: true); + ReportVoid($"{shipped.Name} N=8 (headline, 200 tiles)", headline); + Assert.True(headline.MaxDifference == 0.0f, $"the masked fixed-step march differs from the reference by {headline.MaxDifference:R}"); + Assert.True(headline.MaxLodDifference <= 0.02f, $"the footprint-aware march differs from the reference by {headline.MaxLodDifference:R}"); + var steps = (double)headline.DenseSteps / headline.LodSteps; + + if (ratios["a"] < 2.0 || ratios["c"] < 2.0 || steps < 2.0) + Assert.Skip($"saving-2x-scenarios: at N = 8, (a) saves {ratios["a"]:F2}x and (c) {ratios["c"]:F2}x combined cost (the lower of warp 0 and D); (e) takes {steps:F2}x fewer steps per pixel at the shipped point; the contract is 2x each"); + } }