Commit graph CultLib/packages/cultmath/tests
Author SHA1 Message Date
Meta Vens
6a2b42f650 Pin mtp test-runner for CultMath and document mutation testing
CultMath.Tests is on xunit.v3. Stryker's default vstest runner cannot
capture coverage against xunit.v3's test host and hangs retrying test
sessions indefinitely (observed: 2096 mutants queued, zero completed after
~9 minutes of retries). Stryker's Microsoft.Testing.Platform runner
(-t mtp, still preview) works correctly against xunit.v3 and completed the
same 1351-mutant run in ~4.5 minutes. Pin it in config so a plain
`dotnet stryker` works without a CLI flag.

Add docs/mutation-testing.md: how to run it, the --since form for a single
cut, the triage rules, a known coverage-capture caveat in the mtp runner
(confirmed by hand-mutation against static readonly fields), and the
packages still needing their own config.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 21:24:05 +02:00
Meta Vens
e92413d6eb Add Stryker config for CultMath and ignore its output
Scope mutation testing to CultMath.Tests only, matching Aetheria's
stryker-config.json shape: Standard level, progress/html/json reporters,
break threshold 0 (score is not a gate). Mutate only CultMath's own
sources, excluding the generated Swizzles.g.cs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 20:50:10 +02:00
Meta Vens
b688cb2091 Cut 12.0: test erf and erfinv behaviour
Covers the pinned accuracy bar against tabulated erf values (0, +-0.5,
+-1, +-2, +-3, within 2e-6), odd symmetry, the +-1 limits, erfinv's
round trip through erf over y in [-0.999, 0.999], and erfinv monotonicity.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 20:42:57 +02:00
Meta Vens
0f2c1f04ef 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
Meta Vens
111b918ec0 Make CultMath normalize follow dxc and release 0.2.2
dxc 1.9.0.5402 lowers float2/3/4 normalize(x) to x * Rsqrt(Dot(x, x)),
and DXIL.rst defines Rsqrt as 1 / sqrt(src). A zero vector normalizes
to NaN and a NaN component poisons every component. CultMath divided
by max(length, 1e-20), which returned 0 for a zero vector and a huge
finite component beside a NaN one.

- math.normalize(float2/3/4) is now value * (1 / sqrt(dot(value, value))).
- The HLSL mirror drops cultmath_normalize and its epsilon constant: it
  now equals the normalize intrinsic, and nothing consumed it.
- HlslSemanticsTests pins zero and NaN normalize, the rsqrt bit pattern,
  and zero length/distance. design.md states the rule and notes that
  DXIL.rst's Rsqrt special-value table is a copy of Round's.
- GameCult.Geometry's iso-surface gave degenerate triangles (an inside
  vertex on the isovalue) a zero normal through the old epsilon. It now
  guards them explicitly, with a test.
- quaternion normalize keeps its identity fallback: quaternion is not
  an HLSL type.
- Bump to 0.2.2 and rebuild the tracked Unity DLL and pdb.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 18:04:04 +02:00
Meta Vens
7821e06d55 Add HLSL vector conversions, int clamp, and double frac to CultMath
Aetheria Cut 8a needs int2(float2), clamp(int, int, int), and
frac(double). Fill them in CultMath as dxc compiles them:

- generate-swizzles.ps1 emits same-size conversion constructors and
  math functions between float, int, and bool vectors. dxc lowers
  float to int as fptosi (toward zero; NaN and out-of-range undefined),
  numeric to bool as != 0 (fcmp une, NaN is true), bool to numeric as
  0 or 1. floatN(int) splat keeps floatN(0) from going ambiguous
  against floatN(intN).
- Int and int-vector clamp are min(max(x, a), b), matching dxc's
  IMin(IMax(x, a), b); inverted bounds return b.
- frac(double) keeps double precision. dxc demotes double frac to
  float, as it does exp and lerp; CultMath's existing double overloads
  already diverge that way, and design.md now says so.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 16:16:32 +02:00
