From 2ba0dc17f4ab8018f597047e87df6d80e3ea9ded Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 3 Oct 2026 13:40:16 +0200 Subject: [PATCH] CultMath GLSL: Phacelle lowered into its own MPL-2.0 file; iv_frustum_ball and iv_exp in the fixture GlslLowering.SeparateFiles is the one name-keyed rule: CultMath.Phacelle.hlsl lowers into shaders/CultMath.Phacelle.glsl (MPL-2.0, guard CULTMATH_PHACELLE_GLSL) and its include line leaves CultMath.glsl, which is now MIT and says so in its provenance line. ReadShaderSource takes an include predicate (one resolver); the C# mirror still inlines everything. GlslMirrorTests pins both files, checks tokens in each, takes the union of definitions and asserts cultmath_phacelle( and CultPhasor live only in the Phacelle file. The fixture appends cultmath_iv_frustum_ball (exact) and cultmath_iv_exp ("ulp-bounded, platform exp", platform named); the comparison is per family, the platform family within 1 ulp with the maximum printed. THIRD-PARTY-NOTICES lists CultMath.Phacelle.glsl, not CultMath.glsl, as MPL-2.0. Regenerated on Yggdrasil (Ubuntu 24.04.5, .NET 10.0.12); 286/286 green, iv_exp within 0 ulp. Co-Authored-By: Claude Opus 5.5 --- packages/cultmath/THIRD-PARTY-NOTICES.md | 10 +- .../cultmath/shaders/CultMath.Phacelle.glsl | 99 ++++ packages/cultmath/shaders/CultMath.glsl | 102 +--- .../tests/CultMath.Tests/GlslLowering.cs | 69 ++- .../tests/CultMath.Tests/GlslMirrorTests.cs | 126 ++++- .../HlslSourceCompatibilityTests.cs | 12 +- .../CultMath.Tests/fixtures/glsl-parity.json | 525 ++++++++++++++++++ 7 files changed, 799 insertions(+), 144 deletions(-) create mode 100644 packages/cultmath/shaders/CultMath.Phacelle.glsl diff --git a/packages/cultmath/THIRD-PARTY-NOTICES.md b/packages/cultmath/THIRD-PARTY-NOTICES.md index f96efa7f..d6da41ea 100644 --- a/packages/cultmath/THIRD-PARTY-NOTICES.md +++ b/packages/cultmath/THIRD-PARTY-NOTICES.md @@ -36,8 +36,8 @@ gradient and the exact pruning are new here. covers exactly the files that carry the port and nothing else: `src/CultMath/math.Phacelle.cs`, `src/CultMath/CultPhasor.cs`, `shaders/CultMath.Phacelle.hlsl` and the Unity copy of that include, and - `shaders/CultMath.glsl`, which is generated from the HLSL with that include - inlined, so the generated file as a whole is MPL-2.0. Each carries the - MPL-2.0 header and this attribution. The rest of CultMath is MIT, - so the `GameCult.Math` NuGet package and the `org.gamecult.cultmath` Unity - package both declare `MIT AND MPL-2.0` and ship this file. + `shaders/CultMath.Phacelle.glsl`, which is generated from that include. Each + carries the MPL-2.0 header and this attribution. The rest of CultMath, the + generated GLSL library included, is MIT, so the `GameCult.Math` NuGet + package and the `org.gamecult.cultmath` Unity package both declare + `MIT AND MPL-2.0` and ship this file. diff --git a/packages/cultmath/shaders/CultMath.Phacelle.glsl b/packages/cultmath/shaders/CultMath.Phacelle.glsl new file mode 100644 index 00000000..11405fa2 --- /dev/null +++ b/packages/cultmath/shaders/CultMath.Phacelle.glsl @@ -0,0 +1,99 @@ +// Generated from shaders/CultMath.Phacelle.hlsl by GlslLowering (packages/cultmath/tests/CultMath.Tests/GlslLowering.cs). Do not edit; regenerate with CULTMATH_WRITE_GLSL=1 dotnet test --filter GlslMirrorTests. MPL-2.0; requires CultMath.glsl before it. +#ifndef CULTMATH_PHACELLE_GLSL +#define CULTMATH_PHACELLE_GLSL +// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of +// the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// Phacelle noise. Copyright (c) 2025 Rune Skovbo Johansen (Shadertoy t3dyWl; MPL-2.0), read from the +// C# port PhacelleNoise in lpmitchell/AdvancedTerrainErosion, converted to burstable C# by Luke +// Mitchell (2026). Modified by GameCult: 3D cells, a caller-supplied stripe wave vector, an exact +// gradient and exact pruning. This file is an MPL-2.0 file; CultMath.hlsl, which includes it after +// its own definitions of CULTMATH_TAU and cultmath_pcg3d, is MIT (see THIRD-PARTY-NOTICES.md). + +// Invariant 8's struct return shape for cultmath_phacelle, compared field by field and bit for bit +// against CultMath.CultPhasor, same field order. +struct CultPhasor +{ + vec4 cos; + vec4 sin; +}; + +// Johansen's Phacelle noise on 3D cells. See math.phacelle's comment (math.Phacelle.cs) for the stripe wave +// vector, the weight, the normalization, the exact gradient and why the prune is exact. Mirrors +// the C# body operation for operation; the C#-only internal overload that switches the prune off +// has no shader twin. +CultPhasor cultmath_phacelle(vec3 p, vec3 side, float offset, float normalization) +{ + vec3 cell = floor(p); + vec3 local = p - cell; + float phaseOffset = offset * CULTMATH_TAU; + float sumW = 0.0; + float sumCos = 0.0; + float sumSin = 0.0; + vec3 gradW = vec3(0.0, 0.0, 0.0); + vec3 gradCos = vec3(0.0, 0.0, 0.0); + vec3 gradSin = vec3(0.0, 0.0, 0.0); + + for (int dz = -1; dz <= 2; dz++) + { + float gz = max(0.0, abs(local.z - float(dz)) - 0.5); + for (int dy = -1; dy <= 2; dy++) + { + float gy = max(0.0, abs(local.y - float(dy)) - 0.5); + for (int dx = -1; dx <= 2; dx++) + { + float gx = max(0.0, abs(local.x - float(dx)) - 0.5); + if (gx * gx + gy * gy + gz * gz >= 2.25) + continue; + + vec3 gridStep = vec3(dx, dy, dz); + ivec3 hash = cultmath_pcg3d(ivec3(cell + gridStep)); + vec3 jitter = vec3( + float(uint(hash.x) >> 8) * (1.0 / 16777216.0) - 0.5, + float(uint(hash.y) >> 8) * (1.0 / 16777216.0) - 0.5, + float(uint(hash.z) >> 8) * (1.0 / 16777216.0) - 0.5); + vec3 v = local - gridStep - jitter; + float falloff = exp(-2.0 * dot(v, v)); + float w = max(0.0, falloff - 0.01111); + vec3 dw = w > 0.0 ? (-4.0 * falloff) * v : vec3(0.0, 0.0, 0.0); + float phase = dot(v, side) + phaseOffset; + float c = cos(phase); + float s = sin(phase); + + sumW += w; + sumCos += w * c; + sumSin += w * s; + gradW += dw; + gradCos += dw * c - (w * s) * side; + gradSin += dw * s + (w * c) * side; + } + } + } + + float invW = 1.0 / sumW; + float rawCos = sumCos * invW; + float rawSin = sumSin * invW; + vec3 gradRawCos = (gradCos - rawCos * gradW) * invW; + vec3 gradRawSin = (gradSin - rawSin * gradW) * invW; + float rawLength = sqrt(rawCos * rawCos + rawSin * rawSin); + float floorLength = 1.0 - normalization; + + CultPhasor result; + if (rawLength > floorLength) + { + float outCos = rawCos / rawLength; + float outSin = rawSin / rawLength; + vec3 gradLength = (rawCos * gradRawCos + rawSin * gradRawSin) / rawLength; + result.cos = vec4((gradRawCos - outCos * gradLength) / rawLength, outCos); + result.sin = vec4((gradRawSin - outSin * gradLength) / rawLength, outSin); + } + else + { + result.cos = vec4(gradRawCos / floorLength, rawCos / floorLength); + result.sin = vec4(gradRawSin / floorLength, rawSin / floorLength); + } + + return result; +} + +#endif diff --git a/packages/cultmath/shaders/CultMath.glsl b/packages/cultmath/shaders/CultMath.glsl index 6adf439b..61b6ebdf 100644 --- a/packages/cultmath/shaders/CultMath.glsl +++ b/packages/cultmath/shaders/CultMath.glsl @@ -1,4 +1,4 @@ -// Generated from shaders/CultMath.hlsl and its includes by GlslLowering (packages/cultmath/tests/CultMath.Tests/GlslLowering.cs). Do not edit; regenerate with CULTMATH_WRITE_GLSL=1 dotnet test --filter GlslMirrorTests. +// Generated from shaders/CultMath.hlsl and CultMath.Interval.hlsl by GlslLowering (packages/cultmath/tests/CultMath.Tests/GlslLowering.cs). Do not edit; regenerate with CULTMATH_WRITE_GLSL=1 dotnet test --filter GlslMirrorTests. MIT. cultmath_phacelle is in CultMath.Phacelle.glsl, to be concatenated after this file by consumers that call it. #ifndef CULTMATH_GLSL #define CULTMATH_GLSL // CultMath HLSL mirror. @@ -509,106 +509,6 @@ CultCellular cultmath_cellular(vec3 p) -// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of -// the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. -// -// Phacelle noise. Copyright (c) 2025 Rune Skovbo Johansen (Shadertoy t3dyWl; MPL-2.0), read from the -// C# port PhacelleNoise in lpmitchell/AdvancedTerrainErosion, converted to burstable C# by Luke -// Mitchell (2026). Modified by GameCult: 3D cells, a caller-supplied stripe wave vector, an exact -// gradient and exact pruning. This file is an MPL-2.0 file; CultMath.hlsl, which includes it after -// its own definitions of CULTMATH_TAU and cultmath_pcg3d, is MIT (see THIRD-PARTY-NOTICES.md). - -// Invariant 8's struct return shape for cultmath_phacelle, compared field by field and bit for bit -// against CultMath.CultPhasor, same field order. -struct CultPhasor -{ - vec4 cos; - vec4 sin; -}; - -// Johansen's Phacelle noise on 3D cells. See math.phacelle's comment (math.Phacelle.cs) for the stripe wave -// vector, the weight, the normalization, the exact gradient and why the prune is exact. Mirrors -// the C# body operation for operation; the C#-only internal overload that switches the prune off -// has no shader twin. -CultPhasor cultmath_phacelle(vec3 p, vec3 side, float offset, float normalization) -{ - vec3 cell = floor(p); - vec3 local = p - cell; - float phaseOffset = offset * CULTMATH_TAU; - float sumW = 0.0; - float sumCos = 0.0; - float sumSin = 0.0; - vec3 gradW = vec3(0.0, 0.0, 0.0); - vec3 gradCos = vec3(0.0, 0.0, 0.0); - vec3 gradSin = vec3(0.0, 0.0, 0.0); - - for (int dz = -1; dz <= 2; dz++) - { - float gz = max(0.0, abs(local.z - float(dz)) - 0.5); - for (int dy = -1; dy <= 2; dy++) - { - float gy = max(0.0, abs(local.y - float(dy)) - 0.5); - for (int dx = -1; dx <= 2; dx++) - { - float gx = max(0.0, abs(local.x - float(dx)) - 0.5); - if (gx * gx + gy * gy + gz * gz >= 2.25) - continue; - - vec3 gridStep = vec3(dx, dy, dz); - ivec3 hash = cultmath_pcg3d(ivec3(cell + gridStep)); - vec3 jitter = vec3( - float(uint(hash.x) >> 8) * (1.0 / 16777216.0) - 0.5, - float(uint(hash.y) >> 8) * (1.0 / 16777216.0) - 0.5, - float(uint(hash.z) >> 8) * (1.0 / 16777216.0) - 0.5); - vec3 v = local - gridStep - jitter; - float falloff = exp(-2.0 * dot(v, v)); - float w = max(0.0, falloff - 0.01111); - vec3 dw = w > 0.0 ? (-4.0 * falloff) * v : vec3(0.0, 0.0, 0.0); - float phase = dot(v, side) + phaseOffset; - float c = cos(phase); - float s = sin(phase); - - sumW += w; - sumCos += w * c; - sumSin += w * s; - gradW += dw; - gradCos += dw * c - (w * s) * side; - gradSin += dw * s + (w * c) * side; - } - } - } - - float invW = 1.0 / sumW; - float rawCos = sumCos * invW; - float rawSin = sumSin * invW; - vec3 gradRawCos = (gradCos - rawCos * gradW) * invW; - vec3 gradRawSin = (gradSin - rawSin * gradW) * invW; - float rawLength = sqrt(rawCos * rawCos + rawSin * rawSin); - float floorLength = 1.0 - normalization; - - CultPhasor result; - if (rawLength > floorLength) - { - float outCos = rawCos / rawLength; - float outSin = rawSin / rawLength; - vec3 gradLength = (rawCos * gradRawCos + rawSin * gradRawSin) / rawLength; - result.cos = vec4((gradRawCos - outCos * gradLength) / rawLength, outCos); - result.sin = vec4((gradRawSin - outSin * gradLength) / rawLength, outSin); - } - else - { - result.cos = vec4(gradRawCos / floorLength, rawCos / floorLength); - result.sin = vec4(gradRawSin / floorLength, rawSin / floorLength); - } - - return result; -} - - - - - - // Interval arithmetic over vec2(lo, hi), lo <= hi, both finite. Every cultmath_iv_* function returns // an interval containing f(x) for every x in its input intervals, mirroring math.Interval.cs bit for // bit; see that file and docs/design.md ("Intervals") for each op's enclosure and its ulp widening. diff --git a/packages/cultmath/tests/CultMath.Tests/GlslLowering.cs b/packages/cultmath/tests/CultMath.Tests/GlslLowering.cs index 950fb270..e5ccb88f 100644 --- a/packages/cultmath/tests/CultMath.Tests/GlslLowering.cs +++ b/packages/cultmath/tests/CultMath.Tests/GlslLowering.cs @@ -4,24 +4,66 @@ using System.Text.RegularExpressions; namespace CultMath.Tests; /// -/// The HLSL to GLSL ES 3.00 transformation that produces shaders/CultMath.glsl from -/// shaders/CultMath.hlsl. The steps below, in order, are the complete list documented in -/// docs/design.md (GLSL Target: Source Transformations). A dialect gap outside the list is fixed in the -/// HLSL, in the subset both languages share, never by a special case here; GlslMirrorTests fails on any -/// HLSL-only token that survives. +/// The HLSL to GLSL ES 3.00 transformation that produces shaders/CultMath.glsl, and one file per +/// entry, from shaders/CultMath.hlsl and its includes. The steps below, +/// in order, are the complete list documented in docs/design.md (GLSL Target: Source Transformations). +/// A dialect gap outside the list is fixed in the HLSL, in the subset both languages share, never by a +/// special case here; GlslMirrorTests fails on any HLSL-only token that survives. /// internal static class GlslLowering { - internal const string ProvenanceLine = - "// Generated from shaders/CultMath.hlsl and its includes by GlslLowering " + - "(packages/cultmath/tests/CultMath.Tests/GlslLowering.cs). Do not edit; regenerate with " + + /// + /// Includes under a file-level licence of their own: each is lowered into its own output file instead + /// of being inlined, so CultMath.glsl stays MIT. This table is the lowering's only rule keyed by name. + /// + internal static readonly IReadOnlyDictionary SeparateFiles = + new Dictionary + { + // Phacelle is a port of MPL-2.0 code, and MPL-2.0 covers the files that carry it. + ["CultMath.Phacelle.hlsl"] = ("CultMath.Phacelle.glsl", "MPL-2.0"), + }; + + private const string Regenerate = + "by GlslLowering (packages/cultmath/tests/CultMath.Tests/GlslLowering.cs). Do not edit; regenerate with " + "CULTMATH_WRITE_GLSL=1 dotnet test --filter GlslMirrorTests."; /// - /// Lowers the HLSL text whose includes are already inlined in place (step 1: - /// , the C# mirror's own resolver). + /// The lowered files as (name under shaders/, text): CultMath.glsl first, then one per + /// entry, which requires CultMath.glsl before it. /// - internal static string Lower(string hlsl) + internal static IReadOnlyList<(string File, string Text)> Lower(string cultMathRoot) + { + // 1. Each #include is inlined in place by the C# mirror's own resolver, except a SeparateFiles + // include, which leaves nothing behind and is lowered into its own file. + var inlined = new List(); + var library = HlslSourceCompatibilityTests.ReadShaderSource(cultMathRoot, name => + { + if (SeparateFiles.ContainsKey(name)) + return false; + inlined.Add(name); + return true; + }); + + var outputs = new List<(string File, string Text)>(); + var pointers = new List(); + foreach (var (hlsl, (glsl, licence)) in SeparateFiles) + { + var text = LowerText( + File.ReadAllText(Path.Combine(cultMathRoot, "shaders", hlsl)), + $"// Generated from shaders/{hlsl} {Regenerate} {licence}; requires CultMath.glsl before it.", + glsl); + var functions = Regex.Matches(text, @"(?m)^\w+\s+(cultmath_\w+)\s*\(").Select(m => m.Groups[1].Value); + pointers.Add($" {string.Join(", ", functions)} is in {glsl}, to be concatenated after this file by consumers that call it."); + outputs.Add((glsl, text)); + } + + var includes = string.Concat(inlined.Select(name => " and " + name)); + outputs.Insert(0, ("CultMath.glsl", LowerText( + library, $"// Generated from shaders/CultMath.hlsl{includes} {Regenerate} MIT.{string.Concat(pointers)}", "CultMath.glsl"))); + return outputs; + } + + private static string LowerText(string hlsl, string provenance, string file) { var source = hlsl.ReplaceLineEndings("\n"); @@ -56,8 +98,9 @@ internal static class GlslLowering // 7. Float literals pass through: HLSL's unsuffixed 0.5 and 1.0e30 are GLSL literals already. - // 8. The CULTMATH_GLSL guard, and 9. the provenance line. - return ProvenanceLine + "\n#ifndef CULTMATH_GLSL\n#define CULTMATH_GLSL\n" + source.Trim('\n') + "\n\n#endif\n"; + // 8. The guard named for the file (CultMath.glsl -> CULTMATH_GLSL), and 9. the provenance line. + var guard = file.ToUpperInvariant().Replace('.', '_'); + return provenance + "\n#ifndef " + guard + "\n#define " + guard + "\n" + source.Trim('\n') + "\n\n#endif\n"; } private static readonly Regex Cast = new(@"\((float|int|uint)\)"); diff --git a/packages/cultmath/tests/CultMath.Tests/GlslMirrorTests.cs b/packages/cultmath/tests/CultMath.Tests/GlslMirrorTests.cs index e31b69da..129fa7de 100644 --- a/packages/cultmath/tests/CultMath.Tests/GlslMirrorTests.cs +++ b/packages/cultmath/tests/CultMath.Tests/GlslMirrorTests.cs @@ -1,5 +1,7 @@ using System.Reflection; +using System.Runtime.InteropServices; using System.Text; +using System.Text.Json.Nodes; using System.Text.RegularExpressions; using CultMath; using Xunit; @@ -7,34 +9,40 @@ using Xunit; namespace CultMath.Tests; /// -/// shaders/CultMath.glsl is derived: it is exactly of the committed HLSL, -/// no HLSL-only token survives the lowering, no public function is dropped except the texture samplers, -/// and the golden fixture a WebGL2 consumer evaluates is C#'s current output. With CULTMATH_WRITE_GLSL=1 -/// set, the first and last tests write the committed files before comparing, which is the one -/// regeneration path. +/// shaders/CultMath.glsl and shaders/CultMath.Phacelle.glsl are derived: each is exactly its +/// output from the committed HLSL, no HLSL-only token survives the +/// lowering, no public function is dropped except the texture samplers, the MPL-2.0 Phacelle code lives +/// only in its own file, and the golden fixture a WebGL2 consumer evaluates is C#'s current output. With +/// CULTMATH_WRITE_GLSL=1 set, the first and last tests write the committed files before comparing, which +/// is the one regeneration path. /// public sealed class GlslMirrorTests { + private readonly ITestOutputHelper output; + + public GlslMirrorTests(ITestOutputHelper output) => this.output = output; + private static bool Write => Environment.GetEnvironmentVariable("CULTMATH_WRITE_GLSL") == "1"; private static string Root => HlslSourceCompatibilityTests.FindCultMathRoot(); - private static string GlslPath => Path.Combine(Root, "shaders", "CultMath.glsl"); - private static string FixturePath => Path.Combine(Root, "tests", "CultMath.Tests", "fixtures", "glsl-parity.json"); private static string Hlsl => HlslSourceCompatibilityTests.ReadShaderSource(Root).ReplaceLineEndings("\n"); - private static string Lowered => GlslLowering.Lower(HlslSourceCompatibilityTests.ReadShaderSource(Root)); + private static IReadOnlyList<(string File, string Text)> Lowered => GlslLowering.Lower(Root); [Fact] public void CommittedGlslEqualsLowering() { - var expected = Lowered; - if (Write) - File.WriteAllText(GlslPath, expected); - var committed = File.ReadAllText(GlslPath).ReplaceLineEndings("\n"); - Assert.True(committed == expected, "shaders/CultMath.glsl is not the lowering of the committed HLSL (regenerate with CULTMATH_WRITE_GLSL=1):\n" + UnifiedDiff(committed, expected)); + foreach (var (file, expected) in Lowered) + { + var path = Path.Combine(Root, "shaders", file); + if (Write) + File.WriteAllText(path, expected); + var committed = File.ReadAllText(path).ReplaceLineEndings("\n"); + Assert.True(committed == expected, $"shaders/{file} is not the lowering of the committed HLSL (regenerate with CULTMATH_WRITE_GLSL=1):\n" + UnifiedDiff(committed, expected)); + } } // Identifier boundaries: cultmath_lerp is a CultMath function, lerp( is HLSL. @@ -44,8 +52,11 @@ public sealed class GlslMirrorTests [Fact] public void NoHlslOnlyTokensSurvive() { - var survivors = HlslOnly.Matches(Lowered).Select(m => m.Value).Distinct().ToArray(); - Assert.True(survivors.Length == 0, "HLSL-only tokens in the lowering: " + string.Join(", ", survivors)); + foreach (var (file, text) in Lowered) + { + var survivors = HlslOnly.Matches(text).Select(m => m.Value).Distinct().ToArray(); + Assert.True(survivors.Length == 0, $"HLSL-only tokens in {file}: " + string.Join(", ", survivors)); + } } private static readonly Regex Definition = new(@"(?m)^\w+\s+(cultmath_\w+)\s*\(([^)]*)\)"); @@ -53,11 +64,21 @@ public sealed class GlslMirrorTests [Fact] public void EveryPublicFunctionIsLowered() { + var lowered = Lowered; var hlsl = Definition.Matches(Hlsl).Where(m => !Regex.IsMatch(m.Groups[2].Value, @"\b(Texture2D|SamplerState)\b")) .Select(m => m.Groups[1].Value).ToHashSet(); - var glsl = Definition.Matches(Lowered).Select(m => m.Groups[1].Value).ToHashSet(); + var glsl = lowered.SelectMany(output => Definition.Matches(output.Text)).Select(m => m.Groups[1].Value).ToHashSet(); Assert.Contains("cultmath_snoise", glsl); Assert.True(hlsl.SetEquals(glsl), "HLSL only: " + string.Join(", ", hlsl.Except(glsl)) + "; GLSL only: " + string.Join(", ", glsl.Except(hlsl))); + + // The MPL-2.0 code is in its own file and nowhere in the MIT library. + var library = lowered.Single(output => output.File == "CultMath.glsl").Text; + var phacelle = lowered.Single(output => output.File == "CultMath.Phacelle.glsl").Text; + foreach (var token in new[] { "cultmath_phacelle(", "CultPhasor" }) + { + Assert.DoesNotContain(token, library); + Assert.Contains(token, phacelle); + } } [Fact] @@ -70,10 +91,59 @@ public sealed class GlslMirrorTests File.WriteAllText(FixturePath, expected); } - var committed = File.ReadAllText(FixturePath).ReplaceLineEndings("\n"); - Assert.True(committed == expected, "fixtures/glsl-parity.json is not C#'s current output (regenerate with CULTMATH_WRITE_GLSL=1):\n" + UnifiedDiff(committed, expected)); + // Family by family: every family is compared as text, except one whose tolerance names the + // platform, whose results are compared case by case within one ulp. + var committed = JsonNode.Parse(File.ReadAllText(FixturePath))!.AsObject(); + var current = JsonNode.Parse(expected)!.AsObject(); + var committedFamilies = committed["functions"]!.AsArray(); + var currentFamilies = current["functions"]!.AsArray(); + Assert.Equal(currentFamilies.Count, committedFamilies.Count); + for (var f = 0; f < currentFamilies.Count; f++) + { + var want = currentFamilies[f]!.AsObject(); + var have = committedFamilies[f]!.AsObject(); + var name = (string)want["glsl"]!; + Assert.Equal(name, (string?)have["glsl"]); + Assert.Equal((string)want["tolerance"]!, (string?)have["tolerance"]); + if (((string)want["tolerance"]!).Contains(PlatformTolerance)) + { + var ulps = MaxUlps(have["cases"]!.AsArray(), want["cases"]!.AsArray(), name); + output.WriteLine($"{name}: within {ulps} ulp of this platform's exp ({RuntimeInformation.OSDescription}); fixture generated on {have["platform"]}"); + Assert.True(ulps <= 1, $"{name}: {ulps} ulp from this platform's result, more than 1"); + } + else + { + Assert.True(JsonNode.DeepEquals(want, have), $"fixtures/glsl-parity.json family {name} is not C#'s current output (regenerate with CULTMATH_WRITE_GLSL=1)"); + } + } + + committed.Remove("functions"); + current.Remove("functions"); + Assert.True(JsonNode.DeepEquals(current, committed), "fixtures/glsl-parity.json header is not the generator's (regenerate with CULTMATH_WRITE_GLSL=1)"); } + // The largest distance in float32 ulps between two families' results; arguments must be identical. + private static long MaxUlps(JsonArray committed, JsonArray current, string name) + { + Assert.Equal(current.Count, committed.Count); + long max = 0; + for (var c = 0; c < current.Count; c++) + { + var have = ((string)committed[c]!).Split(" -> "); + var want = ((string)current[c]!).Split(" -> "); + Assert.True(have[0] == want[0], $"{name} case {c}: arguments differ"); + var haveBits = have[1].Split(' ').Select(h => (int)Convert.ToUInt32(h, 16)).ToArray(); + var wantBits = want[1].Split(' ').Select(h => (int)Convert.ToUInt32(h, 16)).ToArray(); + Assert.Equal(wantBits.Length, haveBits.Length); + max = Math.Max(max, haveBits.Zip(wantBits, (x, y) => Math.Abs((long)Ordered(x) - Ordered(y))).Max()); + } + + return max; + } + + // A float32 bit pattern as an integer whose order and differences are the floats' order and ulps. + private static int Ordered(int bits) => bits < 0 ? int.MinValue - bits : bits; + // ---- The golden fixture ---- private const int Seed = 20261002; @@ -92,9 +162,14 @@ public sealed class GlslMirrorTests return new float2(centre - width * 0.5f, centre + width * 0.5f); } - // Every family the site's WebGL2 readback evaluates. pcg3d and pcg4d are integer-only and must match - // exactly; the float families are ulp-bounded, with the bound measured on the device by the consumer. - // iv_exp is left out: it calls the platform's exp, so its C# bits are not the same on every OS. + // A tolerance containing this names a family whose C# bits come from the platform's exp and so differ + // by OS: GoldenFixtureMatchesCSharp compares it within one ulp, and its entry records the platform. + private const string PlatformTolerance = "platform exp"; + + // Every family the site's WebGL2 readback evaluates, in a fixed order. A new family is appended, so the + // seeded stream and every earlier family's cases stay as they were. pcg3d, pcg4d and iv_frustum_ball + // are "exact"; the other float families are ulp-bounded, with the bound measured on the device by the + // consumer. private static readonly Family[] Families = { new("float cultmath_snoise(vec3)", "ulp-bounded", r => new object[] { Point3(r) }), @@ -122,6 +197,13 @@ public sealed class GlslMirrorTests new("vec2 cultmath_iv_lerp(vec2, vec2, float)", "ulp-bounded", r => new object[] { Interval(r), Interval(r), Uniform(r, 0.0f, 1.0f) }), new("vec2 cultmath_iv_snoise_ball(vec3, float)", "ulp-bounded", r => new object[] { Point3(r), Uniform(r, 0.001f, 2.0f) }), new("vec2 cultmath_iv_fbm_ball(vec3, float, int, float, float)", "ulp-bounded", r => new object[] { Point3(r), Uniform(r, 0.001f, 2.0f), 4, 2.0f, 0.5f }), + new("vec4 cultmath_iv_frustum_ball(vec2, float, float, float, float)", "exact", r => + { + var slope = new float2(Uniform(r, -1.0f, 1.0f), Uniform(r, -1.0f, 1.0f)); + var z0 = Uniform(r, 0.1f, 100.0f); + return new object[] { slope, z0, z0 + Uniform(r, 0.01f, 50.0f), Uniform(r, 0.0001f, 0.05f), Uniform(r, 0.0f, 2.0f) }; + }), + new("vec2 cultmath_iv_exp(vec2)", "ulp-bounded, " + PlatformTolerance, r => new object[] { Interval(r) }), }; private static string GoldenFixture() @@ -141,6 +223,8 @@ public sealed class GlslMirrorTests json.Append(" {\n"); json.Append($" \"glsl\": \"{family.Glsl}\",\n"); json.Append($" \"tolerance\": \"{family.Tolerance}\",\n"); + if (family.Tolerance.Contains(PlatformTolerance)) + json.Append($" \"platform\": \"{RuntimeInformation.OSDescription}; {RuntimeInformation.FrameworkDescription}\",\n"); json.Append(" \"cases\": [\n"); for (var p = 0; p < Points; p++) { diff --git a/packages/cultmath/tests/CultMath.Tests/HlslSourceCompatibilityTests.cs b/packages/cultmath/tests/CultMath.Tests/HlslSourceCompatibilityTests.cs index 5ebf9525..ce38dea4 100644 --- a/packages/cultmath/tests/CultMath.Tests/HlslSourceCompatibilityTests.cs +++ b/packages/cultmath/tests/CultMath.Tests/HlslSourceCompatibilityTests.cs @@ -315,13 +315,17 @@ public sealed class HlslSourceCompatibilityTests /// /// The text of CultMath.hlsl with each #include "name" replaced in place by the /// included file from the same directory, as the shader compiler would resolve it. Only the - /// package's own includes exist, so a missing file is an error rather than a skip. + /// package's own includes exist, so a missing file is an error rather than a skip. An include for + /// which returns false is dropped with its line instead (the GLSL + /// lowering's separately licensed files); the C# mirror inlines every one. /// - internal static string ReadShaderSource(string cultMathRoot) + internal static string ReadShaderSource(string cultMathRoot, Func? inline = null) { var shaders = Path.Combine(cultMathRoot, "shaders"); - return Regex.Replace(File.ReadAllText(Path.Combine(shaders, "CultMath.hlsl")), @"(?m)^#include ""([^""]+)""[ \t]*\r?$", - match => File.ReadAllText(Path.Combine(shaders, match.Groups[1].Value))); + return Regex.Replace(File.ReadAllText(Path.Combine(shaders, "CultMath.hlsl")), @"(?m)^#include ""([^""]+)""[ \t]*\r?$(\n?)", + match => inline is null || inline(match.Groups[1].Value) + ? File.ReadAllText(Path.Combine(shaders, match.Groups[1].Value)) + match.Groups[2].Value + : string.Empty); } /// The documented HLSL-to-C# transformations, and nothing else. diff --git a/packages/cultmath/tests/CultMath.Tests/fixtures/glsl-parity.json b/packages/cultmath/tests/CultMath.Tests/fixtures/glsl-parity.json index 433a41b6..ddafeb62 100644 --- a/packages/cultmath/tests/CultMath.Tests/fixtures/glsl-parity.json +++ b/packages/cultmath/tests/CultMath.Tests/fixtures/glsl-parity.json @@ -6553,6 +6553,531 @@ "41968490 c1ad3591 c0f22840 3f0bd3ed 00000004 40000000 3f000000 -> bff00000 3ff00000", "419cc42c 41bf0d0c 4103e728 3ec4ecc5 00000004 40000000 3f000000 -> bff00000 3ff00000" ] + }, + { + "glsl": "vec4 cultmath_iv_frustum_ball(vec2, float, float, float, float)", + "tolerance": "exact", + "cases": [ + "3eba7b90 3eb7145c 427992e9 42ba552a 3c779789 3f90a589 -> 41e2a26a 41de7fa1 429b8f4f 419e80d6", + "3f73f64c 3f0c1560 4222d6fe 4297c95b 3d2b135d 3f4911fc -> 425e3d89 41ff38c0 426934da 41f0be8c", + "bf0b3bac bdf4d708 3fcf79e5 40cf912e 3d3114bd 3e3e1d32 -> c00d1a19 bef82057 4081b7d4 40500a91", + "3f3a6c22 3e316390 42b4bb59 42c660ce 3ca20ba7 3fabaad2 -> 428a094c 418358f4 42bd8e14 410d060f", + "be31a3d8 3d899d70 42748f85 42bfc55b 3cfe1a00 3fce5929 -> c159ec11 40a8d228 429d068f 41b20e9c", + "3f4a9c78 3f411d36 4206979d 42890daa 3c1cb84a 3ed166e1 -> 4221bb7f 421a26c5 424c5978 41d73c63", + "bf6a8307 bf066589 428944ec 428eff1a 3b87959a 3f7e8c43 -> c2805ed0 c21322c4 428c2203 4057d762", + "3e53d0f0 bc876000 41751d2c 4233d09e 3c035ac7 3efddf6e -> 40c77b7a befefc0a 41f117e9 417fd2f9", + "beed4c2a beb0f3c0 42239431 428e1c3a 3d333100 3ffeee4d -> c1cf8ad7 c19ac388 425fe652 41b427bb", + "bed8bcb0 3c3e8000 41e413bf 4298518b 3c26e20b 3fc469e0 -> c1b13b43 3f1bc6db 4251567b 41e1777c", + "3f687470 bedf98e0 4177bf60 42360b63 3d258870 3ff533f9 -> 41dd8aa6 c155198b 41f3fb3b 41c88b16", + "bf62de25 3d7a4040 41c9076e 42835586 3b3af21b 3fa07ae9 -> c220ed38 40318369 42359762 41e46bc0", + "be3ee140 3f1d3204 424a7a45 428de3fc 3ceaff9c 3fe71569 -> c1354881 42154ad0 4273211e 417ea87a", + "3d155040 3f242db2 4216395d 425ede18 3caf3282 3f15b6a9 -> 3fd99b9b 41ef459f 423a8bba 41491a58", + "be5186a0 bed461aa 3eb176b7 418ad2a2 3ca8146d 3faf1262 -> bfe7c7e9 c06af099 410d987d 41317a97", + "3f571914 bf527068 4068a7bb 42464d73 3babcf63 3fcc9d21 -> 41b2d633 c1aef697 41d4d7ef 42153b67", + "bea098aa bf7e7b00 4288d592 429c4d84 3cdbcdc2 3fa0cc15 -> c1b7e4bd c291b2d4 4292918b 41262439", + "3f7ea95c bdadf0f8 41da358f 4284b466 3c2226dc 3e0a9e8e -> 423a4728 c07e7771 423b41ca 41e3353b", + "bca1cf40 3f438e26 42ba9a04 42e0c8f8 3cb28a08 3f434e6d -> c0020326 429d206a 42cdb17e 41739f77", + "be5c8f00 3e3a3608 42ab0a9e 430099fa 3c27696a 3f83e9c8 -> c1b87a7e 419bbfec 42d61f49 41c5cbfd", + "3f692462 3ede1ca8 41439ca8 422240ba 3d367174 3ff75747 -> 41c04d51 4137343b 41d327e4 41beeaf2", + "3e3b5de8 bcc485a0 428cd979 42d37f82 3b5555d3 3f91e546 -> 4180f120 c0073e06 42b02c7e 419b8e6a", + "bf0ff773 beb4875a 41e48a22 4283d273 3c964fe0 3ff81378 -> c1d486fc c1854029 423cf4fc 41cc7754", + "bf759152 3e6e00c0 411b7809 4261d395 3d2e1d59 3f86f841 -> c1fde838 40f615e2 420458cc 4211296a", + "3f007a50 3d9d4c50 4282ea47 42c450a8 3cd80964 3f831c17 -> 422439d0 40c910b1 42a39d78 41af98b7", + "3f636f84 bd16e440 42a01495 42b0f436 3c452c66 3fd19c95 -> 4295b6cf c046a7a8 42a88466 41058ed4", + "3c5cb300 be0de0f4 4262f0a1 429011d6 3c1cf805 3fb554e5 -> 3f5e06cc c10ebb65 4280c513 411d49fc", + "be8b8962 3f1140b4 42a3c75f 42c8185d 3c06d0b1 3e63702a -> c1c655c1 424e75b1 42b5efde 413c603a", + "bf4e97c7 3e94f9d8 41b4cb94 4285cd78 3d2c585b 3f96285a -> c210746b 415055d0 4233005d 4204b12a", + "be25d7fc bf757789 42ad6f1f 42e8db86 3d272309 3f34556b -> c1839a72 c2c2c993 42cb2552 41d16b3d", + "bec0915a 3f600790 42c04fbd 42d38210 3d1c330e 3f94e70e -> c217e150 42b0b1c0 42c9e8e6 413d3025", + "3f0a1670 bf664628 4108b80d 41fede26 3c45ec62 3fef5ae7 -> 412e59b0 c1915f6c 41a19d16 41992df4", + "be486fe8 3edbaa80 422ab7b0 4278545e 3cea40d8 3e078533 -> c1240c4c 41b3c934 42518607 414a2433", + "3ed58464 be65965c 421673f1 42818d9f 3ce4afc5 3fe18959 -> 41aacbf8 c137a6d2 424cc798 4194c7c0", + "beda2f86 bf623449 42802552 429586c3 3d199c25 3edfd7ad -> c1eca815 c2755aa0 428ad60a 412ba98b", + "3f674cd8 3f533df0 427626cd 42908fcf 3d4b8e48 3f013f1d -> 4271d09c 425cd846 4285d19b 41495e4f", + "3efcbb28 bf27f8d8 400fbbc4 4161b481 3cf35cdc 3e66b496 -> 40812616 c0ababf2 4102d1b9 41051545", + "bf726bfc 3e85765c 422265b0 425c792a 3cc69f42 3e81bc02 -> c2354815 41479acc 423f6f6d 413c3ed0", + "bf2de8d0 bf7015c3 42340781 42aa1aec 3d356d0f 3f5b0cde -> c230b549 c273f2de 42820f56 420d08c3", + "3f7412de 3f099c24 42184c5f 42287042 3d395b8a 3fb43acb -> 4218e5b6 41ac6892 42205e50 40c9c110", + "3f61efa0 3de59850 41f11e40 4292e041 3cd31933 3f27ae99 -> 4236d3e6 40b9c9de 424f27d1 41fc315b", + "beb8555c bf20097a 42902d49 42a1926c 3d03734f 3f4c5975 -> c1dc27bc c23f2322 4298dfda 410c0d6b", + "bf691e1b bd69e000 429ef423 42b94118 3cd404b3 3fed5642 -> c29cb877 c09d3ad1 42ac1a9e 41527036", + "bd47db10 3f7e4e1c 4265e4eb 42a6530b 3b34e8d7 3fb7ed3c -> c05b95b7 428bb463 428ca2c0 419e41b1", + "3f09c3fc bf32be2e 4032a729 414285bd 3c443705 3ee68bed -> 4080b78c c0a700bc 40ef2f87 40dad7f2", + "3f3e9f40 3eaa481c 41d63aab 422aa228 3c737515 3d98b369 -> 41ced0e7 4138bf66 420adfbf 412f4330", + "3f1fb802 bf2dc82b 413ae66e 41795cfe 3d46b83f 3f17c941 -> 410817ba c1141357 415a21b6 4080218a", + "be405494 3f791cc8 421c39c0 42265819 3d1af6d1 3f5410aa -> c0f257f4 421cf210 422148ec 4085d752", + "3e8877d8 3edafaec 41ca49f0 423fc5a2 3cf8189f 3fe87ee7 -> 411c25c2 417a8ee2 4212754d 417f43ec", + "bf3d965a be144368 42494b41 429ee0fa 3ba1a96f 3f3a1e60 -> c24032cc c1164e27 4281c34d 419adaa1", + "becc395a bf3bfe4c 42bde57c 42ef73e5 3c781d3b 3ff98f12 -> c22b41b4 c29da564 42d6acb0 419f44e5", + "bf3c61c8 3ee8b028 418d320e 425e105f 3cb92445 3f364f56 -> c1d75c30 41850173 421254b3 41d7fe89", + "3f1f518a be517dec 402f6f49 418520cc 3bb331ca 3e08bdb7 -> 40c0ff0b bffdc68e 411b0eb5 4108873c", + "beaf9b28 3edfeb30 421e2147 428c5912 3ce33d4f 3f862e96 -> c196823f 41bfeaa9 425b69b6 41a42f23", + "3f7230fe be255068 40c7692d 41c1736c 3c1b21c0 3fde40b6 -> 41662e07 c01d1d82 41734db7 41667a4c", + "3d8d7cd0 bf0a62ac 40cee4ba 42305acc 3d1f49ef 3fd6618e -> 3fdf8631 c15a9f99 41ca3763 41c68156", + "be9ca544 3f523dac 428913c2 42b299b0 3d3c1bd5 3d6d10c0 -> c1c1297c 4281a02a 429dd6b9 418fb2e1", + "bf05e0c4 3f408168 41d2f10d 428e5765 3d03997b 3ed42d63 -> c1cc08fa 4212b15a 424313a8 4204514f", + "3f437482 3f3426f8 42004794 42577efe 3c511304 3f41a29a -> 42033c5b 41f1ebf3 422be349 41894800", + "3f7005ae 3ec05170 4267bdef 429e6929 3cd6611c 3fe1d7a1 -> 428094da 41ce0d59 42892410 41979fcc", + "3f53b14c 3f0be0ca 4257fad5 428ac0d7 3c37bddf 3ee28024 -> 424c09b3 4206d1f7 4276be42 4140c8a2", + "bed4a09a bf61eb11 42784d7c 428f119a 3d3ea961 3ec9afe4 -> c1ddf24d c26bd1e4 42859c2c 41254876", + "be7248cc 3f53a884 42a01b57 42c4b295 3c99cc55 3fad8b25 -> c1a8d810 4293804c 42b266f6 417447e1", + "bef485c8 bf5894a5 429b8756 42f0f2e8 3cc3db31 3f2bf933 -> c23d59da c2a7b6ad 42c63d1f 42054dae", + "3dc53f20 bf4d87cc 42c333f6 4303e17f 3d20930f 3e3756fa -> 4130d09b c2b83d9c 42e57b7a 41db217d", + "bef74dce 3f57eea6 421b85ce 429e0d80 3ccde299 3e949ec7 -> c1e3cdbf 4246e7db 426bd067 41f22740", + "bcacdba0 3f5caf3a 4282b80a 4286e9b2 3cf797dd 3f954671 -> bfb35c8e 4264fccf 4284d0de 4092de01", + "3e31d288 3ec55ca4 42b838f1 42c48bbe 3b3809b0 3f2e3fbd -> 41843e91 4212c693 42be6258 4089a73e", + "3e1f74b8 3e52f818 41fb696e 4298f538 3c07c796 3f6410d6 -> 41066c52 4131d96d 4257cf94 41c65214", + "bf4a5ad3 bf61a4da 41ed6d75 421f7761 3d1b9c7c 3e7e4ada -> c1dbe314 c1f531a9 420b170e 411a852d", + "bf027502 3e5acab0 41dcaf58 4258884a 3d3e9953 3c94d55b -> c1a69322 410baec2 42236ffb 418d9f16", + "3f1e1676 3e216db8 4224eeaa 4293fe26 3d3385fa 3f62ec26 -> 420e50ba 411152a8 4266757b 41bc71dd", + "bf736034 bf0fa2e2 423d88ab 42646dd4 3c5c35b7 3fc8cd4c -> c246ad06 c1ea82a0 4250fb40 41194085", + "be2e61d0 be1966c8 4090d234 42113fd8 3bb35275 3eb8f1cc -> c05e8b55 c043c4e3 41a35a1e 4186db1f", + "3f0d4866 be91f542 42979500 42f5dbcc 3d0f1823 3f627d75 -> 425b5786 c1e299a7 42c6b866 4203b3be", + "3f32b786 bedb4b24 418a02c0 422ed72c 3d233756 3fff31a2 -> 41aa3b61 c150e1d2 41f3d88c 41a6afc1", + "bec4a242 bf6357ae 42987da8 42f3b000 3c814be6 3f736e2b -> c2182708 c2afea16 42c616d4 420a648b", + "3f5b6fd6 3f586726 4291640e 42e86e34 3d338823 3ff1d23f -> 42a1edf6 429fb0d2 42bce921 42242731", + "3f71bb6a 3f5227fa 429e6e2b 42d19c23 3d219f6b 3f2b21d0 -> 42adc392 429710f6 42b80527 41ca62c6", + "bf571be5 3e6056a0 4247ddfd 425acec2 3d1ab4de 3f92b3dc -> c22fe65a 4137726a 42515660 40cb2247", + "befe8cbe 3f3b65d2 42b02b2b 43088db7 3d252d55 3f972a0b -> c25f5d85 42a470a1 42e0a34c 421c31af", + "bee9f5ee bc10fec0 4229656f 42845ab2 3d3591a6 3ed87261 -> c1c65db9 bef5df13 42590d6a 4183a56d", + "bf74a6a3 3bbe4800 4132b52b 421e76e1 3d10edb7 3ebd7165 -> c1c222b5 3e16fdfb 41cb242c 41ab91ae", + "bdbcb4e8 3f4fa734 4240a4c3 42b1a182 3d46cb93 3fc9a29c -> c0c9f0b3 425e3723 4288f9f2 420081b3", + "3e9a07a4 bea68b80 42470a0d 42b74464 3cb8733e 3ebf7eae -> 41aa259a c1b7f8b3 428d64b5 41cab2ce", + "bb97c700 bf12b63d 41f16809 421e914a 3d076a8a 3f65854d -> be2592f8 c1a00c54 420ba2a7 40f532d3", + "3e41ffb8 3f6b1d62 413bd8e1 4194788f 3cf59dda 3f42f7ea -> 4037b046 415e9e89 41726500 40bfbca1", + "3d31c600 bc0daf80 40209f69 41fc56df 3cc8ee2d 3f125a99 -> 3f3d2c8a be16c594 41883566 417e03d9", + "bf0d995f 3cb47540 42b4834e 42d3f16b 3d03572d 3fd96e5a -> c2591369 400a52e8 42c43a5c 41613f5a", + "bf50e9b9 bdfc7750 42b31956 42bc6cfe 3c833420 3f700442 -> c295f658 c13539c2 42b7c32a 40af09a3", + "bf502f2f be070110 40976663 4184461b 3d48761c 3fac4249 -> c10a591d bfb36ed9 412a1fb4 411cd1fc", + "3f5214c8 3f2a7fda 42a88172 42eac3b2 3d4782eb 3fbf740f -> 42a577ba 42864aa8 42c9a292 41fa8548", + "bf5ceff2 bebaa414 41f5ef3f 420f0fcf 3c6fd64a 3f732bfa -> c1e597bb c141f3e7 420503b7 409d4b51", + "3f06eb94 bf5c3ca2 42a99885 42bebc80 3cfbcfe3 3ff02e01 -> 423de816 c29aff34 42b42a82 41450d63", + "bedca1a2 bf2209ae 426550b5 429fd72a 3ba232e4 3ee3808a -> c1ec92e5 c22dbf13 42893fc2 4171126d", + "3dd8a5c0 3b56ea00 42801f2f 42c0e718 3b1289d0 3ea8b8d9 -> 4107d693 3e86c058 42a08324 4186a5d7", + "bea988de bedb58cc 424ef45d 428a9d1f 3d09673f 3fa81207 -> c1a052d1 c1cf6de9 4272174e 415a092f", + "bde784e0 3df98600 4256f0e3 4275e52f 3c4a7fb0 3f254dc3 -> c0d0622e 40e096b8 42666b09 40aa7e82", + "3dae8d40 3f7bbcc0 416c94c3 420f41ee 3c878a8c 3eff1685 -> 400a01c4 41c70857 41ca671f 417dd268", + "3e77b7a8 3f43a826 42a40d21 42aee283 3d42dc23 3fcb95b2 -> 41a3fc2f 42818595 42a977d2 41138c90", + "bf4cc979 bda99760 42933734 42d13e49 3bb68839 3f71e998 -> c28e9314 c0ec2456 42b23abe 41ab6b73", + "bf48e6a4 be22514c 41dc85bc 420aafde 3d1cf156 3c36242d -> c1c35dec c09dd8aa 41f8f2bc 40bc8638", + "3f6a846e be33a5fc 422013ff 42465eec 3d1d55a9 3dd96f64 -> 42242f3c c0fb8ac4 42333976 4108e86c", + "be4a1c98 bf3d78dc 42bb9479 42d4985e 3d3a38c0 3e5819f8 -> c19df80a c2941704 42c8166c 414ebf0b", + "bf5984a7 bf3c2fe6 418a515e 4213694a 3abc478f 3ea484ed -> c1b803f5 c19f33b8 41d891f9 417163ad", + "be9f0948 bf0c24c6 42060978 428b3068 3c4f048a 3ed8bd2e -> c1801a7b c1e1c55d 424e3524 41b4f9ac", + "3f576772 3f258474 4234bc5c 4241a76d 3b203676 3fd32f32 -> 421d828b 41f21005 423b31e4 4083ee46", + "bf677596 be4b73e0 41b546a7 41edece4 3cbdd74a 3fead377 -> c1bd8208 c0a693cf 41d199c6 40eb27a4", + "3f32f38e be101180 4138693a 424782ac 3d10e1bc 3fd2493a -> 41abb0d2 c08a3917 41f59cfa 41d7ab9c", + "3f36ff22 3f333640 425bfa20 42ad0686 3cdf9f14 3ffa2888 -> 424a4e9b 42461f7b 428d81cb 41d4df09", + "becc5fc4 3eddabb8 421dd3a0 4234b70b 3d235cf7 3f8a215e -> c1872293 41929266 42294556 40c671b7", + "3eaf3d20 bf06f7bb 41818740 41ccc480 3c5edce2 3ff1ca69 -> 40e4d59a c1303f26 41a725e0 40f953ce", + "3e507960 bea7016a 4106300f 4160a610 3c26d80f 3f822982 -> 40121c0e c06a17aa 41336b10 408608bb", + "3f4267b6 3f5e0efe 4286131d 429f914d 3cde39ad 3f98dd6c -> 425efd8e 427eb5c6 4292d235 41515ce0", + "bf684c68 3e1f20e0 424ff7a2 429b4b09 3d310d0c 3f4d684b -> c26b4598 41212a4d 4281a36d 41acc146", + "3ea60f1c bed69d58 41df2a6d 424e6d06 3d1c8fba 3f2938d3 -> 414e4838 c1854ca9 421f011e 418068cf", + "be0d5b44 3f5e0986 427183ee 42bd638c 3cc113b8 3ee76fd4 -> c12b413b 42868009 429b12c2 41cc2802", + "3f032efa 3ea574e4 417fd8e6 41c3f0ce 3c08b29f 3f3d6832 -> 4125f5a6 40d1517b 41a1eea0 40bd4456", + "3ef433fc 3efa6288 4281a92d 4285c8df 3d19d31d 3f9c804c -> 41fb4e32 4200d563 4283b906 409f7882", + "bec57db6 bf68a26d 418c8a3d 4265a500 3b64d728 3ee0d619 -> c1675e70 c208453b 4215f50f 41e511e4", + "bf75b100 bf2f08fe 42151d13 42968cd4 3cd7be4c 3ffaecc2 -> c2580ad5 c219e99d 42611b5e 42053838", + "3eaa3fe8 bf3ea02e 426556ce 42bdeb19 3d0bc9a0 3f096c57 -> 41ca8ffa c262ce48 42984b40 41e06b41", + "3f4f633e bf1cf456 423991e3 428932fa 3ccc9f37 3fe14abf -> 423a4fdc c20d0105 4265fbec 419a6b26", + "bdfdaf88 3f40b98c 42253c54 42432b8c 3d1eb6ab 3e752f0c -> c0b292e3 4207a990 423433f0 40dac5e6", + "3f01a7e8 3e780e20 42581c66 42aec09d 3c0a2600 3fb7e5ef -> 420f3bb4 418903fa 428d6768 41aa5dff", + "bf304d18 3e895da4 42ad2a68 42d38f7c 3d088ea1 3f1fd6ca -> c28479d4 41ce7035 42c05cf2 4180b244", + "3f352cac 3f7e1f50 415aadc0 41b4f5df 3cceb02c 3d99a7d4 -> 414d72ec 419015d4 41912660 40f686f2", + "3f18125a 3f78da5c 40cb4e94 41cab204 3be1a410 3f8ea160 -> 41169989 417671bb 417d85a9 417ad4f9", + "bf34409a 3f39e924 425b02b3 428affeb 3c9379eb 3ec4277f -> c22ef99a 423477bb 42788144 4141f5b3", + "3f1d37a2 3f3dc374 4219b180 424c35f4 39e26e74 3fe79703 -> 41dbccb9 4204a691 4232f3ba 41298ae9", + "bdf44b38 3f7fabae 42c02677 430ace14 3cf4c524 3dc3aee5 -> c16023c4 42ea93f3 42eae150 420a2ecb", + "be473f94 3d2de880 42531f52 42716594 3cf21237 3f809221 -> c13019d9 4019b465 42624273 40d4afc9", + "3efa1934 bef69a6c 42155ecc 42606496 3c90e8c0 3fc9d999 -> 41b692cd c1b4059b 423ae1b1 415f1119", + "bf62c0d3 be815848 424241e0 42c36550 3cd4f595 3f1b943d -> c2818d6d c193cc87 42924320 4212309d", + "bf284e22 3f38c27c 41dce214 42754831 3cec966d 3fcba4cc -> c1e9de21 42005db1 4231dc9e 41d7573b", + "3f5b6d56 3e4fb4c0 42c5f03b 42dd11ea 3d175bb0 3f811735 -> 42b392e5 41a9fb49 42d18112 414ccb26", + "bf58ad78 3f62885e 421bf330 421ef427 3d4810ce 3f9d6120 -> c205444a 420b53fd 421d73ac 4070e914", + "bf70bbde 3f7e54fc 41daed7a 41ecf107 3ce1e14f 3e4bf610 -> c1d65791 41e2730c 41e3ef40 403b1dc1", + "3f23344c be0b1b3c 424f735c 42a33f5a 3c2151ff 3fa209f3 -> 422a331a c111119e 42857c84 419ea8c5", + "be24d2f0 bdad4c98 42405e06 42a1e00b 3d0c1142 3fbc6520 -> c1262647 c0aeb162 42810787 41a77489", + "3f0b775a 3eea0f40 42bce7a8 43030c31 3c37a7c5 3ffd1f13 -> 4275b349 424e2c73 42e18005 41cf32a4", + "3ccd9680 3cd63780 4250733d 42bf14de 3c756c9c 3f51f0d3 -> 3fed2775 3ff71ba1 4293a73e 41bfd66d", + "be4e69c0 bf6a578c 421efb33 42a6879d 3d4329c3 3f85aff2 -> c1465dd2 c26134e6 42760536 420b584a", + "bf702991 bf3f43e2 41f8c92b 421f956a 3d2f3da4 3e9427ca -> c205343e c1d42ad3 420dfd00 410dd7b6", + "bee40158 beaa91d8 4222ade1 426a0c65 3d264d31 3f0fca3f -> c1b0abfe c1842adb 42465d23 41525397", + "bf73c865 be23eeb4 41cfeb1f 426aa46e 3c47d659 3efe38a3 -> c221389d c0d8d3c6 42294cff 41bf5d06", + "3eae619c becee1c8 429adde8 42b74c81 3cda6951 3d9990a6 -> 41e659c3 c208a430 42a91534 4128ee29", + "3e21ad98 3f668342 41579c48 41971e48 3c21d25c 3e9cafb4 -> 40238643 4169252b 41817636 4085220b", + "bf071c06 3dc95610 424a2252 42c4e2da 3d005723 3f8e8dff -> c21d404c 40ea54c2 4294fa02 41fb184b", + "be1851a8 3e45d968 42aeaa15 42fea5a0 3cba9d5a 3fe3a2ff -> c17f7043 41a5e576 42d6a7da 41ca158d", + "3e294510 bf3036f4 428c589f 42a4578d 3d2d0da9 3fe0d3ca -> 41497680 c251ba86 42985816 41494082", + "bf32bf6e 3efbc45c 42a003cb 42aee3fb 3c253572 3fa7a677 -> c269d797 4224af10 42a773e3 40e2a1d4", + "3efe0ff4 3efcad90 426dd177 4292342a 3cf4cc3a 3fe7b877 -> 42038d99 4202d618 42848e73 41452db9", + "bf7a7965 bf295a1c 415d613e 41d3fc5a 3bc87a44 3f0664d9 -> c19ddaf7 c15575c4 41a1567c 4127c14f", + "3f0d4fda bf5e5f50 429ed973 42e515ee 3c3bd066 3fa55483 -> 425623d9 c2a87d02 42c1f7b0 41de6b15", + "be7fdc74 3f3db342 42a8b8f1 43009b84 3d0fcb74 3f40bffd -> c1d4da6a 429dd035 42d4f7fc 4205690d", + "bea7e01c bea4b8f4 412a4d12 41bb793d 3d440e6f 3f666e84 -> c0b2c6e4 c0af6b52 41884fe3 4110f3b4", + "be966274 bf532fa0 420be4c9 429af20a 3c2d6339 3fe1107f -> c1841c56 c239861d 4260e46e 41f696d2", + "3f6ce8ea bf31c0ff 42c7f3ce 431460da 3d21ac1b 3f120fce -> 42e5d59f c2ac71fb 42f85ac1 422dbef9", + "3f00df9c beae8788 4067a1c9 41cc9399 3cbe6a10 3fc76c5f -> 40eb1fc9 c09f35f3 416987d2 416ff8af", + "bf54e4f9 bdd29d80 424f7372 426e4ba1 3d1e826f 3f28bc74 -> c2395876 c0b75c7e 425edf8a 40ffd630", + "3f1f7382 3f39f60e 4093ca91 4178292a 3d3fdee4 3f03f214 -> 40c89849 40e9f1fc 41210739 410c77f1", + "3f19671c 3f02a0e6 41aa3dbc 425e9ee4 3d3859cc 3d877a71 -> 41b8685c 419d07c8 4219dee1 41c38c6a", + "3f53a9cc 3f5a2264 42ab2654 430351e3 3d17930b 3e196331 -> 42b354a6 42b8d030 42d8e50d 42220f3e", + "3e95908c be3e6674 4103e4b1 4210a074 3bdd11b8 3f3e7228 -> 40cf8563 c0841718 41b199a0 417c1f53", + "bea292c2 3f4b52a8 42af1761 42cfc26a 3c0dd300 3f823647 -> c1f3214d 42980929 42bf6ce6 414a9edb", + "3f5d2424 bf1a1148 41fbc156 4201d229 3b048cc6 3f3198e2 -> 41dce17a c199e2d6 41ffb2d4 3fbcd2fc", + "3f2af89c 3efaeba4 41ad7e3e 426b2afe 3c0b556a 3d8ab058 -> 41d6fe16 419dc378 4220f50e 41c54612", + "bf5a7d38 bf5f1844 422c1293 422c8af9 3cd7a64a 3f89aad0 -> c2130f5d c21628f4 422c4ec6 401372e8", + "3f58c6fe bf295088 428ddb47 42ba3150 3cd07a34 3f3ba454 -> 428ae4d0 c258f783 42a4064c 419af92c", + "bf00ffdc befcac32 42920e49 42d5638c 3d338b8b 3da24fd9 -> c2352029 c23162ea 42b3b8ea 41cad7b7", + "be70e5b4 bd5c9f80 4269a1fe 42bb28dc 3d1ad4bb 3fb36bc5 -> c18f057f c082fc0f 4297fcee 41b83d3c", + "3ee05348 3f49ab34 42b1868e 42e468ae 3b87a5ff 3ffe9ac3 -> 4231dab2 429fe428 42caf79e 419cb4b3", + "3f37b66e 3f4ed99c 41bf7f1e 41d2a285 3cc70b15 3e0d2a4d -> 41904a4e 41a27673 41c910d2 40227bec", + "3f627c2e bea4c6e2 4214e1b3 42586c08 3ca59ed6 3b8d9b84 -> 422197ea c16b2199 4236a6de 414b1864", + "3f196b94 bcb9ae00 428a47c7 42e9088e 3d1e1759 3fc90806 -> 425e870b c006a8b7 42b9a82a 4206c21a", + "3e6f5490 bf2dbda0 42adbf21 42bf689c 3c93d98f 3fb202ae -> 41aab05b c277d255 42b693de 4108dac1", + "bdcc0168 be853b62 41f78cd5 4286c48b 3c6913c3 3fa1b424 -> c09cb6c2 c14cb189 4244a7c0 41a916b9", + "3e99da60 be2ec77c 410ed285 42329b06 3cefb9ab 3f9a9f16 -> 4100cc6b c0925123 41d64fa7 41ab4f60", + "bf3e86ac bf196622 4299b68f 42dbd3f6 3d15cd09 3f898ff5 -> c28b00a8 c25fd4d0 42bac542 41dfa502", + "3eac15a0 bf690f4e 40e2b494 4210a7e8 3d391f41 3f08a12b -> 40e8932e c19d7df5 41acfe7a 41b36e8a", + "bf7aff66 3f0b0850 4145434b 418b9917 3d36b33b 3feb984a -> c1699307 410161a0 416e3abc 40cee096", + "bf3f83d5 3ebabc18 4259e958 42a34a5c 3cf50cc6 3e6b9217 -> c24bab3a 41c695d4 42881f84 41a2c319", + "3f053692 bf12a241 40b63931 422ac5c0 3d0ce444 3ffd0dfa -> 41496eeb c15dba0f 41c18ce6 41d6b2c2", + "3f53deb4 bf545fde 42b4dd8d 42bd9810 3d27efb7 3f92324b -> 42994c9b c299aa10 42b93ace 41064047", + "bf775306 3f695bf8 422c20e6 426e67b0 3cd0a526 3f213766 -> c2464f76 423b1ce6 424d444b 417ec08e", + "3e8072d4 3f3cc4d2 423aae25 424318d0 3d135c9a 3f82f40e -> 413f8eb9 420cc1d0 423ee37a 4083913c", + "3c977140 3dc4a660 42514dff 4272f32e 3cf16286 3fe6ee8d -> 3f85c541 40adb3e9 42622096 40fa3a5b", + "bf617357 3e2d4ec8 427154d5 42ba00e8 3d388b6f 3f846e27 -> c287095f 414f9c26 429955a9 41d9532a", + "bd18c3a0 3f5b33ee 420f400c 421f9233 3d08e15e 3fd18a61 -> bfb4b43d 4201a5a5 42176920 40b50751", + "be8cac80 3f5867d6 42be6415 42f58879 3b8ebd6f 3fefa3e1 -> c1ef8b1b 42b84034 42d9f647 41a6cfb2", + "bc9af820 3f16e2ba 418bd2eb 41e7aa1a 3d3c00b5 3fba90d0 -> bee0e144 415af443 41b9be82 41173533", + "3e986e38 3edf8778 42460dae 42997b76 3c3e3b3b 3e8ea7cb -> 41965a15 41dc7b20 427c824d 41848a50", + "bd8c7390 3efba588 4246e2b4 42475b34 3ac08d91 3ef0a89e -> c05a7da1 41c3bc1a 42471ef4 3f1be83c", + "3f6fb12a be8ba324 40b2a4c5 424cd2b1 3d2e1ecf 3f948cd2 -> 41d4aee2 c0f7ce2e 41e3274a 420ccd43", + "3f6a2f64 3db44b70 41a16ef4 425242b7 3d0b3ede 3fcbdb00 -> 42051747 404ceda8 42117d18 41cafa67", + "3df43e70 3f6a7362 42394d59 4297540e 3d0ae34a 3fcb3647 -> 40e8c67b 425f7125 4273faba 41c0f94b", + "3ee99940 3f29128c 42856d7b 42dcd89a 39a8e24c 3ebafdbe -> 4221a2ee 4269f9ee 42b1230a 41e36792", + "bf74639c 3f5fafbe 428b8d18 42a16246 3cfc828a 3fec04c5 -> c28fa49f 4283798d 429677af 41542173", + "beb67832 be8ee122 42b31132 42fac2fd 3cef369b 3fb8be7e -> c2192f55 c1efe5b3 42d6ea18 41c63b6b", + "3f432e66 3e1ff0e0 42bcf8ad 42f858f8 3c600f47 3f3f62cb -> 42a6b639 41889c98 42daa8d2 41aa0bb1", + "3d6391a0 bf7f618e 407fe383 421bab2d 3d49b348 3f0ba7f7 -> 3f9898db c1ab3f26 41aba965 41d92420", + "3eb4d508 3f420ee8 40dd5182 42284269 3c61acce 3ec9f321 -> 410a6566 419484b4 41c3ec99 41bf11f1", + "3e0bb358 bf4fe0a3 420fee13 429f31de 3d364503 3fe9baf7 -> 40fc4a4e c23bb4eb 426728e8 420677c9", + "3f44010e 3ef4f694 42b9acf7 42d59835 3c860463 3f2339a8 -> 4298d94d 423f0741 42c7a296 413c9db0", + "3f4d5a46 3f2f7bce 42162435 42952e9a 3d109a2f 3e4b9de0 -> 4233e2e4 4219b8af 426040b4 41ee228d", + "3f32af80 bf3b2a36 425f715a 428d0936 3c8e69a3 3bd47f6c -> 42306c17 c238cb4e 427cc1e3 413a7274", + "bf367bc6 beb3db08 42be6b94 42e8e685 3cde349d 3fbc97c3 -> c296e084 c214b44c 42d3a90c 4191943f", + "3f351a78 3e737e68 410bc39f 422e8e58 3bf66112 3f7a77a1 -> 419434a1 40c7433b 41d17f40 41b8aa11", + "be8cd402 bf7b6c19 4289ba60 428ced3a 3d11b3d4 3f8867b8 -> c1994a6d c288d5ff 428b53cd 4096dd79", + "bddf41a0 bf00a5e2 42872368 42cf2a88 3d235b84 3e9180d9 -> c11542d2 c22c04c6 42ab26f8 41c54f29", + "3f4bcb94 3f5efe1e 418b7eeb 4283c160 3d2ef13e 3e8eefd0 -> 4204a642 4211251d 4226a11b 4222399a", + "3e8e637c bee62fb4 42959286 42b92dee 3d15588f 3f90be93 -> 41ba30d7 c2167fa4 42a7603a 41693759", + "3f6c026a bdab7c80 419ec1f8 41eb77fc 3caeaf54 3fb4fa94 -> 41b5b892 c0040a38 41c51cfa 410932f0", + "bf090815 bf1807e2 428adfda 42e36e1e 3c9b943d 3fb2502c -> c244134a c2598993 42b726fc 41ff2e89", + "be36147c 3f22685e 4229aa09 422ec50b 3d2da1f2 3e47fa55 -> c0f4fa88 41da82b6 422c378a 4033f41c", + "beb04164 be1cf30c 425309a6 42ba84c6 3cd81a81 3f9b25bd -> c1c91131 c1330b1b 429204cc 41ca7fb9", + "3e015378 3f4512dc 42990ac0 42f7ba71 3b4760af 3fdf9b82 -> 414a7609 429a42a2 42c86298 42009606", + "3ef761f0 bf7ca7fe 41e0c7b6 4209025e 3b6e3514 3f016556 -> 4171010b c1f62449 41f96639 40a6796c", + "bea8c484 befc0758 42a4b77b 4302c794 3cc83f75 3f981bc2 -> c20c82cb c251d4e2 42d52352 42021a28", + "bdd4e1b0 bf799930 425ef466 429f3044 3ce4cd55 3d966382 -> c0e113c9 c283f2e3 4287553c 41980349", + "3edd2fc8 3e68e568 422b4ee7 4296a852 3ac4ac73 3f1c5306 -> 41cc2cf9 4156fc0f 426c4fc6 419676b7", + "bf6c65b3 bf32b0ae 42ba4f75 42f57a84 3d00dcce 3f8a58d3 -> c2c75cdd c296b237 42d7e4fc 41dc8ed4", + "3f624a5c bedba3d2 4167eea2 425e1c30 3beea70e 3f3e7e65 -> 41f79668 c1704fa3 420c0bec 41ef485f", + "be2b7528 bed8e920 429552e9 42a66d1e 3d0d77f3 3f97f848 -> c15379c5 c205c4c8 429de004 410c27f2", + "bee72e0a be8fea76 42bb1d07 430c346b 3c94c541 3f839f1e -> c25318ef c20369fb 42e9c2ee 41eff0b0", + "bf66dc3d bf53e573 420325b0 428a67aa 3c3e5f4e 3f63f710 -> c237f28d c228d64c 424bfa82 41f3cb95", + "bdde21f8 bf51171d 42c3b27e 43129879 3b8da9ea 3feb7ab3 -> c1541b0d c2c7a6e9 42f471b8 420837f2", + "beb70c1c 3ef96f0c 427a5053 4299785b 3aba11cd 3ecf35f6 -> c1c739ec 4207bd7f 428b5042 410c8b89", + "bdcc8170 3e827c54 42adbca8 42c03de6 3b4d254d 3f0a0149 -> c1122e5a 41ba8afa 42b6fd47 40b45e04", + "bed6ae6c 3db8e9c0 42b8bae9 430611ed 3c9a6899 3fab29f6 -> c23de363 41238ed0 42e26f62 41d46eb9", + "be854e4a bf4a1108 429ddace 42c7999c 3cde951d 3f8ad607 -> c1ba22cc c28d12d2 42b2ba35 418aef9e", + "bf12d47c bf129400 40b1ee60 42220661 3c9a0b00 3dd65745 -> c1535fa7 c15302d2 41b8442d 41badb30", + "be59ebb4 bdf03f38 41a377f5 42770bd2 3d2e1010 3ff2518f -> c10bf009 c09a4640 422463e6 41ce43e0", + "3f6fbad6 3f7b8508 420578c2 428c0e56 3cc635a8 3fbc0891 -> 4241a62d 424b2c37 424ecab7 4208439a", + "3e951670 3f3a7a70 4028e196 40b0fd27 3cb3d3ef 3faf8a5c -> 3f983fe8 403e6eeb 4082b6f9 40552b32", + "3d3185a0 3e64c430 428f3c5f 42ae3c36 3cf82dd1 3ffab316 -> 405c2614 418dd958 429ebc4a 4148bc27", + "bf3d0a9f 3f4eccc8 41a18289 41c2d0de 3b6ab514 3fd00f17 -> c183902e 418fec13 41b229b4 40998f95", + "bf34612c 3f009f30 41caacca 42844b79 3d222462 3f9e8fc1 -> c200eae4 41b7da37 4236f6ac 41f6c0f6", + "bf5e3503 bd942bb0 428b2ab7 42ecde78 3d316137 3f44da12 -> c2a332eb c0d9a57c 42bc0498 421928b9", + "bea85958 3e8bf354 427922db 42820aed 3d1e2583 3fada402 -> c1a76f77 418b30e7 427e9c5a 40ab6191", + "3d0a91c0 bf305956 429b34bd 42c01d2c 3c127ff5 3fc35c69 -> 403bffe5 c26f4184 42ada8f4 41598122", + "bea77dc2 bf552054 42a7475d 42bdb7fc 3d0e9d0e 3ff219f9 -> c1e991f2 c2949ac5 42b27fac 414b8a1a", + "3efb5c1c 3f45312c 42b84ff0 42ec599d 3cf02406 3fbb4e29 -> 424e84cc 42a203a3 42d254c6 41b4615b", + "bd1432a0 3f28a31c 40ec522d 41dd8a66 3d4b1806 3ff95d84 -> bf227362 4138db1b 418c4f79 4177c56f", + "3f7af83c 3db6e620 429538fa 42b660b8 3d4789ee 3ff7ec0c -> 42a28ad1 40ece95a 42a5ccd9 4190158b", + "be929a90 bf58f981 4154d95b 421dcad3 3ce841d3 3fea08e3 -> c0f1ac1f c1b2d6a2 41d3012a 41a3e5b0", + "bf6d1840 3f0eb766 4265c805 42a3d8fe 3cc4f2af 3fb52249 -> c28113b0 421b6486 428b5e80 41ab4450", + "3f10f92a 3f2efadc 4299a986 42e4a639 3c3ef190 3ffb0f59 -> 425880f6 4282a86b 42bf27e0 41e2e2bb", + "bd979428 3f2170ec 40a6ef64 42470814 3ceae80f 3ff13f6a -> c00233ed 418aaca5 41dbe600 41ed89fb", + "3e57c750 bf473a54 4183cd3a 426240d9 3c9ba809 3fa1edc2 -> 40f6409a c1e35d3e 421213bb 41e0b2f6", + "bf77285e bf7d6e99 41309743 4259b5bd 3d31b344 3f9e9930 -> c1fcd020 c2019d8e 4202edc7 42227e76", + "3f20891a bf0dfefe 41ffb971 42015bdc 3cc66812 3fce847b -> 41a14d1e c18eac5f 42009c4a 402901ac", + "bf2f4a96 3f7c215a 420c1793 425530f5 3cff70e8 3e997e01 -> c1f1e783 422df8b6 4230a444 4181da37", + "bf1686a4 bf04f04c 41fb087a 428b474f 3d31e84b 3ea2c24d -> c1ed9792 c1d1d503 424a096e 41dd4108", + "3f559eca bd8b2348 4249a0d0 426f9e4e 3d3bed26 3f691ae3 -> 4238199b c06fd215 425c9f8f 411da3b1" + ] + }, + { + "glsl": "vec2 cultmath_iv_exp(vec2)", + "tolerance": "ulp-bounded, platform exp", + "platform": "Ubuntu 24.04.5 LTS; .NET 10.0.12", + "cases": [ + "41202b2a 4120314e -> 46ade79e 46ae2a6b", + "c0d1a016 bfb9db96 -> 3abb4cbc 3e6fb77f", + "bf8fdd04 bf8cd5fc -> 3ea6664d 3eaa61db", + "40024c0a 40b9ecd3 -> 40f51721 43a6d60d", + "4197c9d9 4197cafb -> 4d25c5b1 4d25dd2e", + "418b9b87 418b9bc1 -> 4c10a577 4c10a991", + "40ceb567 40cf50f9 -> 441fb557 4422c533", + "c0d71f2c c0d71180 -> 3a9dbd63 3a9e00d8", + "41149716 4115dd36 -> 4628ae10 4636a8d3", + "406d8085 406d9a7b -> 422392dc 4223d546", + "bef5bff4 bef5b08c -> 3f1e6964 3f1e6e2a", + "40750628 40750e38 -> 4237f95f 42381090", + "c0e728c3 c0e719d5 -> 3a3f1fca 3a3f790c", + "c18aec70 c13b5136 -> 32f6c219 370a21a5", + "41902341 4190380f -> 4c7ed262 4c80b63b", + "c176d7d2 c174bd5e -> 3456290a 34743f6f", + "c10d5076 c0d80228 -> 39190e2c 3a996ddd", + "c11b8847 c11b8125 -> 387bc1f8 387c324f", + "c06ec60d c06eb253 -> 3cc46278 3cc49f0b", + "bebf1b28 3fff14ca -> 3f3040e5 40eac236", + "41284f27 412dd379 -> 4710a018 474c2b74", + "c14fa678 c14fa5ac -> 361b0a4e 361b1209", + "c0fad4ed c0fab9bb -> 39ceb51b 39cf6513", + "c17de632 c17db6c8 -> 3409c9b1 340b6464", + "414f6c8c 41531eac -> 48d06242 490343fa", + "40e7d966 413623fd -> 44af2f87 47aba695", + "4070804e 40800e69 -> 422b6be7 425ac6f1", + "41939c17 41977345 -> 4cc4a616 4d1ee90f", + "40b28176 40b2921a -> 43844fa5 43849487", + "409c38a3 409c42f5 -> 4303e1fe 43040c90", + "c0d1e402 c0cc5be6 -> 3ab9c0d3 3adcce50", + "41947227 4195082d -> 4cda50ce 4ceae88e", + "c19594e2 c1958d68 -> 32023b0e 3202b4fe", + "40f50c60 410a2aac -> 4504502d 45afdbe3", + "c140d47e c140d36a -> 36c3be27 36c3cb5a", + "4157dcae 41587a42 -> 49308d18 493779c5", + "c18e5e55 c18b41ad -> 32a06b8f 32ecb2ea", + "bfbee7f0 bf3151e0 -> 3e66724b 3f00101e", + "41583ab2 415903ee -> 4934a680 493dbf51", + "4155faca 4155fb46 -> 491cf494 491cf956", + "c1642b98 c15d8292 -> 352bff46 3582660f", + "c170f311 c170e907 -> 349ac48d 349b25c9", + "40d7b019 40d7b79f -> 44537137 4453a2f5", + "c197fc9c c196e2aa -> 31c0d460 31dd4a53", + "417d6191 417e0607 -> 4ae63cd8 4aefab93", + "408b17d2 408b1c2e -> 429a709b 429a85a9", + "4070bc94 4070f07c -> 422c0da3 422c9969", + "3fbb7abf 3fbb87c1 -> 408a7002 408a7e16", + "411a81e4 411c27dc -> 46742941 468753f5", + "c1bc5882 c167813d -> 2e8314cf 350ba3e2", + "4080a820 4080c3b0 -> 425eebc5 425fac1c", + "c13b3160 c132dfce -> 370b358e 376a223e", + "41565c23 4156610d -> 4920baf8 4920ec5f", + "4154b6dc 417c728c -> 49110553 4ad92fd3", + "c13cf6ed c13cf369 -> 36f93c12 36f972de", + "417e9504 417e9654 -> 4af82f45 4af843a3", + "c03dd70d c036ea23 -> 3d52ed7e 3d6b086f", + "c189a001 c188ae87 -> 33111f87 332348b9", + "3f52dca6 3f8c44ed -> 4011d8fd 403f7897", + "417629fd 4187c3a2 -> 4a92a631 4bb2ef53", + "c1091e4a c1091d4e -> 3946f2f5 3946ff35", + "417f141f 41818095 -> 4b0000e3 4b2399cf", + "bf91ec53 bf59891a -> 3ea3be49 3edae43d", + "4184d364 4189dc9c -> 4b77dc1a 4be8937a", + "c14bf8de c14be10a -> 36431c63 36443fd0", + "c193ea76 c193e75e -> 32206043 32209e56", + "be900eda 3fad7536 -> 3f41378f 407825b7", + "c08794db c025ce1a -> 3c6cc82d 3d99886a", + "3ec7f803 3ecc0d7d -> 3fbd2902 3fbeacd1", + "c012cb8e c0119f12 -> 3dcea215 3dd27533", + "c0daaa3f c0daa5c1 -> 3a8d34be 3a8d4894", + "c050634f c039c7b1 -> 3d1ddc02 3d60bdf3", + "409cd424 409dee6c -> 43066900 430b1f58", + "408cef3e 415094cd -> 42a3968b 48e003a2", + "c14e4c28 c10ce1c8 -> 3628b807 391d3f60", + "40752252 4075256e -> 42384a67 4238535d", + "40fd9a10 40fda0f0 -> 452cdb86 452d00af", + "41036130 41040d3c -> 456620f1 45700037", + "c077e68c c074d5f4 -> 3caa48ab 3cb2a306", + "c1147257 c114703b -> 38c40310 38c41ceb", + "c1048df6 c1048c9c -> 39844f07 39845a37", + "40dfedd6 410d03dd -> 4488c689 45d22042", + "c1624386 c15f0030 -> 3541c354 356d98cb", + "405e12af 40636bf1 -> 4200886a 420bbc84", + "c14500ef c144fb45 -> 3696ccaf 3697021d", + "c17d28a2 c17d267e -> 34105085 341063d7", + "c123eec5 c123a46f -> 3814ecdd 3817a718", + "3eddffe7 3ede4019 -> 3fc57a2b 3fc592f1", + "40c3acf4 40c3b46c -> 43e24452 43e2792a", + "c1a9f6d8 c165a819 -> 30230d14 351cbd2c", + "4184c5e2 41aaf14e -> 4b763af9 4ee31c73", + "3f9ec35a 3f9f0406 -> 405d3bd5 405dabba", + "40c71490 4165a95c -> 43fbab24 49d1201c", + "3fc1015d 3fc19163 -> 40908b7c 40912e7d", + "c17ee168 c151c9c2 -> 34019755 3607a61b", + "c0c3c1df 3fa3ddfc -> 3b107367 40663bd7", + "c17826ef c17819e9 -> 34455621 3445f704", + "c159be3f c159b8bf -> 35a50371 35a53c36", + "c0020079 bffad0ae -> 3e0650d0 3e104fe8", + "bf44e41c 40b3c1d4 -> 3eed4658 43899686", + "41864aa6 41864b9a -> 4b94d9e9 4b94eba8", + "413cd099 413cd103 -> 48023ffc 4802435d", + "414b49e2 414b9486 -> 48a0ebfd 48a3e196", + "414771e6 414778e2 -> 487d1ce3 487d8b7a", + "40dd96fa 40dfcc76 -> 447e4461 4488382e", + "416ded4c 416df1fc -> 4a2f472a 4a2f7a8d", + "bf429f16 bf41ceea -> 3eef6343 3ef0263e", + "c154a284 c1548980 -> 35e3142d 35e4784d", + "40394b3b 403a4ab5 -> 4190b309 4192f92f", + "c181b997 c181b831 -> 33c2cb42 33c2ed54", + "c1708f7c c1708764 -> 349e939c 349ee3ea", + "c006ab5a c0067636 -> 3df9bcce 3dfa8c83", + "4138ad4a 4138af22 -> 47c922dd 47c93a0e", + "c1aaddba c18114f2 -> 3011aaed 33d319ca", + "c1052891 c1051da3 -> 397ed0c3 397f7f11", + "40f07cfb 40f07f15 -> 44e57ad1 44e589e6", + "40a83277 40a834e1 -> 433fbe6b 433fcce4", + "c1970cb1 c18a3aaf -> 31d8cba2 330690ee", + "c1664f64 c16597a8 -> 35167759 351d5e8f", + "c016aa8e c01673a2 -> 3dc28157 3dc3288b", + "c152f8e3 c152ed29 -> 35fbf1e6 35fcaad3", + "40c4c97f 40c707f1 -> 43ea43a1 43fb47f7", + "c19a2cb6 c19a29e4 -> 3192b092 3192e454", + "bf5cb30b 3e91dd16 -> 3ed833f5 3faa30d9", + "c173577e c162d986 -> 3485462a 353acbab", + "40c3d3a0 40c3d490 -> 43e35669 43e35d14", + "bf70eaa9 bf6e8997 -> 3ec7c912 3ec9a69e", + "4156037d 41593933 -> 491d4a01 49403b2d", + "40edd117 40eebf99 -> 44d3199b 44d95626", + "c13a6ff3 c13611a9 -> 3711f0d7 373fc165", + "c0886ac7 c004b4a2 -> 3c66adc4 3e00c274", + "41915f3c 41917138 -> 4c94aa97 4c95fa47", + "416c02e9 417a49a7 -> 4a1b802a 4abdc33b", + "404d6730 404df460 -> 41c61e4c 41c7d540", + "c11e8199 c11e6ff3 -> 38510e1b 3851f534", + "c1357761 c13576b7 -> 37471dab 374725f1", + "419b9eea 41a2b6d6 -> 4d85d234 4e22655d", + "4199121e 41a4bcae -> 4d42976e 4e511dca", + "403ceedf 40ad4180 -> 41992a72 43609544", + "c10ac2eb c109be27 -> 39338851 393f5577", + "40b06b2d 40b0756b -> 4377ea0c 43783974", + "4146c423 414736b5 -> 4872998b 48797b3f", + "40a579c5 40ad2ef3 -> 43301c69 43601338", + "40867c7c 40868924 -> 4285bb91 4285f082", + "c11b8dde c11b6a62 -> 387b6a13 387d9a15", + "c15e5062 c15b8992 -> 3578044a 3593823d", + "c0f20ac4 c0df35c8 -> 3a080645 3a7504c4", + "4175e861 4175f5af -> 4a9051a2 4a90c9d8", + "c0d0b202 c0d0a082 -> 3ac0d2ae 3ac13c40", + "40f23807 40f24359 -> 44f23b80 44f29142", + "40bcb7fe 40bcbac2 -> 43b60e43 43b61e01", + "3fec5a99 403a96c4 -> 40cace61 4193a841", + "4058f90a 406037f6 -> 41ed6007 4204ea3f", + "c19c9154 c19a637e -> 315987b7 318ed16a", + "c083c2e8 c083c20c -> 3c85670b 3c856aa2", + "c1927ebc c189bbdc -> 323f8b7c 330f29a6", + "c13bc94e c138e5ba -> 370623e5 3720af66", + "c1903287 c1903073 -> 327f45d2 327f882c", + "c18dcc20 c18da0d0 -> 32ac4a91 32aff94d", + "41241856 41241c9a -> 46de4628 46de8174", + "c149a219 c1498cf3 -> 3661d2b7 3662fdfc", + "c17daadb c15be833 -> 340bcc6f 359023d4", + "c1526edf c1515197 -> 36024a19 360baffd", + "410f5073 410f5735 -> 45f2990a 45f2ff98", + "418081a1 4182dfcb -> 4b107375 4b4234b8", + "be2f2f61 be2a219f -> 3f57bec7 3f58d00a", + "c147088e c14707c6 -> 3684d52f 3684dbae", + "418415d1 41847393 -> 4b61f244 4b6c87da", + "40cca9b9 40ccaa97 -> 4415d180 4415d592", + "418103b8 418103fc -> 4b19ec9e 4b19f1bc", + "40915d28 4106f73c -> 42bbdf9c 458ff907", + "415a09c0 415a1148 -> 494a45ac 494aa4fb", + "c0d79718 c0d76aa4 -> 3a9b728e 3a9c4b17", + "c022fa3b c0152575 -> 3da077df 3dc72e06", + "4100d622 4104fde6 -> 45444f18 457e8659", + "c1356667 c110c6bd -> 3747f160 38f68d2d", + "3ebbca75 3ec9858b -> 3fb8b6cf 3fbdbc1c", + "c19237e7 c192310d -> 32464918 3246f335", + "419707fd 4199690f -> 4d16cce4 4d4b07b9", + "40ab2960 40ab2a60 -> 43525b19 435261ad", + "4171dd3d 417d47d3 -> 4a602e46 4ae4cb97", + "4131b591 4136ef6f -> 47822040 47b4644a", + "3e10a062 3f06df68 -> 3f936ad1 3fd8c755", + "409828ee 40982db2 -> 42e85391 42e8762f", + "411800b7 41189eb9 -> 4650c83b 4658fe59", + "3fd45d75 3fd4636b -> 40a8259a 40a82d71", + "400f4bec 402d8b74 -> 411624f8 4170dcbf", + "3fd386e5 3fd3cdbb -> 40a70caa 40a76938", + "bf91ee69 3fa50909 -> 3ea3bb9e 40685834", + "3f29298b 3fff963a -> 3ff7d963 40ebb013", + "c16d1803 c16d1739 -> 34c4f0fc 34c4fab4", + "3fc7105d 3fc73ac3 -> 40978d13 4097bf50", + "c00692a2 bffc4cbd -> 3dfa1d56 3e0ea5e3", + "41500857 41554a31 -> 48d87662 49165507", + "c19c5c71 c19b7aa9 -> 315f3881 31793cc7", + "bf8bdf6c 40b3764b -> 3eabab4a 4388533e", + "c0cf7436 c0737944 -> 3ac87344 3cb67ab6", + "c180611b c152db2e -> 33e679ea 35fdc764", + "c168c603 c168c403 -> 3500fedf 35010f02", + "411b944b 411d77cd -> 46828a0c 4692e51f", + "4111ea58 4111f560 -> 460eb64f 460f18d8", + "418942a2 41894466 -> 4bd7bb33 4bd7ead7", + "c165800d c1657c43 -> 351e4765 351e6ce7", + "c0ff15d8 c0e732e0 -> 39b4fa92 3a3ee36f", + "418b2695 41913acb -> 4c089e3d 4c920b63", + "4141eb39 4141ec7f -> 4833310b 48333f51", + "c1618094 c14fd848 -> 354b353c 36192a8e", + "416d127a 4181d013 -> 4a2628ec 4b2a1365", + "c12c2882 c12b27f0 -> 37b22074 37bda432", + "c132c5f9 c10872dd -> 376b9d70 394f73c3", + "415a1b25 418b5009 -> 494b220b 4c0b695d", + "c1664144 c1663f72 -> 3516fc69 35170d99", + "4193287c 4193c44c -> 4cb9db17 4cc88c31", + "40e35323 40eb070d -> 4498166f 44c17aa3", + "407ae7f7 4083215c -> 4249af1d 4270d60b", + "c16c6d87 c16c5279 -> 34cd4fbb 34ceac0d", + "c1854c1f c14bc377 -> 33794569 3645abde", + "c1878438 c174a2d0 -> 333ce31c 3475d61f", + "c16e15b2 c16dfe3c -> 34b91d2a 34ba2d63", + "c186191d c1861537 -> 33618758 3361f55c", + "c0ee532b c0ee51a1 -> 3a18c798 3a18cef4", + "4125bcb1 4125c2ef -> 46f64bf5 46f6ac20", + "41435826 4143a7e2 -> 4843e39d 4847bd60", + "4141e5b9 4142f0c7 -> 4832f37d 483f01e0", + "419f32eb 419f39c5 -> 4dd14cf3 4dd20083", + "40cb4342 40cdb75e -> 440f676c 441ad4f1", + "4165e3b7 4182eb9d -> 49d4204a 4b43547f", + "41079ed6 411a174a -> 4595fc6e 466de380", + "c16a9ed5 c15ab975 -> 34e5dd5c 359b327a", + "c12f9849 c12e8603 -> 378fb266 3799a604", + "3edb505a 3f136953 -> 3fc471ac 3fe3a921", + "c1506cd1 c15060a7 -> 3613b61c 36142695", + "40b9f7e9 40ba784f -> 43a70fe1 43a9b37f", + "419521ae 41952506 -> 4cedda1d 4cee3d9e", + "41074ff8 410750a8 -> 45932031 45932685", + "40d9f8c3 4115426e -> 4463159f 462fe2c7", + "c0b7ab40 40806080 -> 3b52c17e 425cfafe", + "4005e1fc 40640484 -> 41019b63 420d0b33", + "c1762c19 c1416a91 -> 345f546a 36bcb364", + "c0e1be65 c0e0b257 -> 3a625df5 3a69e58a", + "412e612d 412e62c3 -> 47535b4e 47537044", + "40e4c428 40e4dcb8 -> 449f1864 449f92b3", + "419c93fb 419d0c35 -> 4d96d4f0 4d9ff38c", + "bf4f1957 bf4e8269 -> 3ee3ffea 3ee48680", + "c144d67d c144cba1 -> 36985ed0 3698c65f", + "40fcf00d 40fd1413 -> 45294e9f 452a0da4", + "c170377a c16f9c1a -> 34a2054c 34a848e7", + "4119be6c 411dc410 -> 4668c871 4695a7d5", + "41732663 41737c65 -> 4a72f06f 4a78182a", + "40690b7e 40697ad2 -> 421891cf 42199c1e", + "beb85bd2 be97a02e -> 3f329775 3f3e61d7", + "41325a08 4187bbc4 -> 4787750a 4bb23fb3", + "406ea634 40909ea6 -> 4226883c 42b78e0b", + "c18b47ee c16b52c0 -> 32ebfa26 34dbfc84", + "c11e1519 c0deff46 -> 3856aab4 3a76a784", + "40c786cc 40c7941c -> 43ff33d9 43ff9e1c" + ] } ] }