2026-09-14 13:57:03 +02:00
|
|
|
using System.Reflection;
|
|
|
|
|
using System.Text.RegularExpressions;
|
|
|
|
|
using Microsoft.CodeAnalysis;
|
|
|
|
|
using Microsoft.CodeAnalysis.CSharp;
|
|
|
|
|
using Xunit;
|
Make CultMath HLSL-shaped: writable components, bool-vector comparisons
HLSL is now CultMath's semantic authority where it defines one.
- float2/3/4, int2, double2/3, bool2 and quaternion are plain structs with
public mutable fields instead of record structs. Indexers and swizzles
(float2.yx; float3 xy xz yx yz zy xyz xzy yzx zyx; float4 xy xyz xyw yzx)
have setters.
- ==, !=, <, >, <=, >= are component-wise and return bool2/3/4. Equals and
GetHashCode stay value-based so vectors remain dictionary keys.
quaternion is not an HLSL type and keeps a bool ==.
- Scalar/vector arithmetic and comparisons get explicit overloads on both
sides instead of leaning on implicit splat.
- New types: bool3, bool4, int3, int4, float2x2, float3x3 (row-major like
HLSL, _mRC fields, Rotate / RotateX/Y/Z / Euler with math.RotationOrder).
- New intrinsics: mul (matrix-vector both sides, matrix-matrix), any, all,
select (HLSL argument order), log, vector pow/sqrt/sin/cos/acos/atan2,
double min/max, int3/int4/bool3/bool4/float2x2/float3x3 constructor
functions.
- simplex_noise is renamed snoise and gains the Ashima 3D variant; the HLSL
mirror follows as cultmath_snoise(float2/float3).
- Random gains NextFloat3() and NextFloat3(float3 max) on the same draw order.
Tests pin snoise(float3), float3x3.Euler and float2x2.Rotate to
Unity.Mathematics reference values, and an HLSL-shaped source block compiles
under using static CultMath.math.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 13:29:59 +02:00
|
|
|
using static CultMath.math;
|
|
|
|
|
|
|
|
|
|
namespace CultMath.Tests;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2026-09-14 13:57:03 +02:00
|
|
|
/// The real shader mirror, <c>shaders/CultMath.hlsl</c>, compiles as C# against CultMath under
|
|
|
|
|
/// <c>using static CultMath.math;</c> after exactly the source transformations listed in
|
|
|
|
|
/// docs/design.md (HLSL Target: Source Transformations). <see cref="TransformHlslToCSharp"/> is
|
|
|
|
|
/// that list in code; if a body needs anything else, this test fails.
|
Make CultMath HLSL-shaped: writable components, bool-vector comparisons
HLSL is now CultMath's semantic authority where it defines one.
- float2/3/4, int2, double2/3, bool2 and quaternion are plain structs with
public mutable fields instead of record structs. Indexers and swizzles
(float2.yx; float3 xy xz yx yz zy xyz xzy yzx zyx; float4 xy xyz xyw yzx)
have setters.
- ==, !=, <, >, <=, >= are component-wise and return bool2/3/4. Equals and
GetHashCode stay value-based so vectors remain dictionary keys.
quaternion is not an HLSL type and keeps a bool ==.
- Scalar/vector arithmetic and comparisons get explicit overloads on both
sides instead of leaning on implicit splat.
- New types: bool3, bool4, int3, int4, float2x2, float3x3 (row-major like
HLSL, _mRC fields, Rotate / RotateX/Y/Z / Euler with math.RotationOrder).
- New intrinsics: mul (matrix-vector both sides, matrix-matrix), any, all,
select (HLSL argument order), log, vector pow/sqrt/sin/cos/acos/atan2,
double min/max, int3/int4/bool3/bool4/float2x2/float3x3 constructor
functions.
- simplex_noise is renamed snoise and gains the Ashima 3D variant; the HLSL
mirror follows as cultmath_snoise(float2/float3).
- Random gains NextFloat3() and NextFloat3(float3 max) on the same draw order.
Tests pin snoise(float3), float3x3.Euler and float2x2.Rotate to
Unity.Mathematics reference values, and an HLSL-shaped source block compiles
under using static CultMath.math.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 13:29:59 +02:00
|
|
|
/// </summary>
|
|
|
|
|
public sealed class HlslSourceCompatibilityTests
|
|
|
|
|
{
|
2026-09-14 13:57:03 +02:00
|
|
|
[Fact]
|
|
|
|
|
public void ShaderMirrorBodiesCompileAsCSharp()
|
|
|
|
|
{
|
|
|
|
|
var (assembly, errors) = CompileShaderMirror();
|
|
|
|
|
Assert.True(assembly is not null, "HLSL mirror did not compile as C#:" + Environment.NewLine + string.Join(Environment.NewLine, errors));
|
|
|
|
|
|
|
|
|
|
var shader = assembly!.GetType("CultMathHlsl.HlslShader")!;
|
|
|
|
|
Assert.NotNull(ShaderFunction(shader, "cultmath_snoise", typeof(float3)));
|
|
|
|
|
Assert.NotNull(ShaderFunction(shader, "cultmath_snoise", typeof(float2)));
|
|
|
|
|
Assert.NotNull(ShaderFunction(shader, "cultmath_value_noise_bicubic", typeof(float2)));
|
Extend the HLSL mirror comparison to one struct return shape
HlslSourceCompatibilityTests previously flattened only bare vector returns to
scalar leaves. Values() now recurses through struct fields of any type, so a
struct return (invariant 8's one shape, e.g. CultCellular) is compared field
by field down to bit-for-bit scalars, exactly like a vector return already
was. Components() stays scoped to scalar-typed fields, since it only ever
builds function arguments and must not recurse.
TransformHlslToCSharp gains transformation 5: struct fields gain `public`,
since HLSL has no field access-modifier concept and a C# struct's fields
default to private, which would hide them from Values()' reflection. The old
wrap-in-a-class transformation is renumbered 6. design.md's transformation
list documents both, matching design.md:92's numbered list.
StructReturnComparisonRejectsAMismatchedField is the extension's own test: two
hand-built structs with the same shape (mirroring a real mirror-vs-C#
comparison) prove the field-by-field, bit-for-bit walk actually rejects a
struct whose leaf field differs, or whose nested vector's own component
differs, not just a length check.
EveryMirrorFunctionMatchesCSharpMath and HlslMirrorTests' required-symbol list
now cover cultmath_smin_grad and cultmath_cellular; the mirrored-function
count moves from 28 to 30.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 19:18:21 +02:00
|
|
|
Assert.NotNull(ShaderFunction(shader, "cultmath_smin_grad", typeof(float4), typeof(float4), typeof(float)));
|
|
|
|
|
Assert.NotNull(ShaderFunction(shader, "cultmath_cellular", typeof(float3)));
|
2026-09-26 02:54:17 +02:00
|
|
|
Assert.NotNull(ShaderFunction(shader, "cultmath_snoise_grad", typeof(float3)));
|
|
|
|
|
Assert.NotNull(ShaderFunction(shader, "cultmath_fbm_grad", typeof(float3), typeof(int), typeof(float), typeof(float)));
|
|
|
|
|
Assert.NotNull(ShaderFunction(shader, "cultmath_ridged_grad", typeof(float3), typeof(int), typeof(float), typeof(float)));
|
CultMath: phacelle, Johansen's Phacelle noise on 3D cells with an exact gradient
Cut 2a-ii-b. phacelle(float3 p, float3 side, float offset, float normalization)
returns CultPhasor { float4 cos; float4 sin }, each float4(gradient.xyz, value.w).
- 4x4x4 cells, pcg3d-hashed jitter in [-0.5, 0.5), weight max(0, exp(-2 d^2) - 0.01111),
normalization as upstream (lpmitchell/AdvancedTerrainErosion PhacelleNoise). The
caller supplies the stripe wave vector; offset is in cycles.
- The gradient is exact: weight derivatives and the normalization are included, not
upstream's grad(cos) ~ -sin * side.
- Cells whose per-axis lower bound is at least 2.25 have weight exactly 0 and are
skipped. An internal overload switches the prune off so the test can pin bit
equality without a second copy of the loop.
- cultmath_phacelle and CultPhasor mirror it in both tracked CultMath.hlsl copies
(the same blob). The mirror test compares the struct field by field; it now covers
34 functions.
- PhacelleTests: value against an independent double-precision reference, quarter-cycle
phase rotation, gradient against central differences on both sides of the
normalization threshold (excluding the threshold and each cell's weight-support
edge, where the exact gradient is discontinuous), stripes along the flow on the unit
sphere (0.215 against the 0.25 bound), step-difference continuity across cell planes,
and pruned == unpruned bit for bit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 23:51:02 +02:00
|
|
|
Assert.NotNull(ShaderFunction(shader, "cultmath_phacelle", typeof(float3), typeof(float3), typeof(float), typeof(float)));
|
2026-09-14 13:57:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// HLSL functions carry no access modifier, so they compile as private instance methods.
|
|
|
|
|
private static MethodInfo? ShaderFunction(Type shader, string name, params Type[] parameters) =>
|
|
|
|
|
shader.GetMethod(name, BindingFlags.Instance | BindingFlags.NonPublic, null, parameters, null);
|
|
|
|
|
|
Compare every HLSL mirror function against C# math, bit for bit
Only cultmath_snoise was compared, at four inputs, so a flipped cultmath_step,
a changed hash constant, and a snoise2 tie change all passed. The mirror is
now discovered by reflection: every cultmath_* overload with a math
counterpart (26 functions) runs over 104 deterministic cases - each special
value in every argument (0, -0, +-1, ties, 1e-7, +-1e4, 1e7, NaN), special
pairs, seeded randoms, and vectors with x == y (snoise's x0.x == x0.y) - and
must match exactly. Both sides are float32 C# over the same intrinsics, so no
tolerance. A cultmath_* function with no counterpart must be named in
MirrorOnly. The four-input snoise theory is gone.
Mutations: flipped step args fail (349 mismatches); hash constants changed
by more than one float32 ulp fail (43758.55: 467, 127.2: 289, 74.8: 97).
43758.5453 -> .5454 is the same float32 and cannot fail. snoise2 x0.x > x0.y
-> >= is invisible: over 1,829,415 exact ties the result is bit-identical,
because the off-simplex corner's kernel is <= 0 on the shared edge.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 14:17:11 +02:00
|
|
|
// Mirror internals with no public C# math counterpart. Any other cultmath_* function must match one.
|
|
|
|
|
private static readonly string[] MirrorOnly = { "cultmath_snoise_mod289", "cultmath_snoise_permute" };
|
|
|
|
|
|
|
|
|
|
private static readonly float[] Specials = { 0.0f, -0.0f, 1.0f, -1.0f, 0.5f, -2.75f, 3.0f, 1.0e-7f, 1.0e4f, -1.0e4f, 1.0e7f, float.NaN };
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2026-09-14 14:32:43 +02:00
|
|
|
/// Every mirror function with a C# math counterpart returns the same bits (any NaN equals any NaN;
|
|
|
|
|
/// -0 differs from 0) over random inputs, every special value in every argument (ties, zeros,
|
|
|
|
|
/// negatives, NaN, large values, integer lattice points), special pairs, and vectors whose first two
|
|
|
|
|
/// components tie (x0.x == x0.y in snoise). This proves the text of CultMath.hlsl computes the same
|
|
|
|
|
/// float32 results as C# math on the CPU. It does not prove GPU agreement: driver sin precision alone
|
2026-09-14 14:47:10 +02:00
|
|
|
/// makes cultmath_hash and value noise differ bit for bit on hardware. The mirror's intrinsics are C#
|
|
|
|
|
/// math itself (it compiles under using static CultMath.math), so this proves the composition in the
|
|
|
|
|
/// text, not the intrinsic rules; HlslSemanticsTests alone pins those.
|
Compare every HLSL mirror function against C# math, bit for bit
Only cultmath_snoise was compared, at four inputs, so a flipped cultmath_step,
a changed hash constant, and a snoise2 tie change all passed. The mirror is
now discovered by reflection: every cultmath_* overload with a math
counterpart (26 functions) runs over 104 deterministic cases - each special
value in every argument (0, -0, +-1, ties, 1e-7, +-1e4, 1e7, NaN), special
pairs, seeded randoms, and vectors with x == y (snoise's x0.x == x0.y) - and
must match exactly. Both sides are float32 C# over the same intrinsics, so no
tolerance. A cultmath_* function with no counterpart must be named in
MirrorOnly. The four-input snoise theory is gone.
Mutations: flipped step args fail (349 mismatches); hash constants changed
by more than one float32 ulp fail (43758.55: 467, 127.2: 289, 74.8: 97).
43758.5453 -> .5454 is the same float32 and cannot fail. snoise2 x0.x > x0.y
-> >= is invisible: over 1,829,415 exact ties the result is bit-identical,
because the off-simplex corner's kernel is <= 0 on the shared edge.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 14:17:11 +02:00
|
|
|
/// </summary>
|
|
|
|
|
[Fact]
|
|
|
|
|
public void EveryMirrorFunctionMatchesCSharpMath()
|
2026-09-14 13:57:03 +02:00
|
|
|
{
|
|
|
|
|
var (assembly, errors) = CompileShaderMirror();
|
|
|
|
|
Assert.True(assembly is not null, string.Join(Environment.NewLine, errors));
|
|
|
|
|
var shaderType = assembly!.GetType("CultMathHlsl.HlslShader")!;
|
|
|
|
|
var shader = Activator.CreateInstance(shaderType);
|
|
|
|
|
|
Compare every HLSL mirror function against C# math, bit for bit
Only cultmath_snoise was compared, at four inputs, so a flipped cultmath_step,
a changed hash constant, and a snoise2 tie change all passed. The mirror is
now discovered by reflection: every cultmath_* overload with a math
counterpart (26 functions) runs over 104 deterministic cases - each special
value in every argument (0, -0, +-1, ties, 1e-7, +-1e4, 1e7, NaN), special
pairs, seeded randoms, and vectors with x == y (snoise's x0.x == x0.y) - and
must match exactly. Both sides are float32 C# over the same intrinsics, so no
tolerance. A cultmath_* function with no counterpart must be named in
MirrorOnly. The four-input snoise theory is gone.
Mutations: flipped step args fail (349 mismatches); hash constants changed
by more than one float32 ulp fail (43758.55: 467, 127.2: 289, 74.8: 97).
43758.5453 -> .5454 is the same float32 and cannot fail. snoise2 x0.x > x0.y
-> >= is invisible: over 1,829,415 exact ties the result is bit-identical,
because the off-simplex corner's kernel is <= 0 on the shared edge.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 14:17:11 +02:00
|
|
|
var random = new System.Random(0x5EED);
|
|
|
|
|
float Next() => random.NextSingle() * 200.0f - 100.0f;
|
|
|
|
|
var cases = new List<Func<int, float>>();
|
|
|
|
|
foreach (var s in Specials) cases.Add(_ => s);
|
|
|
|
|
for (var k = 0; k < Specials.Length; k++) { var o = k; cases.Add(i => Specials[(i + o + 1) % Specials.Length]); }
|
|
|
|
|
for (var k = 0; k < 64; k++) { var values = Enumerable.Range(0, 32).Select(_ => Next()).ToArray(); cases.Add(i => values[i]); }
|
|
|
|
|
for (var k = 0; k < 16; k++) { var values = Enumerable.Range(0, 32).Select(_ => Next()).ToArray(); cases.Add(i => values[i & ~1]); }
|
|
|
|
|
|
tests: pin cellular's HLSL mirror against radius-2, 2^24, and tie inputs
EveryMirrorFunctionMatchesCSharpMath's generic random/special-value cases
never happen to hit a point where a radius-2 cell wins (about 3.5e-5 of
points) or an integer point at or past 2^23, so an HLSL mutant of cellular's
search radius, its lower-bound prune, or the removed F2=0 guard could change
cultmath_cellular's real output and still pass this test.
Adds three targeted point cases, reusing CellularAndSminGradTests' own
fixtures so the mirror test does not carry a second copy of the construction:
- the six points from AxisAlignedRadiusTwoOffsets/FindPointRealizingOffset,
each of which realizes a different axis-aligned radius-2 offset as F1 or
F2 in the independent 7x7x7 oracle;
- four integer points at 2^24 (both signs), where distinct cells' jittered
features can round onto the same float32 value, reaching the F2 = 0 guard;
- one exact F1 = F2 tie point, the midpoint of two adjacent cells' feature
points (bit-exact-equidistant from both by construction, no search
needed).
Verified against four HLSL mutants applied to shaders/CultMath.hlsl on
scratch copies (not committed) and reverted after each check:
- `dz < 2` instead of `dz <= 2`: killed by the axis-slice point at
(-56.671272, -67.01973, -79.001).
- `dx` starting at -1 instead of -2: killed by the axis-slice point at
(62, -77.91331, -79.42451).
- pruning against f1 instead of f2: killed by an axis-slice point and both
of the 2^24 integer points (3 mismatches).
- the F2 = 0 guard removed (`grad2 = (p - c2) / f2`, unconditional): killed
by the (2^24, 2^24, 2^24) integer point (NaN vs the guarded zero).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 23:14:10 +02:00
|
|
|
// Points aimed squarely at cellular's HLSL mirror (F6/F3 Soul findings): the generic random
|
|
|
|
|
// and special-value cases above pass ~92 inputs through cultmath_cellular and never happen to
|
|
|
|
|
// hit a point where a radius-2 cell wins (about 3.5e-5 of points, Soul measured) or an
|
|
|
|
|
// integer point at or past 2^23, so an HLSL mutant of the search radius, the prune, or the
|
|
|
|
|
// removed F2=0 guard can survive this test even though it changes cellular's real output.
|
|
|
|
|
void AddPointCase(float3 point)
|
|
|
|
|
{
|
|
|
|
|
var values = new[] { point.x, point.y, point.z };
|
|
|
|
|
cases.Add(i => i < values.Length ? values[i] : 0.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The six constructed points from CellularAndSminGradTests.SearchReachesEveryAxisAlignedRadiusTwoSlice:
|
|
|
|
|
// each one realizes a different axis-aligned radius-2 offset as F1 or F2, which is exactly
|
|
|
|
|
// the case an `if (lowerBound >= f2)` prune written as `>= f1`, or a search radius narrowed on
|
|
|
|
|
// one axis (`dz < 2`, `dx` starting at -1), changes.
|
|
|
|
|
foreach (var offset in CellularAndSminGradTests.AxisAlignedRadiusTwoOffsets)
|
|
|
|
|
AddPointCase(CellularAndSminGradTests.FindPointRealizingOffset(offset));
|
|
|
|
|
|
|
|
|
|
// Integer points at 2^24, several, both signs: past |p| ~= 2^23 distinct cells' jittered
|
|
|
|
|
// features can round onto the same float32 value (design.md, "Precision domain"), making
|
|
|
|
|
// F2 = 0 reachable and exercising the grad2 = f2 > 0 ? ... : zero guard this test would
|
|
|
|
|
// otherwise never touch.
|
|
|
|
|
const float twoTo24 = 16777216.0f;
|
|
|
|
|
AddPointCase(new float3(twoTo24, twoTo24, twoTo24));
|
|
|
|
|
AddPointCase(new float3(-twoTo24, -twoTo24, -twoTo24));
|
|
|
|
|
AddPointCase(new float3(twoTo24 + 3.0f, -twoTo24 + 5.0f, twoTo24 - 7.0f));
|
|
|
|
|
AddPointCase(new float3(-twoTo24 + 11.0f, twoTo24 - 13.0f, -twoTo24 + 17.0f));
|
|
|
|
|
|
|
|
|
|
// One exact F1 = F2 tie point: the midpoint of two adjacent cells' feature points is
|
|
|
|
|
// bit-exact-equidistant from both by construction (negating a vector does not change its
|
|
|
|
|
// length, and p - a = -(p - b) here by symmetry), without needing to search for one.
|
|
|
|
|
var tieMidpoint = (CellularAndSminGradTests.FeaturePoint(new float3(0.0f, 0.0f, 0.0f))
|
|
|
|
|
+ CellularAndSminGradTests.FeaturePoint(new float3(1.0f, 0.0f, 0.0f))) * 0.5f;
|
|
|
|
|
AddPointCase(tieMidpoint);
|
|
|
|
|
|
Compare every HLSL mirror function against C# math, bit for bit
Only cultmath_snoise was compared, at four inputs, so a flipped cultmath_step,
a changed hash constant, and a snoise2 tie change all passed. The mirror is
now discovered by reflection: every cultmath_* overload with a math
counterpart (26 functions) runs over 104 deterministic cases - each special
value in every argument (0, -0, +-1, ties, 1e-7, +-1e4, 1e7, NaN), special
pairs, seeded randoms, and vectors with x == y (snoise's x0.x == x0.y) - and
must match exactly. Both sides are float32 C# over the same intrinsics, so no
tolerance. A cultmath_* function with no counterpart must be named in
MirrorOnly. The four-input snoise theory is gone.
Mutations: flipped step args fail (349 mismatches); hash constants changed
by more than one float32 ulp fail (43758.55: 467, 127.2: 289, 74.8: 97).
43758.5453 -> .5454 is the same float32 and cannot fail. snoise2 x0.x > x0.y
-> >= is invisible: over 1,829,415 exact ties the result is bit-identical,
because the off-simplex corner's kernel is <= 0 on the shared edge.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 14:17:11 +02:00
|
|
|
var mismatches = new List<string>();
|
|
|
|
|
var compared = new HashSet<string>();
|
|
|
|
|
foreach (var mirror in shaderType.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic).Where(m => m.Name.StartsWith("cultmath_")))
|
|
|
|
|
{
|
|
|
|
|
var types = mirror.GetParameters().Select(p => p.ParameterType).ToArray();
|
|
|
|
|
var counterpart = typeof(math).GetMethod(mirror.Name["cultmath_".Length..], BindingFlags.Public | BindingFlags.Static, null, types, null);
|
|
|
|
|
if (counterpart is null)
|
|
|
|
|
{
|
|
|
|
|
Assert.Contains(mirror.Name, MirrorOnly);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2026-09-14 13:57:03 +02:00
|
|
|
|
Compare every HLSL mirror function against C# math, bit for bit
Only cultmath_snoise was compared, at four inputs, so a flipped cultmath_step,
a changed hash constant, and a snoise2 tie change all passed. The mirror is
now discovered by reflection: every cultmath_* overload with a math
counterpart (26 functions) runs over 104 deterministic cases - each special
value in every argument (0, -0, +-1, ties, 1e-7, +-1e4, 1e7, NaN), special
pairs, seeded randoms, and vectors with x == y (snoise's x0.x == x0.y) - and
must match exactly. Both sides are float32 C# over the same intrinsics, so no
tolerance. A cultmath_* function with no counterpart must be named in
MirrorOnly. The four-input snoise theory is gone.
Mutations: flipped step args fail (349 mismatches); hash constants changed
by more than one float32 ulp fail (43758.55: 467, 127.2: 289, 74.8: 97).
43758.5453 -> .5454 is the same float32 and cannot fail. snoise2 x0.x > x0.y
-> >= is invisible: over 1,829,415 exact ties the result is bit-identical,
because the off-simplex corner's kernel is <= 0 on the shared edge.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 14:17:11 +02:00
|
|
|
compared.Add(mirror.Name);
|
|
|
|
|
foreach (var input in cases)
|
|
|
|
|
{
|
|
|
|
|
var slot = 0;
|
Add PCG integer hashes to CultMath and release 0.2.3
CultMath's hash is the shader-style frac(sin(x) * 43758.5453) and
returns float. Unity.Mathematics' hash returned a well-mixed uint, and
seeding from the float hash collapses: a 100x100 position grid gave 1625
distinct values in Aetheria's zone generator.
- math.pcg(uint), pcg3d(int3/float2/float3), and pcg4d(int4/float4)
follow Jarzynski and Olano, "Hash Functions for GPU Rendering"
(JCGT 9(3), 2020). pcg3d and pcg4d are the paper's code from page 32.
pcg is O'Neill's RXS-M-XS 32/32 output over one LCG step, with the
constants checked against pcg-c's pcg_variants.h.
- CultMath has no uint vectors, so pcg3d and pcg4d carry uint bit
patterns in int3/int4. The float overloads hash IEEE-754 bits via the
HLSL intrinsic asuint(float), which is new. pcg3d(float2) holds z at
0, the paper's route for 2 -> N inputs.
- The HLSL mirror gains the same functions, written over scalar uints
so the text compiles both in dxc 1.9.0.5402 (lib_6_3, HV 2021, clean)
and in C#. HlslSourceCompatibilityTests now compares int and uint
arguments and results by value; a wrong shift in the mirror's pcg4d
gives 208 mismatches.
- Known-answer vectors come from an independent Python transcription.
pcg3d separates a 100x100 half-unit grid (the full int3). One
component alone has one birthday collision there, as a 32-bit value
can. The same grid on the integer lattice has none.
- design.md states that the hashes are integer-exact in HLSL and on the
CPU: dxc wraps uint multiply and add modulo 2^32, as C# unchecked does.
- Bump to 0.2.3 and rebuild the tracked Unity DLL and pdb.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 18:19:53 +02:00
|
|
|
var args = types.Select(t => Scalars.Contains(t) ? Scalar(t, input(slot++))
|
|
|
|
|
: Activator.CreateInstance(t, Components(t).Select(f => Scalar(f.FieldType, input(slot++))).ToArray())!).ToArray();
|
|
|
|
|
var expected = Values(counterpart.Invoke(null, args)!);
|
|
|
|
|
var actual = Values(mirror.Invoke(shader, args)!);
|
Extend the HLSL mirror comparison to one struct return shape
HlslSourceCompatibilityTests previously flattened only bare vector returns to
scalar leaves. Values() now recurses through struct fields of any type, so a
struct return (invariant 8's one shape, e.g. CultCellular) is compared field
by field down to bit-for-bit scalars, exactly like a vector return already
was. Components() stays scoped to scalar-typed fields, since it only ever
builds function arguments and must not recurse.
TransformHlslToCSharp gains transformation 5: struct fields gain `public`,
since HLSL has no field access-modifier concept and a C# struct's fields
default to private, which would hide them from Values()' reflection. The old
wrap-in-a-class transformation is renumbered 6. design.md's transformation
list documents both, matching design.md:92's numbered list.
StructReturnComparisonRejectsAMismatchedField is the extension's own test: two
hand-built structs with the same shape (mirroring a real mirror-vs-C#
comparison) prove the field-by-field, bit-for-bit walk actually rejects a
struct whose leaf field differs, or whose nested vector's own component
differs, not just a length check.
EveryMirrorFunctionMatchesCSharpMath and HlslMirrorTests' required-symbol list
now cover cultmath_smin_grad and cultmath_cellular; the mirrored-function
count moves from 28 to 30.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 19:18:21 +02:00
|
|
|
if (!BitwiseEqual(expected, actual))
|
Compare every HLSL mirror function against C# math, bit for bit
Only cultmath_snoise was compared, at four inputs, so a flipped cultmath_step,
a changed hash constant, and a snoise2 tie change all passed. The mirror is
now discovered by reflection: every cultmath_* overload with a math
counterpart (26 functions) runs over 104 deterministic cases - each special
value in every argument (0, -0, +-1, ties, 1e-7, +-1e4, 1e7, NaN), special
pairs, seeded randoms, and vectors with x == y (snoise's x0.x == x0.y) - and
must match exactly. Both sides are float32 C# over the same intrinsics, so no
tolerance. A cultmath_* function with no counterpart must be named in
MirrorOnly. The four-input snoise theory is gone.
Mutations: flipped step args fail (349 mismatches); hash constants changed
by more than one float32 ulp fail (43758.55: 467, 127.2: 289, 74.8: 97).
43758.5453 -> .5454 is the same float32 and cannot fail. snoise2 x0.x > x0.y
-> >= is invisible: over 1,829,415 exact ties the result is bit-identical,
because the off-simplex corner's kernel is <= 0 on the shared edge.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 14:17:11 +02:00
|
|
|
{
|
Fix batch 1 (cellular/smin_grad): exact search, decorrelated id, finite F2=0
Cut 2a-i fix batch 1, findings F1, F3, F4, F5, F6, F7 (Soul falsification of
the first landing):
- cellular now searches a provably exact 5x5x5 neighbourhood (radius 2), not
3x3x3: 3x3x3 missed the true F2 for 9 of 400,000 sampled points. The
comment in math.cs proves 5x5x5 always contains the true F1 and F2 (both
are always < sqrt(3), every excluded cell is always > 2). A cheap
position-only lower-bound prune skips the pcg3d/jitter/distance work for
cells that provably cannot beat the running F2, without changing any
output bit (F6).
- F2 = 0 now gets the same degenerate-gradient guard F1 = 0 already had,
fixing a NaN that was reachable past |p| of about 2^23 (F3).
- id now comes from pcg4d over the winning cell alone, never from the pcg3d
hash that produced that cell's jitter, so id and jitter are no longer
deterministic functions of each other (F4).
- cellular_unit maps the top 24 bits instead of a plain divide-by-2^32, so
the mapping is always inside [0, 1); the old formula rounded
0xFFFFFF80..0xFFFFFFFF up to exactly 1.0f (F7).
- Central-difference tolerances tightened from 0.02 to 3e-4 (measured float
noise at the committed eps is about 6e-5), and cellular's own gradient
test now differentiates cellular's RETURNED .w values instead of a
duplicate FindF1F2 search, so a mutant that corrupts only the returned
gradient can't hide behind an independently-computed value (F1, F5).
- The struct-mirror comparison in HlslSourceCompatibilityTests now checks
field name and declared type in declaration order, not just position, so
a reordered-and-relabelled mirror struct (H2) or a renamed/retyped one
(H3) both fail the walk instead of surviving on values alone (F2).
- FindF1F2 widened to a true independent 7x7x7 brute-force oracle; new tests
pin production's exact agreement with it, the pruned search's bit-for-bit
equivalence to an unpruned 5x5x5 reference, no NaN at 2^24, and id's
correlation with jitter under 0.05.
- Comment/doc fix: smin_grad is Quilez's quadratic-polynomial smin, not
cubic.
Both shaders/CultMath.hlsl and unity/.../Shaders/CultMath.hlsl stay
byte-identical.
Manually verified mutants M9 (grad1 x 1.01), M16 (grad2 x 1.01), and M18 (id
from hash.y) all die against these tests; H2/H3 struct mutants die via the
new comparator tests. Local dotnet test: 191/191 passing. FXC cs_5_0 compile
of a scratch kernel using both cultmath_smin_grad and cultmath_cellular
succeeds.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 20:40:28 +02:00
|
|
|
mismatches.Add($"{mirror.Name}({string.Join(", ", args.Select(a => string.Join(" ", Values(a).Select(v => v.Value))))}): C# {string.Join(" ", expected.Select(v => v.Value))}, mirror {string.Join(" ", actual.Select(v => v.Value))}");
|
Compare every HLSL mirror function against C# math, bit for bit
Only cultmath_snoise was compared, at four inputs, so a flipped cultmath_step,
a changed hash constant, and a snoise2 tie change all passed. The mirror is
now discovered by reflection: every cultmath_* overload with a math
counterpart (26 functions) runs over 104 deterministic cases - each special
value in every argument (0, -0, +-1, ties, 1e-7, +-1e4, 1e7, NaN), special
pairs, seeded randoms, and vectors with x == y (snoise's x0.x == x0.y) - and
must match exactly. Both sides are float32 C# over the same intrinsics, so no
tolerance. A cultmath_* function with no counterpart must be named in
MirrorOnly. The four-input snoise theory is gone.
Mutations: flipped step args fail (349 mismatches); hash constants changed
by more than one float32 ulp fail (43758.55: 467, 127.2: 289, 74.8: 97).
43758.5453 -> .5454 is the same float32 and cannot fail. snoise2 x0.x > x0.y
-> >= is invisible: over 1,829,415 exact ties the result is bit-identical,
because the off-simplex corner's kernel is <= 0 on the shared edge.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 14:17:11 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Assert.True(mismatches.Count == 0, $"{mismatches.Count} mismatches:{Environment.NewLine}{string.Join(Environment.NewLine, mismatches.Take(12))}");
|
CultMath: phacelle, Johansen's Phacelle noise on 3D cells with an exact gradient
Cut 2a-ii-b. phacelle(float3 p, float3 side, float offset, float normalization)
returns CultPhasor { float4 cos; float4 sin }, each float4(gradient.xyz, value.w).
- 4x4x4 cells, pcg3d-hashed jitter in [-0.5, 0.5), weight max(0, exp(-2 d^2) - 0.01111),
normalization as upstream (lpmitchell/AdvancedTerrainErosion PhacelleNoise). The
caller supplies the stripe wave vector; offset is in cycles.
- The gradient is exact: weight derivatives and the normalization are included, not
upstream's grad(cos) ~ -sin * side.
- Cells whose per-axis lower bound is at least 2.25 have weight exactly 0 and are
skipped. An internal overload switches the prune off so the test can pin bit
equality without a second copy of the loop.
- cultmath_phacelle and CultPhasor mirror it in both tracked CultMath.hlsl copies
(the same blob). The mirror test compares the struct field by field; it now covers
34 functions.
- PhacelleTests: value against an independent double-precision reference, quarter-cycle
phase rotation, gradient against central differences on both sides of the
normalization threshold (excluding the threshold and each cell's weight-support
edge, where the exact gradient is discontinuous), stripes along the flow on the unit
sphere (0.215 against the 0.25 bound), step-difference continuity across cell planes,
and pruned == unpruned bit for bit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 23:51:02 +02:00
|
|
|
Assert.Equal(34, compared.Count);
|
Extend the HLSL mirror comparison to one struct return shape
HlslSourceCompatibilityTests previously flattened only bare vector returns to
scalar leaves. Values() now recurses through struct fields of any type, so a
struct return (invariant 8's one shape, e.g. CultCellular) is compared field
by field down to bit-for-bit scalars, exactly like a vector return already
was. Components() stays scoped to scalar-typed fields, since it only ever
builds function arguments and must not recurse.
TransformHlslToCSharp gains transformation 5: struct fields gain `public`,
since HLSL has no field access-modifier concept and a C# struct's fields
default to private, which would hide them from Values()' reflection. The old
wrap-in-a-class transformation is renumbered 6. design.md's transformation
list documents both, matching design.md:92's numbered list.
StructReturnComparisonRejectsAMismatchedField is the extension's own test: two
hand-built structs with the same shape (mirroring a real mirror-vs-C#
comparison) prove the field-by-field, bit-for-bit walk actually rejects a
struct whose leaf field differs, or whose nested vector's own component
differs, not just a length check.
EveryMirrorFunctionMatchesCSharpMath and HlslMirrorTests' required-symbol list
now cover cultmath_smin_grad and cultmath_cellular; the mirrored-function
count moves from 28 to 30.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 19:18:21 +02:00
|
|
|
}
|
|
|
|
|
|
2026-09-29 23:58:43 +02:00
|
|
|
/// <summary>
|
|
|
|
|
/// The generic comparison above passes about a hundred inputs through <c>cultmath_phacelle</c>, and
|
|
|
|
|
/// its prune only changes an output for the rare cell whose weight is nonzero but whose lower
|
|
|
|
|
/// bound sits just under the 2.25 cut (Soul's F6 lesson for cellular): a mirror whose cut is 2.0
|
|
|
|
|
/// survives a hundred inputs. A dense sweep of realistic arguments closes that gap.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Fact]
|
|
|
|
|
public void PhacelleMirrorMatchesCSharpBitForBitAcrossADenseSweep()
|
|
|
|
|
{
|
|
|
|
|
var (assembly, errors) = CompileShaderMirror();
|
|
|
|
|
Assert.True(assembly is not null, string.Join(Environment.NewLine, errors));
|
|
|
|
|
var shaderType = assembly!.GetType("CultMathHlsl.HlslShader")!;
|
|
|
|
|
var shader = Activator.CreateInstance(shaderType);
|
|
|
|
|
var mirror = ShaderFunction(shaderType, "cultmath_phacelle", typeof(float3), typeof(float3), typeof(float), typeof(float))!;
|
|
|
|
|
|
|
|
|
|
var random = new System.Random(0x5EED2);
|
|
|
|
|
float Next(float extent) => (random.NextSingle() * 2.0f - 1.0f) * extent;
|
|
|
|
|
for (var i = 0; i < 20000; i++)
|
|
|
|
|
{
|
|
|
|
|
var p = new float3(Next(50.0f), Next(50.0f), Next(50.0f));
|
|
|
|
|
var side = new float3(Next(9.0f), Next(9.0f), Next(9.0f));
|
|
|
|
|
var offset = random.NextSingle();
|
|
|
|
|
var normalization = random.NextSingle();
|
|
|
|
|
var expected = Values(phacelle(p, side, offset, normalization));
|
|
|
|
|
var actual = Values(mirror.Invoke(shader, new object[] { p, side, offset, normalization })!);
|
|
|
|
|
Assert.True(BitwiseEqual(expected, actual), $"mirror differs from C# at p = {p}, side = {side}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
CultMath: Phacelle port moves into MPL-2.0 files; prune cut pinned by boundary points
MPL-2.0 is file-level and the Phacelle port sat inside MIT files. phacelle and
CultPhasor now live in math.Phacelle.cs and CultPhasor.cs, and cultmath_phacelle
in CultMath.Phacelle.hlsl (included by CultMath.hlsl, both tracked copies), each
with the MPL header and Johansen / Luke Mitchell attribution. math stays one
partial class, so math.phacelle is called exactly as before. The GameCult.Math
package declares MIT AND MPL-2.0 and packs THIRD-PARTY-NOTICES.md and the new
include; the Unity LICENSE.md points at CultLib; build-unity-package.ps1
requires the new include.
The random sweeps never met a cell whose box bound sits just under the 2.25 cut
while its weight is still nonzero, so cuts of 2.245, 2.24 and 2.2 survived.
Six hard-coded points (found by an offline search) sit next to such cells; the
C# pruned-vs-unpruned test and the HLSL mirror test both use them, with a
witness check that re-derives the property from the hash.
Docs: the prune keeps about 45 of 64 cells (14 is the nonzero-weight count),
the support radius is 1.49998 (d^2 = 2.24995), normalization's range and
saturation, W = 0 only for adversarial hashes, and the support-edge gradient
seam size.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 00:34:27 +02:00
|
|
|
/// <summary>
|
|
|
|
|
/// The dense sweep almost never meets a cell whose box bound is just under the 2.25 cut and whose
|
|
|
|
|
/// weight is still nonzero: a mirror cut of 2.245, 2.24 or 2.2 survives it. These points sit next
|
|
|
|
|
/// to such cells (<see cref="PhacelleTests.PruneCutPoints"/>), so the mirror must match C# bit for
|
|
|
|
|
/// bit, and must match the unpruned C# sum (the shader has no prune switch, so that is the
|
|
|
|
|
/// independent statement that the cut is exact).
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Fact]
|
|
|
|
|
public void PhacelleMirrorMatchesCSharpBitForBitNextToCellsJustInsideTheCut()
|
|
|
|
|
{
|
|
|
|
|
var (assembly, errors) = CompileShaderMirror();
|
|
|
|
|
Assert.True(assembly is not null, string.Join(Environment.NewLine, errors));
|
|
|
|
|
var shaderType = assembly!.GetType("CultMathHlsl.HlslShader")!;
|
|
|
|
|
var shader = Activator.CreateInstance(shaderType);
|
|
|
|
|
var mirror = ShaderFunction(shaderType, "cultmath_phacelle", typeof(float3), typeof(float3), typeof(float), typeof(float))!;
|
|
|
|
|
|
|
|
|
|
foreach (var p in PhacelleTests.PruneCutPoints)
|
|
|
|
|
{
|
|
|
|
|
Assert.True(PhacelleTests.HasCellJustInsideTheCut(p), $"no cell just inside the cut at {p}");
|
|
|
|
|
var actual = Values(mirror.Invoke(shader, new object[] { p, PhacelleTests.PruneCutSide, PhacelleTests.PruneCutOffset, PhacelleTests.PruneCutNormalization })!);
|
|
|
|
|
var pruned = Values(phacelle(p, PhacelleTests.PruneCutSide, PhacelleTests.PruneCutOffset, PhacelleTests.PruneCutNormalization));
|
|
|
|
|
var unpruned = Values(phacelle(p, PhacelleTests.PruneCutSide, PhacelleTests.PruneCutOffset, PhacelleTests.PruneCutNormalization, false));
|
|
|
|
|
Assert.True(BitwiseEqual(pruned, actual), $"mirror differs from C# at p = {p}");
|
|
|
|
|
Assert.True(BitwiseEqual(unpruned, actual), $"mirror differs from the unpruned C# sum at p = {p}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Extend the HLSL mirror comparison to one struct return shape
HlslSourceCompatibilityTests previously flattened only bare vector returns to
scalar leaves. Values() now recurses through struct fields of any type, so a
struct return (invariant 8's one shape, e.g. CultCellular) is compared field
by field down to bit-for-bit scalars, exactly like a vector return already
was. Components() stays scoped to scalar-typed fields, since it only ever
builds function arguments and must not recurse.
TransformHlslToCSharp gains transformation 5: struct fields gain `public`,
since HLSL has no field access-modifier concept and a C# struct's fields
default to private, which would hide them from Values()' reflection. The old
wrap-in-a-class transformation is renumbered 6. design.md's transformation
list documents both, matching design.md:92's numbered list.
StructReturnComparisonRejectsAMismatchedField is the extension's own test: two
hand-built structs with the same shape (mirroring a real mirror-vs-C#
comparison) prove the field-by-field, bit-for-bit walk actually rejects a
struct whose leaf field differs, or whose nested vector's own component
differs, not just a length check.
EveryMirrorFunctionMatchesCSharpMath and HlslMirrorTests' required-symbol list
now cover cultmath_smin_grad and cultmath_cellular; the mirrored-function
count moves from 28 to 30.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 19:18:21 +02:00
|
|
|
/// <summary>
|
|
|
|
|
/// A hand-built pair of structs standing in for a mirror struct return and its C# counterpart
|
|
|
|
|
/// (invariant 8's one struct return shape, e.g. <see cref="CultCellular"/>): same field shape,
|
|
|
|
|
/// different concrete type, exactly like the real mirror comparison. Proves the field-by-field,
|
|
|
|
|
/// bit-for-bit walk actually rejects a struct whose field differs; without this, the walk could
|
|
|
|
|
/// have been comparing lengths only, or skipping the nested vector's own components.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private struct StructMirrorProbeExpected { public float4 first; public float second; }
|
|
|
|
|
private struct StructMirrorProbeActual { public float4 first; public float second; }
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void StructReturnComparisonRejectsAMismatchedField()
|
|
|
|
|
{
|
|
|
|
|
var expected = new StructMirrorProbeExpected { first = new float4(1.0f, 2.0f, 3.0f, 4.0f), second = 5.0f };
|
|
|
|
|
var matching = new StructMirrorProbeActual { first = new float4(1.0f, 2.0f, 3.0f, 4.0f), second = 5.0f };
|
|
|
|
|
var mismatchedLeaf = new StructMirrorProbeActual { first = new float4(1.0f, 2.0f, 3.0f, 4.0f), second = 5.5f };
|
|
|
|
|
var mismatchedNestedComponent = new StructMirrorProbeActual { first = new float4(1.0f, 2.0f, -3.0f, 4.0f), second = 5.0f };
|
|
|
|
|
|
|
|
|
|
Assert.True(BitwiseEqual(Values(expected), Values(matching)));
|
|
|
|
|
Assert.False(BitwiseEqual(Values(expected), Values(mismatchedLeaf)));
|
|
|
|
|
Assert.False(BitwiseEqual(Values(expected), Values(mismatchedNestedComponent)));
|
2026-09-14 13:57:03 +02:00
|
|
|
}
|
|
|
|
|
|
Fix batch 1 (cellular/smin_grad): exact search, decorrelated id, finite F2=0
Cut 2a-i fix batch 1, findings F1, F3, F4, F5, F6, F7 (Soul falsification of
the first landing):
- cellular now searches a provably exact 5x5x5 neighbourhood (radius 2), not
3x3x3: 3x3x3 missed the true F2 for 9 of 400,000 sampled points. The
comment in math.cs proves 5x5x5 always contains the true F1 and F2 (both
are always < sqrt(3), every excluded cell is always > 2). A cheap
position-only lower-bound prune skips the pcg3d/jitter/distance work for
cells that provably cannot beat the running F2, without changing any
output bit (F6).
- F2 = 0 now gets the same degenerate-gradient guard F1 = 0 already had,
fixing a NaN that was reachable past |p| of about 2^23 (F3).
- id now comes from pcg4d over the winning cell alone, never from the pcg3d
hash that produced that cell's jitter, so id and jitter are no longer
deterministic functions of each other (F4).
- cellular_unit maps the top 24 bits instead of a plain divide-by-2^32, so
the mapping is always inside [0, 1); the old formula rounded
0xFFFFFF80..0xFFFFFFFF up to exactly 1.0f (F7).
- Central-difference tolerances tightened from 0.02 to 3e-4 (measured float
noise at the committed eps is about 6e-5), and cellular's own gradient
test now differentiates cellular's RETURNED .w values instead of a
duplicate FindF1F2 search, so a mutant that corrupts only the returned
gradient can't hide behind an independently-computed value (F1, F5).
- The struct-mirror comparison in HlslSourceCompatibilityTests now checks
field name and declared type in declaration order, not just position, so
a reordered-and-relabelled mirror struct (H2) or a renamed/retyped one
(H3) both fail the walk instead of surviving on values alone (F2).
- FindF1F2 widened to a true independent 7x7x7 brute-force oracle; new tests
pin production's exact agreement with it, the pruned search's bit-for-bit
equivalence to an unpruned 5x5x5 reference, no NaN at 2^24, and id's
correlation with jitter under 0.05.
- Comment/doc fix: smin_grad is Quilez's quadratic-polynomial smin, not
cubic.
Both shaders/CultMath.hlsl and unity/.../Shaders/CultMath.hlsl stay
byte-identical.
Manually verified mutants M9 (grad1 x 1.01), M16 (grad2 x 1.01), and M18 (id
from hash.y) all die against these tests; H2/H3 struct mutants die via the
new comparator tests. Local dotnet test: 191/191 passing. FXC cs_5_0 compile
of a scratch kernel using both cultmath_smin_grad and cultmath_cellular
succeeds.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 20:40:28 +02:00
|
|
|
/// <summary>
|
|
|
|
|
/// Struct shapes standing in for CultCellular's own field order (<c>nearest; edge; id</c>), used
|
|
|
|
|
/// to falsify two named mutants (F2): H2 declares the mirror struct with its first two fields
|
|
|
|
|
/// swapped (<c>edge; nearest; id</c>) and swaps the assignments to match, so every leaf VALUE
|
|
|
|
|
/// still lands in the same POSITION as the correct struct; only the field NAME at each position
|
|
|
|
|
/// differs. H3 renames and retypes every field (id becomes an int carrying the same bits) while
|
|
|
|
|
/// keeping the original position order, so only NAME and TYPE differ, never position.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private struct NearestEdgeIdExpected { public float4 nearest; public float4 edge; public float id; }
|
|
|
|
|
private struct EdgeNearestIdSwappedNames { public float4 edge; public float4 nearest; public float id; }
|
|
|
|
|
private struct RenamedAndRetypedFields { public float4 a; public float4 b; public int c; }
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void StructReturnComparisonRejectsFieldsSwappedByNameEvenWhenValuesAlign()
|
|
|
|
|
{
|
|
|
|
|
// H2: the mutant struct's field ORDER is (edge, nearest, id), and its assignments are
|
|
|
|
|
// swapped to match, so declaration-position values are identical to the correct struct's.
|
|
|
|
|
// Only checking names (not just position) can tell these apart.
|
|
|
|
|
var expected = new NearestEdgeIdExpected
|
|
|
|
|
{
|
|
|
|
|
nearest = new float4(1.0f, 2.0f, 3.0f, 4.0f),
|
|
|
|
|
edge = new float4(5.0f, 6.0f, 7.0f, 8.0f),
|
|
|
|
|
id = 9.0f,
|
|
|
|
|
};
|
|
|
|
|
var swapped = new EdgeNearestIdSwappedNames
|
|
|
|
|
{
|
|
|
|
|
edge = new float4(1.0f, 2.0f, 3.0f, 4.0f), // holds what should be "nearest"'s value.
|
|
|
|
|
nearest = new float4(5.0f, 6.0f, 7.0f, 8.0f), // holds what should be "edge"'s value.
|
|
|
|
|
id = 9.0f,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Assert.False(BitwiseEqual(Values(expected), Values(swapped)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void StructReturnComparisonRejectsRenamedAndRetypedFields()
|
|
|
|
|
{
|
|
|
|
|
// H3: field order matches the correct struct exactly, but every field is renamed, and id is
|
|
|
|
|
// retyped from float to int carrying the same bit pattern the correct id would round to.
|
|
|
|
|
var expected = new NearestEdgeIdExpected
|
|
|
|
|
{
|
|
|
|
|
nearest = new float4(1.0f, 2.0f, 3.0f, 4.0f),
|
|
|
|
|
edge = new float4(5.0f, 6.0f, 7.0f, 8.0f),
|
|
|
|
|
id = 9.0f,
|
|
|
|
|
};
|
|
|
|
|
var renamed = new RenamedAndRetypedFields
|
|
|
|
|
{
|
|
|
|
|
a = new float4(1.0f, 2.0f, 3.0f, 4.0f),
|
|
|
|
|
b = new float4(5.0f, 6.0f, 7.0f, 8.0f),
|
|
|
|
|
c = BitConverter.SingleToInt32Bits(9.0f),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Assert.False(BitwiseEqual(Values(expected), Values(renamed)));
|
|
|
|
|
}
|
|
|
|
|
|
Add PCG integer hashes to CultMath and release 0.2.3
CultMath's hash is the shader-style frac(sin(x) * 43758.5453) and
returns float. Unity.Mathematics' hash returned a well-mixed uint, and
seeding from the float hash collapses: a 100x100 position grid gave 1625
distinct values in Aetheria's zone generator.
- math.pcg(uint), pcg3d(int3/float2/float3), and pcg4d(int4/float4)
follow Jarzynski and Olano, "Hash Functions for GPU Rendering"
(JCGT 9(3), 2020). pcg3d and pcg4d are the paper's code from page 32.
pcg is O'Neill's RXS-M-XS 32/32 output over one LCG step, with the
constants checked against pcg-c's pcg_variants.h.
- CultMath has no uint vectors, so pcg3d and pcg4d carry uint bit
patterns in int3/int4. The float overloads hash IEEE-754 bits via the
HLSL intrinsic asuint(float), which is new. pcg3d(float2) holds z at
0, the paper's route for 2 -> N inputs.
- The HLSL mirror gains the same functions, written over scalar uints
so the text compiles both in dxc 1.9.0.5402 (lib_6_3, HV 2021, clean)
and in C#. HlslSourceCompatibilityTests now compares int and uint
arguments and results by value; a wrong shift in the mirror's pcg4d
gives 208 mismatches.
- Known-answer vectors come from an independent Python transcription.
pcg3d separates a 100x100 half-unit grid (the full int3). One
component alone has one birthday collision there, as a 32-bit value
can. The same grid on the integer lattice has none.
- design.md states that the hashes are integer-exact in HLSL and on the
CPU: dxc wraps uint multiply and add modulo 2^32, as C# unchecked does.
- Bump to 0.2.3 and rebuild the tracked Unity DLL and pdb.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 18:19:53 +02:00
|
|
|
// Integer arguments take the bit pattern of the float input, so specials become 0, 0x80000000, NaN bits, ...
|
|
|
|
|
private static readonly Type[] Scalars = { typeof(float), typeof(int), typeof(uint) };
|
Compare every HLSL mirror function against C# math, bit for bit
Only cultmath_snoise was compared, at four inputs, so a flipped cultmath_step,
a changed hash constant, and a snoise2 tie change all passed. The mirror is
now discovered by reflection: every cultmath_* overload with a math
counterpart (26 functions) runs over 104 deterministic cases - each special
value in every argument (0, -0, +-1, ties, 1e-7, +-1e4, 1e7, NaN), special
pairs, seeded randoms, and vectors with x == y (snoise's x0.x == x0.y) - and
must match exactly. Both sides are float32 C# over the same intrinsics, so no
tolerance. A cultmath_* function with no counterpart must be named in
MirrorOnly. The four-input snoise theory is gone.
Mutations: flipped step args fail (349 mismatches); hash constants changed
by more than one float32 ulp fail (43758.55: 467, 127.2: 289, 74.8: 97).
43758.5453 -> .5454 is the same float32 and cannot fail. snoise2 x0.x > x0.y
-> >= is invisible: over 1,829,415 exact ties the result is bit-identical,
because the off-simplex corner's kernel is <= 0 on the shared edge.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 14:17:11 +02:00
|
|
|
|
Add PCG integer hashes to CultMath and release 0.2.3
CultMath's hash is the shader-style frac(sin(x) * 43758.5453) and
returns float. Unity.Mathematics' hash returned a well-mixed uint, and
seeding from the float hash collapses: a 100x100 position grid gave 1625
distinct values in Aetheria's zone generator.
- math.pcg(uint), pcg3d(int3/float2/float3), and pcg4d(int4/float4)
follow Jarzynski and Olano, "Hash Functions for GPU Rendering"
(JCGT 9(3), 2020). pcg3d and pcg4d are the paper's code from page 32.
pcg is O'Neill's RXS-M-XS 32/32 output over one LCG step, with the
constants checked against pcg-c's pcg_variants.h.
- CultMath has no uint vectors, so pcg3d and pcg4d carry uint bit
patterns in int3/int4. The float overloads hash IEEE-754 bits via the
HLSL intrinsic asuint(float), which is new. pcg3d(float2) holds z at
0, the paper's route for 2 -> N inputs.
- The HLSL mirror gains the same functions, written over scalar uints
so the text compiles both in dxc 1.9.0.5402 (lib_6_3, HV 2021, clean)
and in C#. HlslSourceCompatibilityTests now compares int and uint
arguments and results by value; a wrong shift in the mirror's pcg4d
gives 208 mismatches.
- Known-answer vectors come from an independent Python transcription.
pcg3d separates a 100x100 half-unit grid (the full int3). One
component alone has one birthday collision there, as a 32-bit value
can. The same grid on the integer lattice has none.
- design.md states that the hashes are integer-exact in HLSL and on the
CPU: dxc wraps uint multiply and add modulo 2^32, as C# unchecked does.
- Bump to 0.2.3 and rebuild the tracked Unity DLL and pdb.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 18:19:53 +02:00
|
|
|
private static object Scalar(Type t, float f) => t == typeof(float) ? f
|
|
|
|
|
: t == typeof(int) ? BitConverter.SingleToInt32Bits(f) : (object)(uint)BitConverter.SingleToInt32Bits(f);
|
|
|
|
|
|
Extend the HLSL mirror comparison to one struct return shape
HlslSourceCompatibilityTests previously flattened only bare vector returns to
scalar leaves. Values() now recurses through struct fields of any type, so a
struct return (invariant 8's one shape, e.g. CultCellular) is compared field
by field down to bit-for-bit scalars, exactly like a vector return already
was. Components() stays scoped to scalar-typed fields, since it only ever
builds function arguments and must not recurse.
TransformHlslToCSharp gains transformation 5: struct fields gain `public`,
since HLSL has no field access-modifier concept and a C# struct's fields
default to private, which would hide them from Values()' reflection. The old
wrap-in-a-class transformation is renumbered 6. design.md's transformation
list documents both, matching design.md:92's numbered list.
StructReturnComparisonRejectsAMismatchedField is the extension's own test: two
hand-built structs with the same shape (mirroring a real mirror-vs-C#
comparison) prove the field-by-field, bit-for-bit walk actually rejects a
struct whose leaf field differs, or whose nested vector's own component
differs, not just a length check.
EveryMirrorFunctionMatchesCSharpMath and HlslMirrorTests' required-symbol list
now cover cultmath_smin_grad and cultmath_cellular; the mirrored-function
count moves from 28 to 30.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 19:18:21 +02:00
|
|
|
// Scoped to scalar-typed fields: this only ever builds function ARGUMENTS (float2/3/4, int2/3/4, ...),
|
|
|
|
|
// never a struct-of-struct return, so it must not recurse.
|
Add PCG integer hashes to CultMath and release 0.2.3
CultMath's hash is the shader-style frac(sin(x) * 43758.5453) and
returns float. Unity.Mathematics' hash returned a well-mixed uint, and
seeding from the float hash collapses: a 100x100 position grid gave 1625
distinct values in Aetheria's zone generator.
- math.pcg(uint), pcg3d(int3/float2/float3), and pcg4d(int4/float4)
follow Jarzynski and Olano, "Hash Functions for GPU Rendering"
(JCGT 9(3), 2020). pcg3d and pcg4d are the paper's code from page 32.
pcg is O'Neill's RXS-M-XS 32/32 output over one LCG step, with the
constants checked against pcg-c's pcg_variants.h.
- CultMath has no uint vectors, so pcg3d and pcg4d carry uint bit
patterns in int3/int4. The float overloads hash IEEE-754 bits via the
HLSL intrinsic asuint(float), which is new. pcg3d(float2) holds z at
0, the paper's route for 2 -> N inputs.
- The HLSL mirror gains the same functions, written over scalar uints
so the text compiles both in dxc 1.9.0.5402 (lib_6_3, HV 2021, clean)
and in C#. HlslSourceCompatibilityTests now compares int and uint
arguments and results by value; a wrong shift in the mirror's pcg4d
gives 208 mismatches.
- Known-answer vectors come from an independent Python transcription.
pcg3d separates a 100x100 half-unit grid (the full int3). One
component alone has one birthday collision there, as a 32-bit value
can. The same grid on the integer lattice has none.
- design.md states that the hashes are integer-exact in HLSL and on the
CPU: dxc wraps uint multiply and add modulo 2^32, as C# unchecked does.
- Bump to 0.2.3 and rebuild the tracked Unity DLL and pdb.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 18:19:53 +02:00
|
|
|
private static FieldInfo[] Components(Type t) => t.GetFields().Where(f => !f.IsStatic && Scalars.Contains(f.FieldType)).ToArray();
|
|
|
|
|
|
Fix batch 1 (cellular/smin_grad): exact search, decorrelated id, finite F2=0
Cut 2a-i fix batch 1, findings F1, F3, F4, F5, F6, F7 (Soul falsification of
the first landing):
- cellular now searches a provably exact 5x5x5 neighbourhood (radius 2), not
3x3x3: 3x3x3 missed the true F2 for 9 of 400,000 sampled points. The
comment in math.cs proves 5x5x5 always contains the true F1 and F2 (both
are always < sqrt(3), every excluded cell is always > 2). A cheap
position-only lower-bound prune skips the pcg3d/jitter/distance work for
cells that provably cannot beat the running F2, without changing any
output bit (F6).
- F2 = 0 now gets the same degenerate-gradient guard F1 = 0 already had,
fixing a NaN that was reachable past |p| of about 2^23 (F3).
- id now comes from pcg4d over the winning cell alone, never from the pcg3d
hash that produced that cell's jitter, so id and jitter are no longer
deterministic functions of each other (F4).
- cellular_unit maps the top 24 bits instead of a plain divide-by-2^32, so
the mapping is always inside [0, 1); the old formula rounded
0xFFFFFF80..0xFFFFFFFF up to exactly 1.0f (F7).
- Central-difference tolerances tightened from 0.02 to 3e-4 (measured float
noise at the committed eps is about 6e-5), and cellular's own gradient
test now differentiates cellular's RETURNED .w values instead of a
duplicate FindF1F2 search, so a mutant that corrupts only the returned
gradient can't hide behind an independently-computed value (F1, F5).
- The struct-mirror comparison in HlslSourceCompatibilityTests now checks
field name and declared type in declaration order, not just position, so
a reordered-and-relabelled mirror struct (H2) or a renamed/retyped one
(H3) both fail the walk instead of surviving on values alone (F2).
- FindF1F2 widened to a true independent 7x7x7 brute-force oracle; new tests
pin production's exact agreement with it, the pruned search's bit-for-bit
equivalence to an unpruned 5x5x5 reference, no NaN at 2^24, and id's
correlation with jitter under 0.05.
- Comment/doc fix: smin_grad is Quilez's quadratic-polynomial smin, not
cubic.
Both shaders/CultMath.hlsl and unity/.../Shaders/CultMath.hlsl stay
byte-identical.
Manually verified mutants M9 (grad1 x 1.01), M16 (grad2 x 1.01), and M18 (id
from hash.y) all die against these tests; H2/H3 struct mutants die via the
new comparator tests. Local dotnet test: 191/191 passing. FXC cs_5_0 compile
of a scratch kernel using both cultmath_smin_grad and cultmath_cellular
succeeds.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 20:40:28 +02:00
|
|
|
/// <summary>
|
|
|
|
|
/// One flattened scalar leaf of a (possibly nested) return value: its declaration path (field
|
|
|
|
|
/// names joined by '.'), its declared type, and its value. Carrying path and type, not just the
|
|
|
|
|
/// value, is what lets the struct-return comparison reject a field that was renamed or retyped
|
|
|
|
|
/// but landed in the same position (F2): a positional-only comparison cannot tell "edge" holding
|
|
|
|
|
/// nearest's value apart from "nearest" holding it.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private readonly record struct Leaf(string Path, Type Type, object Value);
|
|
|
|
|
|
Extend the HLSL mirror comparison to one struct return shape
HlslSourceCompatibilityTests previously flattened only bare vector returns to
scalar leaves. Values() now recurses through struct fields of any type, so a
struct return (invariant 8's one shape, e.g. CultCellular) is compared field
by field down to bit-for-bit scalars, exactly like a vector return already
was. Components() stays scoped to scalar-typed fields, since it only ever
builds function arguments and must not recurse.
TransformHlslToCSharp gains transformation 5: struct fields gain `public`,
since HLSL has no field access-modifier concept and a C# struct's fields
default to private, which would hide them from Values()' reflection. The old
wrap-in-a-class transformation is renumbered 6. design.md's transformation
list documents both, matching design.md:92's numbered list.
StructReturnComparisonRejectsAMismatchedField is the extension's own test: two
hand-built structs with the same shape (mirroring a real mirror-vs-C#
comparison) prove the field-by-field, bit-for-bit walk actually rejects a
struct whose leaf field differs, or whose nested vector's own component
differs, not just a length check.
EveryMirrorFunctionMatchesCSharpMath and HlslMirrorTests' required-symbol list
now cover cultmath_smin_grad and cultmath_cellular; the mirrored-function
count moves from 28 to 30.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 19:18:21 +02:00
|
|
|
/// <summary>
|
|
|
|
|
/// Flattens a return value to its scalar leaves, recursing through struct fields of any type
|
|
|
|
|
/// (invariant 8's one struct return shape, e.g. <see cref="CultCellular"/>'s float4/float4/float
|
|
|
|
|
/// fields) so a struct return is compared field by field, down to bit-for-bit scalars, exactly
|
Fix batch 1 (cellular/smin_grad): exact search, decorrelated id, finite F2=0
Cut 2a-i fix batch 1, findings F1, F3, F4, F5, F6, F7 (Soul falsification of
the first landing):
- cellular now searches a provably exact 5x5x5 neighbourhood (radius 2), not
3x3x3: 3x3x3 missed the true F2 for 9 of 400,000 sampled points. The
comment in math.cs proves 5x5x5 always contains the true F1 and F2 (both
are always < sqrt(3), every excluded cell is always > 2). A cheap
position-only lower-bound prune skips the pcg3d/jitter/distance work for
cells that provably cannot beat the running F2, without changing any
output bit (F6).
- F2 = 0 now gets the same degenerate-gradient guard F1 = 0 already had,
fixing a NaN that was reachable past |p| of about 2^23 (F3).
- id now comes from pcg4d over the winning cell alone, never from the pcg3d
hash that produced that cell's jitter, so id and jitter are no longer
deterministic functions of each other (F4).
- cellular_unit maps the top 24 bits instead of a plain divide-by-2^32, so
the mapping is always inside [0, 1); the old formula rounded
0xFFFFFF80..0xFFFFFFFF up to exactly 1.0f (F7).
- Central-difference tolerances tightened from 0.02 to 3e-4 (measured float
noise at the committed eps is about 6e-5), and cellular's own gradient
test now differentiates cellular's RETURNED .w values instead of a
duplicate FindF1F2 search, so a mutant that corrupts only the returned
gradient can't hide behind an independently-computed value (F1, F5).
- The struct-mirror comparison in HlslSourceCompatibilityTests now checks
field name and declared type in declaration order, not just position, so
a reordered-and-relabelled mirror struct (H2) or a renamed/retyped one
(H3) both fail the walk instead of surviving on values alone (F2).
- FindF1F2 widened to a true independent 7x7x7 brute-force oracle; new tests
pin production's exact agreement with it, the pruned search's bit-for-bit
equivalence to an unpruned 5x5x5 reference, no NaN at 2^24, and id's
correlation with jitter under 0.05.
- Comment/doc fix: smin_grad is Quilez's quadratic-polynomial smin, not
cubic.
Both shaders/CultMath.hlsl and unity/.../Shaders/CultMath.hlsl stay
byte-identical.
Manually verified mutants M9 (grad1 x 1.01), M16 (grad2 x 1.01), and M18 (id
from hash.y) all die against these tests; H2/H3 struct mutants die via the
new comparator tests. Local dotnet test: 191/191 passing. FXC cs_5_0 compile
of a scratch kernel using both cultmath_smin_grad and cultmath_cellular
succeeds.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 20:40:28 +02:00
|
|
|
/// like a bare vector return already is. Each leaf keeps the field name (and its ancestors') and
|
|
|
|
|
/// declared type it was read from.
|
Extend the HLSL mirror comparison to one struct return shape
HlslSourceCompatibilityTests previously flattened only bare vector returns to
scalar leaves. Values() now recurses through struct fields of any type, so a
struct return (invariant 8's one shape, e.g. CultCellular) is compared field
by field down to bit-for-bit scalars, exactly like a vector return already
was. Components() stays scoped to scalar-typed fields, since it only ever
builds function arguments and must not recurse.
TransformHlslToCSharp gains transformation 5: struct fields gain `public`,
since HLSL has no field access-modifier concept and a C# struct's fields
default to private, which would hide them from Values()' reflection. The old
wrap-in-a-class transformation is renumbered 6. design.md's transformation
list documents both, matching design.md:92's numbered list.
StructReturnComparisonRejectsAMismatchedField is the extension's own test: two
hand-built structs with the same shape (mirroring a real mirror-vs-C#
comparison) prove the field-by-field, bit-for-bit walk actually rejects a
struct whose leaf field differs, or whose nested vector's own component
differs, not just a length check.
EveryMirrorFunctionMatchesCSharpMath and HlslMirrorTests' required-symbol list
now cover cultmath_smin_grad and cultmath_cellular; the mirrored-function
count moves from 28 to 30.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 19:18:21 +02:00
|
|
|
/// </summary>
|
Fix batch 1 (cellular/smin_grad): exact search, decorrelated id, finite F2=0
Cut 2a-i fix batch 1, findings F1, F3, F4, F5, F6, F7 (Soul falsification of
the first landing):
- cellular now searches a provably exact 5x5x5 neighbourhood (radius 2), not
3x3x3: 3x3x3 missed the true F2 for 9 of 400,000 sampled points. The
comment in math.cs proves 5x5x5 always contains the true F1 and F2 (both
are always < sqrt(3), every excluded cell is always > 2). A cheap
position-only lower-bound prune skips the pcg3d/jitter/distance work for
cells that provably cannot beat the running F2, without changing any
output bit (F6).
- F2 = 0 now gets the same degenerate-gradient guard F1 = 0 already had,
fixing a NaN that was reachable past |p| of about 2^23 (F3).
- id now comes from pcg4d over the winning cell alone, never from the pcg3d
hash that produced that cell's jitter, so id and jitter are no longer
deterministic functions of each other (F4).
- cellular_unit maps the top 24 bits instead of a plain divide-by-2^32, so
the mapping is always inside [0, 1); the old formula rounded
0xFFFFFF80..0xFFFFFFFF up to exactly 1.0f (F7).
- Central-difference tolerances tightened from 0.02 to 3e-4 (measured float
noise at the committed eps is about 6e-5), and cellular's own gradient
test now differentiates cellular's RETURNED .w values instead of a
duplicate FindF1F2 search, so a mutant that corrupts only the returned
gradient can't hide behind an independently-computed value (F1, F5).
- The struct-mirror comparison in HlslSourceCompatibilityTests now checks
field name and declared type in declaration order, not just position, so
a reordered-and-relabelled mirror struct (H2) or a renamed/retyped one
(H3) both fail the walk instead of surviving on values alone (F2).
- FindF1F2 widened to a true independent 7x7x7 brute-force oracle; new tests
pin production's exact agreement with it, the pruned search's bit-for-bit
equivalence to an unpruned 5x5x5 reference, no NaN at 2^24, and id's
correlation with jitter under 0.05.
- Comment/doc fix: smin_grad is Quilez's quadratic-polynomial smin, not
cubic.
Both shaders/CultMath.hlsl and unity/.../Shaders/CultMath.hlsl stay
byte-identical.
Manually verified mutants M9 (grad1 x 1.01), M16 (grad2 x 1.01), and M18 (id
from hash.y) all die against these tests; H2/H3 struct mutants die via the
new comparator tests. Local dotnet test: 191/191 passing. FXC cs_5_0 compile
of a scratch kernel using both cultmath_smin_grad and cultmath_cellular
succeeds.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 20:40:28 +02:00
|
|
|
private static Leaf[] Values(object value, string path = "") => Scalars.Contains(value.GetType())
|
|
|
|
|
? new[] { new Leaf(path, value.GetType(), value) }
|
|
|
|
|
: value.GetType().GetFields().Where(f => !f.IsStatic)
|
|
|
|
|
.SelectMany(f => Values(f.GetValue(value)!, path.Length == 0 ? f.Name : path + "." + f.Name))
|
|
|
|
|
.ToArray();
|
|
|
|
|
|
|
|
|
|
// Compares leaves by NAME and TYPE as well as value, in declaration order, so a mirror struct
|
|
|
|
|
// whose fields are reordered, renamed, or retyped relative to its C# counterpart fails here even
|
|
|
|
|
// when the flattened values happen to line up positionally (F2's H2/H3 mutants).
|
|
|
|
|
private static bool BitwiseEqual(Leaf[] expected, Leaf[] actual) =>
|
|
|
|
|
expected.Length == actual.Length && expected.Zip(actual).All(p =>
|
|
|
|
|
p.First.Path == p.Second.Path && p.First.Type == p.Second.Type &&
|
|
|
|
|
(p.First.Value is float a && p.Second.Value is float b
|
|
|
|
|
? (float.IsNaN(a) && float.IsNaN(b)) || BitConverter.SingleToInt32Bits(a) == BitConverter.SingleToInt32Bits(b)
|
|
|
|
|
: p.First.Value.Equals(p.Second.Value)));
|
Compare every HLSL mirror function against C# math, bit for bit
Only cultmath_snoise was compared, at four inputs, so a flipped cultmath_step,
a changed hash constant, and a snoise2 tie change all passed. The mirror is
now discovered by reflection: every cultmath_* overload with a math
counterpart (26 functions) runs over 104 deterministic cases - each special
value in every argument (0, -0, +-1, ties, 1e-7, +-1e4, 1e7, NaN), special
pairs, seeded randoms, and vectors with x == y (snoise's x0.x == x0.y) - and
must match exactly. Both sides are float32 C# over the same intrinsics, so no
tolerance. A cultmath_* function with no counterpart must be named in
MirrorOnly. The four-input snoise theory is gone.
Mutations: flipped step args fail (349 mismatches); hash constants changed
by more than one float32 ulp fail (43758.55: 467, 127.2: 289, 74.8: 97).
43758.5453 -> .5454 is the same float32 and cannot fail. snoise2 x0.x > x0.y
-> >= is invisible: over 1,829,415 exact ties the result is bit-identical,
because the off-simplex corner's kernel is <= 0 on the shared edge.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 14:17:11 +02:00
|
|
|
|
CultMath: Phacelle port moves into MPL-2.0 files; prune cut pinned by boundary points
MPL-2.0 is file-level and the Phacelle port sat inside MIT files. phacelle and
CultPhasor now live in math.Phacelle.cs and CultPhasor.cs, and cultmath_phacelle
in CultMath.Phacelle.hlsl (included by CultMath.hlsl, both tracked copies), each
with the MPL header and Johansen / Luke Mitchell attribution. math stays one
partial class, so math.phacelle is called exactly as before. The GameCult.Math
package declares MIT AND MPL-2.0 and packs THIRD-PARTY-NOTICES.md and the new
include; the Unity LICENSE.md points at CultLib; build-unity-package.ps1
requires the new include.
The random sweeps never met a cell whose box bound sits just under the 2.25 cut
while its weight is still nonzero, so cuts of 2.245, 2.24 and 2.2 survived.
Six hard-coded points (found by an offline search) sit next to such cells; the
C# pruned-vs-unpruned test and the HLSL mirror test both use them, with a
witness check that re-derives the property from the hash.
Docs: the prune keeps about 45 of 64 cells (14 is the nonzero-weight count),
the support radius is 1.49998 (d^2 = 2.24995), normalization's range and
saturation, W = 0 only for adversarial hashes, and the support-edge gradient
seam size.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 00:34:27 +02:00
|
|
|
/// <summary>
|
|
|
|
|
/// The text of <c>CultMath.hlsl</c> with each <c>#include "name"</c> 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.
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal static string ReadShaderSource(string cultMathRoot)
|
|
|
|
|
{
|
|
|
|
|
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)));
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-14 13:57:03 +02:00
|
|
|
/// <summary>The documented HLSL-to-C# transformations, and nothing else.</summary>
|
|
|
|
|
internal static string TransformHlslToCSharp(string hlsl)
|
|
|
|
|
{
|
|
|
|
|
// 1. Include guards (#ifndef/#define/#endif) are dropped; C# has no textual include.
|
|
|
|
|
var source = Regex.Replace(hlsl, @"(?m)^\s*#(ifndef|define|endif)\b.*$", string.Empty);
|
|
|
|
|
|
|
|
|
|
// 2. Functions taking Texture2D/SamplerState resources are dropped; GPU resources have
|
|
|
|
|
// no CultMath analog.
|
|
|
|
|
source = Regex.Replace(source, @"(?ms)^\w+ \w+\([^)]*\b(Texture2D|SamplerState)\b[^)]*\)\s*\{.*?^\}[ \t]*\r?$", string.Empty);
|
|
|
|
|
|
|
|
|
|
// 3. File-scope `static const` becomes `const` (C# consts are implicitly static).
|
|
|
|
|
source = Regex.Replace(source, @"(?m)^static const ", "const ");
|
|
|
|
|
|
|
|
|
|
// 4. Floating literals gain the `f` suffix; an unsuffixed C# literal is a double.
|
|
|
|
|
source = Regex.Replace(source, @"(?<![\w.])(\d+\.\d*(?:[eE][+-]?\d+)?|\d+[eE][+-]?\d+)(?![\w.])", "$1f");
|
|
|
|
|
|
Extend the HLSL mirror comparison to one struct return shape
HlslSourceCompatibilityTests previously flattened only bare vector returns to
scalar leaves. Values() now recurses through struct fields of any type, so a
struct return (invariant 8's one shape, e.g. CultCellular) is compared field
by field down to bit-for-bit scalars, exactly like a vector return already
was. Components() stays scoped to scalar-typed fields, since it only ever
builds function arguments and must not recurse.
TransformHlslToCSharp gains transformation 5: struct fields gain `public`,
since HLSL has no field access-modifier concept and a C# struct's fields
default to private, which would hide them from Values()' reflection. The old
wrap-in-a-class transformation is renumbered 6. design.md's transformation
list documents both, matching design.md:92's numbered list.
StructReturnComparisonRejectsAMismatchedField is the extension's own test: two
hand-built structs with the same shape (mirroring a real mirror-vs-C#
comparison) prove the field-by-field, bit-for-bit walk actually rejects a
struct whose leaf field differs, or whose nested vector's own component
differs, not just a length check.
EveryMirrorFunctionMatchesCSharpMath and HlslMirrorTests' required-symbol list
now cover cultmath_smin_grad and cultmath_cellular; the mirrored-function
count moves from 28 to 30.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 19:18:21 +02:00
|
|
|
// 5. Struct fields gain `public`; HLSL has no field access-modifier concept, and a C# struct's
|
|
|
|
|
// fields default to private, which would hide them from the mirror test's reflection-based
|
|
|
|
|
// field comparison (invariant 8's one struct return shape, e.g. CultCellular).
|
|
|
|
|
source = Regex.Replace(source, @"(?ms)(struct\s+\w+\s*\{)(.*?)(\};)", m =>
|
|
|
|
|
m.Groups[1].Value + Regex.Replace(m.Groups[2].Value, @"(?m)^(\s*)(\w+\s+\w+;)", "$1public $2") + m.Groups[3].Value);
|
|
|
|
|
|
|
|
|
|
// 6. The file body is wrapped in a class; C# has no free functions.
|
2026-09-14 13:57:03 +02:00
|
|
|
return "using CultMath;\nusing static CultMath.math;\nnamespace CultMathHlsl;\npublic class HlslShader\n{\n" + source + "\n}\n";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static (Assembly? Assembly, IReadOnlyList<string> Errors) CompileShaderMirror()
|
|
|
|
|
{
|
CultMath: Phacelle port moves into MPL-2.0 files; prune cut pinned by boundary points
MPL-2.0 is file-level and the Phacelle port sat inside MIT files. phacelle and
CultPhasor now live in math.Phacelle.cs and CultPhasor.cs, and cultmath_phacelle
in CultMath.Phacelle.hlsl (included by CultMath.hlsl, both tracked copies), each
with the MPL header and Johansen / Luke Mitchell attribution. math stays one
partial class, so math.phacelle is called exactly as before. The GameCult.Math
package declares MIT AND MPL-2.0 and packs THIRD-PARTY-NOTICES.md and the new
include; the Unity LICENSE.md points at CultLib; build-unity-package.ps1
requires the new include.
The random sweeps never met a cell whose box bound sits just under the 2.25 cut
while its weight is still nonzero, so cuts of 2.245, 2.24 and 2.2 survived.
Six hard-coded points (found by an offline search) sit next to such cells; the
C# pruned-vs-unpruned test and the HLSL mirror test both use them, with a
witness check that re-derives the property from the hash.
Docs: the prune keeps about 45 of 64 cells (14 is the nonzero-weight count),
the support radius is 1.49998 (d^2 = 2.24995), normalization's range and
saturation, W = 0 only for adversarial hashes, and the support-edge gradient
seam size.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 00:34:27 +02:00
|
|
|
var hlsl = ReadShaderSource(FindCultMathRoot());
|
2026-09-14 13:57:03 +02:00
|
|
|
var tree = CSharpSyntaxTree.ParseText(TransformHlslToCSharp(hlsl));
|
|
|
|
|
var references = ((string)AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES")!)
|
|
|
|
|
.Split(Path.PathSeparator)
|
|
|
|
|
.Append(typeof(math).Assembly.Location)
|
|
|
|
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
|
|
|
|
.Select(path => MetadataReference.CreateFromFile(path));
|
|
|
|
|
var compilation = CSharpCompilation.Create(
|
|
|
|
|
"CultMathHlslMirror",
|
|
|
|
|
new[] { tree },
|
|
|
|
|
references,
|
|
|
|
|
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
|
|
|
|
|
|
|
|
|
|
using var stream = new MemoryStream();
|
|
|
|
|
var result = compilation.Emit(stream);
|
|
|
|
|
var errors = result.Diagnostics
|
|
|
|
|
.Where(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
|
|
|
|
|
.Select(diagnostic => diagnostic.ToString())
|
|
|
|
|
.ToList();
|
|
|
|
|
return (result.Success ? Assembly.Load(stream.ToArray()) : null, errors);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string FindCultMathRoot()
|
|
|
|
|
{
|
|
|
|
|
var directory = new DirectoryInfo(AppContext.BaseDirectory);
|
|
|
|
|
while (directory is not null && !File.Exists(Path.Combine(directory.FullName, "packages", "cultmath", "shaders", "CultMath.hlsl")))
|
|
|
|
|
{
|
|
|
|
|
directory = directory.Parent;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Path.Combine(directory?.FullName ?? throw new InvalidOperationException("CultMath package root not found."), "packages", "cultmath");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---- Hand-written HLSL-shaped host code: swizzles, mixed constructors, matrix rows. ----
|
Make CultMath HLSL-shaped: writable components, bool-vector comparisons
HLSL is now CultMath's semantic authority where it defines one.
- float2/3/4, int2, double2/3, bool2 and quaternion are plain structs with
public mutable fields instead of record structs. Indexers and swizzles
(float2.yx; float3 xy xz yx yz zy xyz xzy yzx zyx; float4 xy xyz xyw yzx)
have setters.
- ==, !=, <, >, <=, >= are component-wise and return bool2/3/4. Equals and
GetHashCode stay value-based so vectors remain dictionary keys.
quaternion is not an HLSL type and keeps a bool ==.
- Scalar/vector arithmetic and comparisons get explicit overloads on both
sides instead of leaning on implicit splat.
- New types: bool3, bool4, int3, int4, float2x2, float3x3 (row-major like
HLSL, _mRC fields, Rotate / RotateX/Y/Z / Euler with math.RotationOrder).
- New intrinsics: mul (matrix-vector both sides, matrix-matrix), any, all,
select (HLSL argument order), log, vector pow/sqrt/sin/cos/acos/atan2,
double min/max, int3/int4/bool3/bool4/float2x2/float3x3 constructor
functions.
- simplex_noise is renamed snoise and gains the Ashima 3D variant; the HLSL
mirror follows as cultmath_snoise(float2/float3).
- Random gains NextFloat3() and NextFloat3(float3 max) on the same draw order.
Tests pin snoise(float3), float3x3.Euler and float2x2.Rotate to
Unity.Mathematics reference values, and an HLSL-shaped source block compiles
under using static CultMath.math.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 13:29:59 +02:00
|
|
|
static float3 shade(float3 position, float3 normal, float2 wind, float time)
|
|
|
|
|
{
|
|
|
|
|
float3 p = position;
|
|
|
|
|
p.xz = p.xz + wind * time;
|
|
|
|
|
p.y = saturate(p.y);
|
|
|
|
|
|
|
|
|
|
float3x3 basis = float3x3(float3(1, 0, 0), float3(0, 1, 0), float3(0, 0, 1));
|
2026-09-14 13:57:03 +02:00
|
|
|
basis[1][1] = 1.0f;
|
Make CultMath HLSL-shaped: writable components, bool-vector comparisons
HLSL is now CultMath's semantic authority where it defines one.
- float2/3/4, int2, double2/3, bool2 and quaternion are plain structs with
public mutable fields instead of record structs. Indexers and swizzles
(float2.yx; float3 xy xz yx yz zy xyz xzy yzx zyx; float4 xy xyz xyw yzx)
have setters.
- ==, !=, <, >, <=, >= are component-wise and return bool2/3/4. Equals and
GetHashCode stay value-based so vectors remain dictionary keys.
quaternion is not an HLSL type and keeps a bool ==.
- Scalar/vector arithmetic and comparisons get explicit overloads on both
sides instead of leaning on implicit splat.
- New types: bool3, bool4, int3, int4, float2x2, float3x3 (row-major like
HLSL, _mRC fields, Rotate / RotateX/Y/Z / Euler with math.RotationOrder).
- New intrinsics: mul (matrix-vector both sides, matrix-matrix), any, all,
select (HLSL argument order), log, vector pow/sqrt/sin/cos/acos/atan2,
double min/max, int3/int4/bool3/bool4/float2x2/float3x3 constructor
functions.
- simplex_noise is renamed snoise and gains the Ashima 3D variant; the HLSL
mirror follows as cultmath_snoise(float2/float3).
- Random gains NextFloat3() and NextFloat3(float3 max) on the same draw order.
Tests pin snoise(float3), float3x3.Euler and float2x2.Rotate to
Unity.Mathematics reference values, and an HLSL-shaped source block compiles
under using static CultMath.math.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 13:29:59 +02:00
|
|
|
float3 n = normalize(mul(basis, normal));
|
|
|
|
|
float ndotl = saturate(dot(n, normalize(float3(0.3f, 1, 0.2f))));
|
2026-09-14 13:57:03 +02:00
|
|
|
float4 color = float4(lerp(float3(0.1f, 0.1f, 0.2f), float3(1, 0.9f, 0.7f), ndotl), 1);
|
Make CultMath HLSL-shaped: writable components, bool-vector comparisons
HLSL is now CultMath's semantic authority where it defines one.
- float2/3/4, int2, double2/3, bool2 and quaternion are plain structs with
public mutable fields instead of record structs. Indexers and swizzles
(float2.yx; float3 xy xz yx yz zy xyz xzy yzx zyx; float4 xy xyz xyw yzx)
have setters.
- ==, !=, <, >, <=, >= are component-wise and return bool2/3/4. Equals and
GetHashCode stay value-based so vectors remain dictionary keys.
quaternion is not an HLSL type and keeps a bool ==.
- Scalar/vector arithmetic and comparisons get explicit overloads on both
sides instead of leaning on implicit splat.
- New types: bool3, bool4, int3, int4, float2x2, float3x3 (row-major like
HLSL, _mRC fields, Rotate / RotateX/Y/Z / Euler with math.RotationOrder).
- New intrinsics: mul (matrix-vector both sides, matrix-matrix), any, all,
select (HLSL argument order), log, vector pow/sqrt/sin/cos/acos/atan2,
double min/max, int3/int4/bool3/bool4/float2x2/float3x3 constructor
functions.
- simplex_noise is renamed snoise and gains the Ashima 3D variant; the HLSL
mirror follows as cultmath_snoise(float2/float3).
- Random gains NextFloat3() and NextFloat3(float3 max) on the same draw order.
Tests pin snoise(float3), float3x3.Euler and float2x2.Rotate to
Unity.Mathematics reference values, and an HLSL-shaped source block compiles
under using static CultMath.math.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 13:29:59 +02:00
|
|
|
|
|
|
|
|
if (any(p < 0.0f))
|
2026-09-14 13:57:03 +02:00
|
|
|
color.rgb *= 0.5f;
|
Make CultMath HLSL-shaped: writable components, bool-vector comparisons
HLSL is now CultMath's semantic authority where it defines one.
- float2/3/4, int2, double2/3, bool2 and quaternion are plain structs with
public mutable fields instead of record structs. Indexers and swizzles
(float2.yx; float3 xy xz yx yz zy xyz xzy yzx zyx; float4 xy xyz xyw yzx)
have setters.
- ==, !=, <, >, <=, >= are component-wise and return bool2/3/4. Equals and
GetHashCode stay value-based so vectors remain dictionary keys.
quaternion is not an HLSL type and keeps a bool ==.
- Scalar/vector arithmetic and comparisons get explicit overloads on both
sides instead of leaning on implicit splat.
- New types: bool3, bool4, int3, int4, float2x2, float3x3 (row-major like
HLSL, _mRC fields, Rotate / RotateX/Y/Z / Euler with math.RotationOrder).
- New intrinsics: mul (matrix-vector both sides, matrix-matrix), any, all,
select (HLSL argument order), log, vector pow/sqrt/sin/cos/acos/atan2,
double min/max, int3/int4/bool3/bool4/float2x2/float3x3 constructor
functions.
- simplex_noise is renamed snoise and gains the Ashima 3D variant; the HLSL
mirror follows as cultmath_snoise(float2/float3).
- Random gains NextFloat3() and NextFloat3(float3 max) on the same draw order.
Tests pin snoise(float3), float3x3.Euler and float2x2.Rotate to
Unity.Mathematics reference values, and an HLSL-shaped source block compiles
under using static CultMath.math.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 13:29:59 +02:00
|
|
|
if (all(abs(n) <= 1.0f))
|
2026-09-14 13:57:03 +02:00
|
|
|
color.bgr = color.bgr * float3(1, 1, 1);
|
Make CultMath HLSL-shaped: writable components, bool-vector comparisons
HLSL is now CultMath's semantic authority where it defines one.
- float2/3/4, int2, double2/3, bool2 and quaternion are plain structs with
public mutable fields instead of record structs. Indexers and swizzles
(float2.yx; float3 xy xz yx yz zy xyz xzy yzx zyx; float4 xy xyz xyw yzx)
have setters.
- ==, !=, <, >, <=, >= are component-wise and return bool2/3/4. Equals and
GetHashCode stay value-based so vectors remain dictionary keys.
quaternion is not an HLSL type and keeps a bool ==.
- Scalar/vector arithmetic and comparisons get explicit overloads on both
sides instead of leaning on implicit splat.
- New types: bool3, bool4, int3, int4, float2x2, float3x3 (row-major like
HLSL, _mRC fields, Rotate / RotateX/Y/Z / Euler with math.RotationOrder).
- New intrinsics: mul (matrix-vector both sides, matrix-matrix), any, all,
select (HLSL argument order), log, vector pow/sqrt/sin/cos/acos/atan2,
double min/max, int3/int4/bool3/bool4/float2x2/float3x3 constructor
functions.
- simplex_noise is renamed snoise and gains the Ashima 3D variant; the HLSL
mirror follows as cultmath_snoise(float2/float3).
- Random gains NextFloat3() and NextFloat3(float3 max) on the same draw order.
Tests pin snoise(float3), float3x3.Euler and float2x2.Rotate to
Unity.Mathematics reference values, and an HLSL-shaped source block compiles
under using static CultMath.math.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 13:29:59 +02:00
|
|
|
|
2026-09-14 13:57:03 +02:00
|
|
|
color.rgb = select(p > 10.0f, float3(0), color.rgb);
|
Make CultMath HLSL-shaped: writable components, bool-vector comparisons
HLSL is now CultMath's semantic authority where it defines one.
- float2/3/4, int2, double2/3, bool2 and quaternion are plain structs with
public mutable fields instead of record structs. Indexers and swizzles
(float2.yx; float3 xy xz yx yz zy xyz xzy yzx zyx; float4 xy xyz xyw yzx)
have setters.
- ==, !=, <, >, <=, >= are component-wise and return bool2/3/4. Equals and
GetHashCode stay value-based so vectors remain dictionary keys.
quaternion is not an HLSL type and keeps a bool ==.
- Scalar/vector arithmetic and comparisons get explicit overloads on both
sides instead of leaning on implicit splat.
- New types: bool3, bool4, int3, int4, float2x2, float3x3 (row-major like
HLSL, _mRC fields, Rotate / RotateX/Y/Z / Euler with math.RotationOrder).
- New intrinsics: mul (matrix-vector both sides, matrix-matrix), any, all,
select (HLSL argument order), log, vector pow/sqrt/sin/cos/acos/atan2,
double min/max, int3/int4/bool3/bool4/float2x2/float3x3 constructor
functions.
- simplex_noise is renamed snoise and gains the Ashima 3D variant; the HLSL
mirror follows as cultmath_snoise(float2/float3).
- Random gains NextFloat3() and NextFloat3(float3 max) on the same draw order.
Tests pin snoise(float3), float3x3.Euler and float2x2.Rotate to
Unity.Mathematics reference values, and an HLSL-shaped source block compiles
under using static CultMath.math.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 13:29:59 +02:00
|
|
|
float2 uv = frac(p.xz * 0.25f);
|
2026-09-14 13:57:03 +02:00
|
|
|
color.rg = lerp(color.rg, uv, 0.1f);
|
|
|
|
|
return float4(color.xy, color.zw).xyz;
|
Make CultMath HLSL-shaped: writable components, bool-vector comparisons
HLSL is now CultMath's semantic authority where it defines one.
- float2/3/4, int2, double2/3, bool2 and quaternion are plain structs with
public mutable fields instead of record structs. Indexers and swizzles
(float2.yx; float3 xy xz yx yz zy xyz xzy yzx zyx; float4 xy xyz xyw yzx)
have setters.
- ==, !=, <, >, <=, >= are component-wise and return bool2/3/4. Equals and
GetHashCode stay value-based so vectors remain dictionary keys.
quaternion is not an HLSL type and keeps a bool ==.
- Scalar/vector arithmetic and comparisons get explicit overloads on both
sides instead of leaning on implicit splat.
- New types: bool3, bool4, int3, int4, float2x2, float3x3 (row-major like
HLSL, _mRC fields, Rotate / RotateX/Y/Z / Euler with math.RotationOrder).
- New intrinsics: mul (matrix-vector both sides, matrix-matrix), any, all,
select (HLSL argument order), log, vector pow/sqrt/sin/cos/acos/atan2,
double min/max, int3/int4/bool3/bool4/float2x2/float3x3 constructor
functions.
- simplex_noise is renamed snoise and gains the Ashima 3D variant; the HLSL
mirror follows as cultmath_snoise(float2/float3).
- Random gains NextFloat3() and NextFloat3(float3 max) on the same draw order.
Tests pin snoise(float3), float3x3.Euler and float2x2.Rotate to
Unity.Mathematics reference values, and an HLSL-shaped source block compiles
under using static CultMath.math.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 13:29:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static float2 steer(float2 direction, float torque)
|
|
|
|
|
{
|
|
|
|
|
float2x2 turn = float2x2(cos(torque), -sin(torque), sin(torque), cos(torque));
|
|
|
|
|
float2 turned = mul(direction, turn);
|
|
|
|
|
return normalize(turned) * pow(length(turned), 1.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
2026-09-14 13:57:03 +02:00
|
|
|
public void HandWrittenShaderStyleMathEvaluates()
|
Make CultMath HLSL-shaped: writable components, bool-vector comparisons
HLSL is now CultMath's semantic authority where it defines one.
- float2/3/4, int2, double2/3, bool2 and quaternion are plain structs with
public mutable fields instead of record structs. Indexers and swizzles
(float2.yx; float3 xy xz yx yz zy xyz xzy yzx zyx; float4 xy xyz xyw yzx)
have setters.
- ==, !=, <, >, <=, >= are component-wise and return bool2/3/4. Equals and
GetHashCode stay value-based so vectors remain dictionary keys.
quaternion is not an HLSL type and keeps a bool ==.
- Scalar/vector arithmetic and comparisons get explicit overloads on both
sides instead of leaning on implicit splat.
- New types: bool3, bool4, int3, int4, float2x2, float3x3 (row-major like
HLSL, _mRC fields, Rotate / RotateX/Y/Z / Euler with math.RotationOrder).
- New intrinsics: mul (matrix-vector both sides, matrix-matrix), any, all,
select (HLSL argument order), log, vector pow/sqrt/sin/cos/acos/atan2,
double min/max, int3/int4/bool3/bool4/float2x2/float3x3 constructor
functions.
- simplex_noise is renamed snoise and gains the Ashima 3D variant; the HLSL
mirror follows as cultmath_snoise(float2/float3).
- Random gains NextFloat3() and NextFloat3(float3 max) on the same draw order.
Tests pin snoise(float3), float3x3.Euler and float2x2.Rotate to
Unity.Mathematics reference values, and an HLSL-shaped source block compiles
under using static CultMath.math.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 13:29:59 +02:00
|
|
|
{
|
2026-09-14 13:57:03 +02:00
|
|
|
var color = shade(float3(1.0f, 0.5f, 2.0f), float3(0.0f, 1.0f, 0.0f), float2(1.0f, 0.0f), 0.0f);
|
Make CultMath HLSL-shaped: writable components, bool-vector comparisons
HLSL is now CultMath's semantic authority where it defines one.
- float2/3/4, int2, double2/3, bool2 and quaternion are plain structs with
public mutable fields instead of record structs. Indexers and swizzles
(float2.yx; float3 xy xz yx yz zy xyz xzy yzx zyx; float4 xy xyz xyw yzx)
have setters.
- ==, !=, <, >, <=, >= are component-wise and return bool2/3/4. Equals and
GetHashCode stay value-based so vectors remain dictionary keys.
quaternion is not an HLSL type and keeps a bool ==.
- Scalar/vector arithmetic and comparisons get explicit overloads on both
sides instead of leaning on implicit splat.
- New types: bool3, bool4, int3, int4, float2x2, float3x3 (row-major like
HLSL, _mRC fields, Rotate / RotateX/Y/Z / Euler with math.RotationOrder).
- New intrinsics: mul (matrix-vector both sides, matrix-matrix), any, all,
select (HLSL argument order), log, vector pow/sqrt/sin/cos/acos/atan2,
double min/max, int3/int4/bool3/bool4/float2x2/float3x3 constructor
functions.
- simplex_noise is renamed snoise and gains the Ashima 3D variant; the HLSL
mirror follows as cultmath_snoise(float2/float3).
- Random gains NextFloat3() and NextFloat3(float3 max) on the same draw order.
Tests pin snoise(float3), float3x3.Euler and float2x2.Rotate to
Unity.Mathematics reference values, and an HLSL-shaped source block compiles
under using static CultMath.math.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 13:29:59 +02:00
|
|
|
Assert.True(color.x > 0.1f && color.x <= 1.0f);
|
|
|
|
|
Assert.True(float.IsFinite(color.y) && float.IsFinite(color.z));
|
|
|
|
|
|
2026-09-14 13:57:03 +02:00
|
|
|
var far = shade(float3(20.0f, 20.0f, 20.0f), float3(0.0f, 1.0f, 0.0f), float2(0.0f), 0.0f);
|
Make CultMath HLSL-shaped: writable components, bool-vector comparisons
HLSL is now CultMath's semantic authority where it defines one.
- float2/3/4, int2, double2/3, bool2 and quaternion are plain structs with
public mutable fields instead of record structs. Indexers and swizzles
(float2.yx; float3 xy xz yx yz zy xyz xzy yzx zyx; float4 xy xyz xyw yzx)
have setters.
- ==, !=, <, >, <=, >= are component-wise and return bool2/3/4. Equals and
GetHashCode stay value-based so vectors remain dictionary keys.
quaternion is not an HLSL type and keeps a bool ==.
- Scalar/vector arithmetic and comparisons get explicit overloads on both
sides instead of leaning on implicit splat.
- New types: bool3, bool4, int3, int4, float2x2, float3x3 (row-major like
HLSL, _mRC fields, Rotate / RotateX/Y/Z / Euler with math.RotationOrder).
- New intrinsics: mul (matrix-vector both sides, matrix-matrix), any, all,
select (HLSL argument order), log, vector pow/sqrt/sin/cos/acos/atan2,
double min/max, int3/int4/bool3/bool4/float2x2/float3x3 constructor
functions.
- simplex_noise is renamed snoise and gains the Ashima 3D variant; the HLSL
mirror follows as cultmath_snoise(float2/float3).
- Random gains NextFloat3() and NextFloat3(float3 max) on the same draw order.
Tests pin snoise(float3), float3x3.Euler and float2x2.Rotate to
Unity.Mathematics reference values, and an HLSL-shaped source block compiles
under using static CultMath.math.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 13:29:59 +02:00
|
|
|
Assert.Equal(0.0f, far.z);
|
|
|
|
|
|
2026-09-14 13:57:03 +02:00
|
|
|
var steered = steer(float2(1.0f, 0.0f), 0.5f);
|
|
|
|
|
var viaRotate = mul(float2(1.0f, 0.0f), CultMath.float2x2.Rotate(0.5f));
|
Make CultMath HLSL-shaped: writable components, bool-vector comparisons
HLSL is now CultMath's semantic authority where it defines one.
- float2/3/4, int2, double2/3, bool2 and quaternion are plain structs with
public mutable fields instead of record structs. Indexers and swizzles
(float2.yx; float3 xy xz yx yz zy xyz xzy yzx zyx; float4 xy xyz xyw yzx)
have setters.
- ==, !=, <, >, <=, >= are component-wise and return bool2/3/4. Equals and
GetHashCode stay value-based so vectors remain dictionary keys.
quaternion is not an HLSL type and keeps a bool ==.
- Scalar/vector arithmetic and comparisons get explicit overloads on both
sides instead of leaning on implicit splat.
- New types: bool3, bool4, int3, int4, float2x2, float3x3 (row-major like
HLSL, _mRC fields, Rotate / RotateX/Y/Z / Euler with math.RotationOrder).
- New intrinsics: mul (matrix-vector both sides, matrix-matrix), any, all,
select (HLSL argument order), log, vector pow/sqrt/sin/cos/acos/atan2,
double min/max, int3/int4/bool3/bool4/float2x2/float3x3 constructor
functions.
- simplex_noise is renamed snoise and gains the Ashima 3D variant; the HLSL
mirror follows as cultmath_snoise(float2/float3).
- Random gains NextFloat3() and NextFloat3(float3 max) on the same draw order.
Tests pin snoise(float3), float3x3.Euler and float2x2.Rotate to
Unity.Mathematics reference values, and an HLSL-shaped source block compiles
under using static CultMath.math.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 13:29:59 +02:00
|
|
|
Assert.Equal(viaRotate.x, steered.x, precision: 5);
|
|
|
|
|
Assert.Equal(viaRotate.y, steered.y, precision: 5);
|
|
|
|
|
}
|
|
|
|
|
}
|