Meta Vens
d4c43ed265 Say where intrinsic rules are pinned and that NaN does not propagate
The mirror compiles under using static CultMath.math, so it proves composition,
not intrinsic rules; HlslSemanticsTests alone pins those. design.md also states
where FMin/FMax/Saturate turn NaN or infinity into numbers and tells callers to
check isnan first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 14:47:10 +02:00
Meta Vens
19e23db211 Make double min/max follow DXIL FMin/FMax
dxc lowers double min/max to dx.op.binary.f64 opcodes 36/35 (FMin/FMax),
the same operations as float, so Math.Min/Math.Max (NaN-propagating) was the
wrong rule. Double min/max now use the float formulas, with NaN and signed-zero
assertions beside the float ones. Tracked Unity DLL rebuilt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 14:47:10 +02:00
Meta Vens
08e213dd7c Compare mirror results by bit pattern and say what that proves
SequenceEqual on floats treats -0 as 0. The mirror test now compares
SingleToInt32Bits, with any NaN equal to any NaN. Its summary and
design.md now say the test proves CultMath.hlsl's text computes the same
float32 results as C# math on the CPU, not that a GPU agrees: driver sin
precision alone makes cultmath_hash and value noise differ on hardware.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 14:32:43 +02:00
Meta Vens
9407da0461 Match float min, max, clamp, and saturate to DXIL FMin/FMax/Saturate
dxc lowers float min/max/clamp to dx.op FMin/FMax (36/35) and saturate to
dx.op Saturate (7). DXIL.rst defines them: FMin(a, b) is a < b ? a : b,
FMax(a, b) is a >= b ? a : b, a NaN operand returns the other, and
Saturate is FMin(1, FMax(0, x)). C# used MathF.Min/Max, which pass NaN
through and order signed zeros, so saturate(NaN) was NaN instead of 0.

Every float overload now follows the spec, normalize uses the same max as
its mirror, and the mirror's saturate spells Saturate's operand order
(which maps -0 to +0, where clamp(x, 0, 1) keeps -0). Non-NaN results
change only in the sign of a zero. design.md records the rules and that
dxc marks float math fast, so GPU NaN results are not guaranteed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 14:32:19 +02:00
Meta Vens
2940ca0944 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
Meta Vens
e0a054a7b4 Match step's NaN result to what dxc emits
dxc 1.9.0.5402 (dxc_2026_07_29, lib_6_3, -HV 2021, -Od) lowers step(e, x) to
fcmp olt x, e; select 0 : 1, so NaN on either side gives 1. The previous
x >= e ? 1 : 0 rule came from prose, not the compiler. math.step and
cultmath_step now use x < e ? 0 : 1 for every overload; the mirror's
cultmath_step compiles to the same fcmp/select as the intrinsic. Unity shader
copy synced; DLL rebuilt and byte-checked by build-unity-package.ps1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 14:09:49 +02:00
Meta Vens
360176a7da Compile the real HLSL mirror as C# and with dxc; fix its errors
HlslSourceCompatibilityTests now reads shaders/CultMath.hlsl, applies only
the documented transformations (drop include guards, drop Texture2D/
SamplerState functions, file-scope static const -> const, f literal
suffixes, wrap in a class), and compiles it with Roslyn against CultMath.
Any compile error fails the test (probed with an undeclared call). The
compiled cultmath_snoise(float3/float2) also matches math.snoise.

dxc -T lib_6_3 -HV 2021 rejected the mirror: cultmath_snoise(float2) called
the undeclared cultmath_simplex_mod289/permute. Renamed to the declared
cultmath_snoise_* helpers. Vector const locals became plain locals (C# const
is primitives only), and cultmath_step uses value >= edge like HLSL step and
the C# mirror. dxc 1.9.0.5402 now compiles the file cleanly. The Unity
package's Shaders copy is synced.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 13:57:03 +02:00
Meta Vens
5d8ef0c3de Generate every HLSL swizzle and mixed constructor; fix intrinsic types
tools/generate-swizzles.ps1 writes the checked-in src/CultMath/Swizzles.g.cs
as partial structs plus a partial math: for float2/3/4, int2/3/4 and bool2/3/4
every swizzle of length 2-4 including repeats, in xyzw and rgba naming, with
setters only on non-repeating masks, plus single-component r/g/b/a. double2/3
get lengths 2-3 (there is no double4). Every HLSL mixed constructor
(float4(float2, float2), float4(float, float2, float), copy forms, ...) exists
as a struct constructor and, for float/int/bool, a math function. The
hand-written swizzles and mixed constructors it supersedes are deleted.

Matrices store private rows and return ref float2/float3 from the row
indexer ([UnscopedRef], polyfilled for netstandard2.1), so m[1][2] = s
compiles as in HLSL; _mRC are get/set properties over the rows. Equality and
hashing stay value-based.

Intrinsics follow HLSL: sign returns int/intN and yields 0 for NaN; step is
x >= y ? 1 : 0 so NaN yields 0; any/all accept float and int vectors;
min/max/abs on int vectors return int vectors, and abs(int) returns int.

Strips the UTF-8 BOMs this branch added to math.cs, CultMath.csproj and
HlslSemanticsTests.cs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 13:57:02 +02:00
Meta Vens
d514efebba 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
Meta Vens
ea88cfcd05 Move CultMath into the CultLib monorepo 2026-08-26 17:35:51 +02:00