cultmath: glsl-parity header names the recording image and lavapipe/Mesa; asin/acos/atan2 oracle tolerance capped at 1 ulp
Some checks failed
CultMesh QUIC native / Build win32-x64 (pull_request) Has been skipped
CultMesh QUIC native / Build linux-x64 (pull_request) Has been skipped
CultMesh QUIC native / Release native binaries (pull_request) Has been skipped
CultMesh portability and performance / ubuntu-latest one-command multiplayer onboarding (pull_request) Has been cancelled
CultMesh portability and performance / windows-latest one-command multiplayer onboarding (pull_request) Has been cancelled
CultMesh portability and performance / ubuntu-latest reactive semantics and leases (pull_request) Has been cancelled
CultMesh portability and performance / windows-latest reactive semantics and leases (pull_request) Has been cancelled
CultNet cross-runtime interop / C# / TypeScript / Rust / Python / Kotlin (pull_request) Has been cancelled
CultMesh QUIC native / Verify committed binaries match SHA256SUMS (pull_request) Has been cancelled

The glsl-parity.json header gains recorded_on (generator writes it; goldens untouched). CyclesOracleTests allows
cycles_arcsine, cycles_arccosine and cycles_arctan2 (glsl-parity families 95-97) 1 ulp from Blender, with the measured
cases named; a test pins the cap at 1 ulp, the three names, and that a 2 ulp miss still fails.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Libby 2026-11-04 17:22:13 +00:00
commit 5d8e032d7f
3 changed files with 36 additions and 4 deletions

View file

@ -19,11 +19,23 @@ namespace CultMath.Tests;
/// </summary>
public sealed class CyclesOracleTests
{
// The ulp distance a function is allowed from the reference. Empty: the fixtures are the SSE4.2 kernel's results
// and every function equals them, power and logarithm included (glibc's powf and logf, 0 ulp from MathF on Linux).
// The ulp distance a function is allowed from the reference; a function not named is held to 0. The fixtures are
// the SSE4.2 kernel's results and almost every function equals them, power and logarithm included (glibc's powf and
// logf, 0 ulp from MathF on Linux). asin, acos and atan2 are the exception: the fixtures' glibc and the libm these
// tests run on round them one ulp apart (glsl-parity.json families 95-97 are the same three, platform families
// there too), so each is allowed 1 ulp, never more (TolerancesAreCappedAtOneUlpAndStayOnTheDriftingFunctions).
// An entry needs a measured case on the reference named in it (a failing case's bits and ulp); other builds'
// tolerances belong to the class table in tools/cycles-oracle/README.md, not here.
private static readonly Dictionary<string, (int Ulp, string Measured)> Tolerances = new();
private const int MaxToleratedUlp = 1;
private static readonly Dictionary<string, (int Ulp, string Measured)> Tolerances = new()
{
["cycles_arcsine"] = (1, "case 3f4dd9af -> 3f6f256c, C# 3f6f256d"),
["cycles_arccosine"] = (1, "case 3f027192 -> 3f84a03c, C# 3f84a03b"),
["cycles_arctan2"] = (1, "case 3f000000 bdcccccd -> 3fe2541c, C# 3fe2541d"),
};
private static int Allowed(string function) => Tolerances.TryGetValue(function, out var tolerance) ? tolerance.Ulp : 0;
private const string CSharp = "C#";
private const string Mirror = "HLSL mirror";
@ -120,7 +132,7 @@ public sealed class CyclesOracleTests
var name = (string)entry!["function"]!;
var method = Method(name);
var clamped = (string?)entry["then"] is not null;
var allowed = Tolerances.TryGetValue(name, out var tolerance) ? tolerance.Ulp : 0;
var allowed = Allowed(name);
var worst = 0L;
string? worstCase = null;
var cases = entry["cases"]!.AsArray();
@ -158,6 +170,24 @@ public sealed class CyclesOracleTests
Assert.True(failures.Count == 0, string.Join(Environment.NewLine, failures));
}
[Fact]
public void TolerancesAreCappedAtOneUlpAndStayOnTheDriftingFunctions()
{
Assert.Equal(new[] { "cycles_arccosine", "cycles_arcsine", "cycles_arctan2" }, Tolerances.Keys.Order().ToArray());
Assert.All(Tolerances.Values, t => Assert.InRange(t.Ulp, 1, MaxToleratedUlp));
Assert.All(Tolerances.Values, t => Assert.NotEmpty(t.Measured));
// A result two ulps from the reference is past every allowance, and one ulp is within the drifting functions' only.
foreach (var function in Tolerances.Keys.Append("cycles_power"))
{
var want = 0.7f;
var twoAway = MathF.BitIncrement(MathF.BitIncrement(want));
Assert.Equal(2, Distance(want, twoAway));
Assert.True(Distance(want, twoAway) > Allowed(function), $"{function}: a 2 ulp miss would pass");
Assert.Equal(function != "cycles_power", Distance(want, MathF.BitIncrement(want)) <= Allowed(function));
}
}
private static JsonNode RampEntry(string interpolation) =>
Load("ramp")["functions"]!.AsArray().Single(e => e!["node"]!.ToString() == "ShaderNodeValToRGB " + interpolation)!;

View file

@ -975,6 +975,7 @@ static int4 clamp(int4 x, int4 a, int4 b) { if (a.x > b.x || a.y > b.y || a.z >
var json = new StringBuilder();
json.Append("{\n");
json.Append(" \"generator\": \"GlslMirrorTests.GoldenFixtureMatchesCSharp (packages/cultmath/tests/CultMath.Tests); regenerate with CULTMATH_WRITE_GLSL=1\",\n");
json.Append(" \"recorded_on\": \"platform families: Yggdrasil's mcr.microsoft.com/dotnet/sdk:10.0 image (Debian GNU/Linux 13 trixie, .NET 10.0.14); lavapipe device bounds: fixtures/device-bounds-lavapipe.txt (llvmpipe LLVM 20.1.2, mesa-vulkan-drivers 25.2.8-0ubuntu0.24.04.4, ubuntu:24.04 probe image)\",\n");
json.Append($" \"seed\": {Seed},\n");
json.Append($" \"points\": {Points},\n");
json.Append(" \"encoding\": \"each case is 'arguments -> result', every scalar a float32 or 32-bit integer bit pattern in hex, vectors and structs flattened in field order\",\n");

View file

@ -1,5 +1,6 @@
{
"generator": "GlslMirrorTests.GoldenFixtureMatchesCSharp (packages/cultmath/tests/CultMath.Tests); regenerate with CULTMATH_WRITE_GLSL=1",
"recorded_on": "platform families: Yggdrasil's mcr.microsoft.com/dotnet/sdk:10.0 image (Debian GNU/Linux 13 trixie, .NET 10.0.14); lavapipe device bounds: fixtures/device-bounds-lavapipe.txt (llvmpipe LLVM 20.1.2, mesa-vulkan-drivers 25.2.8-0ubuntu0.24.04.4, ubuntu:24.04 probe image)",
"seed": 20261002,
"points": 256,
"encoding": "each case is 'arguments -> result', every scalar a float32 or 32-bit integer bit pattern in hex, vectors and structs flattened in field order",