diff --git a/packages/cultmath/tests/CultMath.Tests/CyclesOracleTests.cs b/packages/cultmath/tests/CultMath.Tests/CyclesOracleTests.cs
index 28074a69..2cafbcf5 100644
--- a/packages/cultmath/tests/CultMath.Tests/CyclesOracleTests.cs
+++ b/packages/cultmath/tests/CultMath.Tests/CyclesOracleTests.cs
@@ -5,11 +5,14 @@ using Xunit;
namespace CultMath.Tests;
///
-/// math.cycles_* against the results of Blender 5.2.2's own Cycles on the CPU. The fixtures under
-/// fixtures/cycles-oracle are written by tools/cycles-oracle/oracle.py (never by hand): each case is the
-/// operands and the node's baked results as float32 bit patterns. Every function is compared at every case
-/// within the tolerance named in ; a function not named there is held to 0 ulp. Signed zeros are not distinguished (-0 equals 0), and NaN equals NaN. The fixtures come from Cycles'
-/// SSE4.2 CPU kernel, which does not fuse multiply-adds as C# does not (oracle.py checks this before it bakes).
+/// math.cycles_* and the shader mirror's cultmath_cycles_* (shaders/CultMath.Cycles.hlsl, compiled as C#) against
+/// the results of Blender 5.2.2's own Cycles on the CPU. The fixtures under fixtures/cycles-oracle are written by
+/// tools/cycles-oracle/oracle.py (never by hand): each case is the operands and the node's baked results as
+/// float32 bit patterns. Every function is compared at every case within the tolerance named in
+/// ; a function not named there is held to 0 ulp. Signed zeros are not distinguished (-0
+/// equals 0), and NaN equals NaN. The fixtures come from Cycles' SSE4.2 CPU kernel, which does not fuse
+/// multiply-adds as C# does not (oracle.py checks this before it bakes). Cycles' film writes 0 for an infinite or
+/// NaN result, so a case whose result here is not finite and whose fixture result is 0 holds nothing to compare.
///
public sealed class CyclesOracleTests
{
@@ -23,6 +26,35 @@ public sealed class CyclesOracleTests
["cycles_logarithm"] = (1, "0 ulp on Linux"),
};
+ private const string CSharp = "C#";
+ private const string Mirror = "HLSL mirror";
+
+ private static readonly Lazy<(object Shader, Type Type)> ShaderMirror = new(() =>
+ {
+ var (assembly, errors) = HlslSourceCompatibilityTests.CompileShaderMirror();
+ Assert.True(assembly is not null, string.Join(Environment.NewLine, errors));
+ var type = assembly!.GetType("CultMathHlsl.HlslShader")!;
+ return (Activator.CreateInstance(type)!, type);
+ });
+
+ /// One implementation of the Cycles functions: CultMath's C# or the HLSL text compiled as C#.
+ private static object Call(string target, string function, params object[] arguments)
+ {
+ if (target == CSharp)
+ return Method(function).Invoke(null, arguments)!;
+ var (shader, type) = ShaderMirror.Value;
+ var method = type.GetMethod("cultmath_" + function, BindingFlags.Instance | BindingFlags.NonPublic)
+ ?? throw new InvalidOperationException("no mirror function cultmath_" + function);
+ return method.Invoke(shader, arguments)!;
+ }
+
+ public static IEnumerable