math.fbm(p, octaves, lacunarity, gain) and math.fbm(p) (four octaves, lacunarity 2, gain 0.5);
cultmath_fbm in the shaders, the GLSL regenerated by GlslLowering, a golden-fixture family
appended, FbmTests for the rules.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
power_border_step/power_border give the exact distance to the owner's power cell boundary: the least radical-plane
distance over every other site, built from the offset and (w1-w2)(w1+w2) so it does not cancel. site_cells_border is
documented as the two-winner bisector distance. Cell spans refuse non-finite positions, queries and spans over 2^24.
segment_closest is public in C#; the HLSL segment functions are 3D only (no new overload sets). Tests added.
Does not build the GLSL checks: CultMath.glsl and glsl-parity.json are regenerated in the next commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cyclic non-strict step(x0.yzx, x0.xyz) made the four corners a non-simplex
where x0's components are equal: the origin and every (t, t, t) read a different
function (snoise(0) was -0.4358730, jumps up to 0.77). One strict compare, z
against x, gives simplexnoise1234's order. Off the tie set old and new return
the same bits.
NoiseGradTests.SnoiseIsContinuousOnTheSimplexDiagonal fails against the old
rule (0.054 within 1 ulp of (-53.6, -53.6, -53.6)). The origin golden is 0 and a
(1.25, 1.25, 1.25) diagonal row joins it. SNOISE_LIPSCHITZ and the GLSL follow
in the next commits, so the ratio pin and CommittedGlslEqualsLowering are red here.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
math.Interval.cs and shaders/CultMath.Interval.hlsl (included from CultMath.hlsl after Phacelle;
both tracked copies) add iv_point, iv_add, iv_sub, iv_neg, iv_mul, iv_scale, iv_abs, iv_min,
iv_max, iv_sqr, iv_sqrt, iv_exp, iv_clamp, iv_saturate, iv_smoothstep, iv_lerp, iv_snoise_ball
and iv_fbm_ball, and SNOISE_LIPSCHITZ = 10.099261 (1.10 x the refined max |snoise_grad| that
NoiseBoundTests.MeasureLipschitz prints). iv_exp and iv_smoothstep widen by one ulp through
asfloat(uint), added beside asuint as the HLSL intrinsic. The mirror test now compares 52
functions; the Unity package and the NuGet content files carry the new include.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Kernel radius^2 0.6 -> 0.5, scale 42 -> 105, permute (34x+1)x -> (34x+10)x, per
stegu/webgl-noise 22434e04d7 (fix 21d9fe23d7, scale ff3b5d34ea, permute a3e6d57095).
snoise_grad, fbm_grad, ridged_grad follow. C#, both HLSL copies, design.md and
THIRD-PARTY-NOTICES.md change together. The 2D snoise shares the permute helper, so
its values change too. Golden values are regenerated in the next commit.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The doc comments named test classes/methods (SnoiseGradTests, FbmGradTests,
RidgedGradTests) that were never committed under those names; the actual
coverage lives in NoiseGradTests. Point the comments at the real methods.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Analytic-gradient simplex noise, ported from webgl-noise's
noise3Dgrad.glsl differentiation but retargeted onto this repo's own
snoise(float3) constants (0.6 falloff radius, +1 permute offset, 42
scale) so snoise_grad.w stays exactly snoise's own field. fbm_grad and
ridged_grad are octave sums with per-octave frequency/amplitude
compounding (invariant 8). octaves is clamped to [0, 16] so the HLSL
mirror test's full-int32-range oracle cannot turn a garbage octave
count into a multi-billion-iteration loop.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Soul pass 3 confirmed two documentation defects: the F6 search-radius proof
misstated 3 - 2m + 3m^2 at m=1/2 as 2.5 (actual 2.75; true min on [0,1/2] is
8/3 at m=1/3), and both math.cs and design.md cited a fabricated 203.8
chi-square statistic for float-bit hashing that Soul could not reproduce
(6+ seeds scored 7.7-30.9). Corrected the arithmetic and replaced the
unreproducible measurement with what actually pins integer hashing: the
oracle and mirror tests, and the chi-square test's real scope (the hash as
cellular calls it, not the call site itself). No test or code changed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Soul (cut 2a-i, pass 2) found two defects in cellular's own math.cs source
and its HLSL mirror:
- cellular hashed pcg3d/pcg4d over the FLOAT32 BIT PATTERN of each
integer-valued cell coordinate (pcg3d(neighbor), pcg4d(float4(cellId, 0)))
instead of the integer coordinate itself. Two adjacent cells' bit patterns
share more structure (same exponent, mantissa one increment apart) than
the integers do going into pcg3d's own mixing, and that structure survived
into the jitter: measured chi-square 203.8 for jitter.x over 1,000,000
cells against 15 degrees of freedom (critical value 37.7). Both call sites
now hash int3(neighbor)/int4(int3(cellId), 0) directly, in C# and in the
HLSL mirror (both shaders/CultMath.hlsl and the Unity copy, byte-identical
as before). This changes cellular's noise output — nothing has shipped, so
that is acceptable, but it is a real behavioural change, not a comment fix.
- The F6 exactness proof's "near-side neighbour" bound was wrong as written:
picking the near-side neighbour on a FIXED axis is not always within
sqrt(3) of the query (counterexample: u = (0.5, 0, 0) with the x-neighbour
jittered to (-1, 1-2^-24, 1-2^-24) gives ~2.06, past the radius-2
exclusion). The comment is rewritten to the correct argument: choose the
axis whose u_i sits closest to a cell boundary (m = min(u_i, 1-u_i) <=
1/2 on that axis), and the near-side neighbour on THAT axis has squared
distance at most (1+m)^2 + 2*(1-m)^2 = 3 - 2m + 3m^2 <= 3 on [0, 1/2],
strictly below 3 since the jitter never reaches the boundary. Margin
2 - sqrt(3) ~= 0.268 (Soul's adversarial check: worst in-box F2 is exactly
sqrt(3), only in the unattainable corner limit).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
Stryker (scoped to this cut's diff) survived two mutants on the grad2 ternary
in math.cellular: forcing the true branch unconditionally, and loosening
f2 > 0.0f to f2 >= 0.0f. Both are real findings about the code, not the
tests: F2 = 0 needs two distinct integer cells' pcg3d-jittered feature points
to land on the exact float32 value in every component, which the jitter's
~24-bit float precision makes unreachable for any p this function is
actually called with. The spec only asks for the degenerate guard at F1 = 0;
F2's copy of it was added for symmetry and had no reachable case to defend.
Deleted the guard (grad2 is now a bare (p - c2) / f2) in math.cs and both
HLSL mirrors, and corrected design.md's invariant-8 section, which had
overstated this as symmetric with F1's guard.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Inside the static math class, the unqualified name `float3` resolves to
math's own `float3(float,float,float)` constructor-function first, not the
CultMath.float3 type, so `float3.zero` fails to compile there (CS0119). No
prior code in math.cs used the Type.zero static-field spelling from inside
math itself for this reason. Switched cellular's zero vectors to
`new float3(0.0f, 0.0f, 0.0f)`, which is unambiguous under `new`.
Found by the first ygg-verify.sh build/test run on 3eed434.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
smin_grad(float4,float4,float) carries Inigo Quilez's cubic-polynomial smooth
minimum to invariant 8's value-and-gradient form: the gradient blend lerp(b,a,h)
is the exact analytic derivative, not an approximation, since the h-derivative
terms cancel algebraically inside the smoothing band.
cellular(float3) is Worley's cellular texture basis function (SIGGRAPH 1996),
searched over the jittered 3x3x3 neighbourhood of pcg3d-selected feature
points. It returns the new CultCellular struct (nearest, edge, id), following
the repo's existing degenerate-normal convention
(GameCult.Geometry.CultGeometryIsoSurface.EmitOrientedTriangle) to return a
finite zero gradient instead of NaN at a feature point.
Both are fresh implementations from the cited literature, not ports, so they
get a design.md/doc-comment citation instead of a THIRD-PARTY-NOTICES entry.
design.md gains an "Invariant 8: Value-and-Gradient Primitives" section
documenting both primitives' semantics and conventions.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
math.erfinv(1.0f) returned -infinity and erfinv(-1.0f) returned
+infinity: at value = +-1, w = -log((1-value)(1+value)) = -log(0) =
+infinity, the tail branch's leading coefficient is negative, and
`return p * value` delivered the inverted infinity. erf saturates to
exactly 1.0f at 5.543 sigma, so any interval edge beyond that reaches
this bug in ordinary use.
Guard both edges explicitly before the polynomial runs: erfinv(1) is
now +infinity, erfinv(-1) is -infinity, and anything strictly outside
[-1, 1] (including +-infinity) is NaN, which is the correct answer and
was already the fallthrough behavior. Documented the domain and the
edge behavior in a comment, matching the house style `sign` already
uses at math.cs:84. The approximation itself is unchanged.
Also recorded the true measured accuracy beside erf and erfinv: worst
absolute error 6.621e-7 and 5.066e-7 respectively, both inside the
2e-6 bar this package's tests pin.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fire control's Gaussian scatter needs erf for a cell's probability mass
and erfinv to draw the lateral offset, both called per shot. Abramowitz-
Stegun 7.1.26 for erf (stated max abs error 1.5e-7), Giles 2010 (GPU
Computing Gems) for erfinv. Neither is an HLSL intrinsic, so there is no
shader-parity obligation.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
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>
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>