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>
design.md names CultPhasor as the second struct-return consumer and describes
phacelle (stripe wave vector, weight, normalization, exact gradient and its two
discontinuity classes, exact pruning). THIRD-PARTY-NOTICES.md, in both the package
root and the Unity package, cites Rune Skovbo Johansen's Phacelle Noise (MPL-2.0)
and records that the 3D-cell generalisation, the caller-supplied side vector, the
exact gradient and the exact pruning are new.
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>
Extends the invariant-8 primitives list with the new gradient-noise
family: the constant-retargeting for snoise_grad, the per-octave
frequency/amplitude chain rule, ridged_grad's deliberate zero crease,
and the octave clamp's reason (the HLSL mirror test's bit-parity
oracle).
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>
design.md follow-ups matching the math.cs/HLSL fix in the previous commit:
- The F6 "5x5x5 is exact" argument is rewritten with the corrected near-side
neighbour bound (choose the axis closest to its own boundary, not a fixed
axis), the m <= 1/2 derivation, and the counterexample that showed the old
argument wrong. Same text as the math.cs comment, so the two do not drift.
- "Integer hashing" now states that cellular hashes the winning cell's
integer coordinate, not its float32 bit pattern, and records the measured
chi-square evidence (203.8 over 1,000,000 cells, 15 df, critical value
37.7) for why the old bit-pattern hash failed a uniformity check.
- "Precision domain" gets a new paragraph: the lower-bound prune's bit
identity to the unpruned 5x5x5 search holds for |p| < 2^25, not
unconditionally. Above that, cell + offset itself starts to round in
float32, so the pruned and unpruned loops can visit different actual
neighbours at the same nominal offset (Soul, cut 2a-i: 180 of 1,100,000
points differ at |p| ~= 3.4e7). This is a float32 domain limit on the
equivalence proof, not a bug in the prune; the committed test's [-50, 50]^3
sweep is comfortably inside it.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- smin_grad is Quilez's quadratic-polynomial smin, not cubic.
- cellular's search radius is 5x5x5 (F6), not 3x3x3; adds the exactness
proof and the pruning claim.
- id source is now pcg4d over the winning cell, not the pcg3d hash that
produced its jitter (F4).
- F2 = 0 is documented as reachable (past |p| of about 2^23), not
unreachable; removes the false "unreachable" claim and adds a "Precision
domain" note stating the float32 feature-spacing/tie behaviour honestly
(F3, minor finding).
- The struct-mirror comparison note now says it also matches field name and
type in declaration order, not just position (F2).
No entry added to THIRD-PARTY-NOTICES for the Quilez/Worley formulas: per
Self's standing ruling, a published formula is not ported code.
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>
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>
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>
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>
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>
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>
The operator kept the [UnscopedRef] ref this[int] row indexer as is. design.md
and the float2x2/float3x3 indexers now say where m[r][c] = v lands: locals,
array elements, class fields, and ref locals keep the write; a readonly field,
in parameter, property (auto-properties too), or static readonly identity
takes a defensive copy, so the write compiles and is lost. Fix: copy to a
local, write, assign back. Verified by compiling and running each case with
Roslyn against CultMath (no diagnostics; writes land 5, lost 0).
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>
design.md states the generated swizzle and mixed-constructor surface, writable
matrix rows, HLSL intrinsic types and NaN behavior, the deferred matrix items
(float4x4, transpose, determinant, _11 names), the exact HLSL-to-C#
transformations the compatibility test applies, and the dxc check.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The design doc now names HLSL as the parity target: HLSL-shaped math
compiles as C# under using static CultMath.math, components and swizzles are
writable, comparisons are component-wise, matrices are row-major through mul,
and select uses HLSL's argument order. "Prefer small immutable value types"
becomes plain mutable value types. CultMath keeps its own decisions where
HLSL is silent (safe normalize, float hash, its own Random), and the core
assembly stays engine-free.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>