Delete cellular's unreachable F2=0 guard instead of leaving it undefended

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>
This commit is contained in:
Meta Vens 2026-09-25 19:47:36 +02:00
commit 1add078eea
4 changed files with 22 additions and 9 deletions

View file

@ -151,15 +151,20 @@ together, laid out as `float4(gradient.xyz, value.w)`, with no value-only twin
jittered 3×3×3 neighbourhood of feature points selected by `pcg3d` (never
the sin-based `hash`). It returns `CultCellular { nearest, edge, id }`:
`nearest` is `(∇F1, F1)`, `edge` is `(∇F2 - ∇F1, F2 - F1)`, and `id` is the
nearest cell's `pcg3d` hash mapped to `[0, 1)`. `∇F1`/`∇F2` are undefined
exactly at their own feature point (`F1 = 0` or `F2 = 0`); `cellular`
follows the repo's existing degenerate-normal convention
nearest cell's `pcg3d` hash mapped to `[0, 1)`. `∇F1` is undefined exactly
at its own feature point (`F1 = 0`); `cellular` follows the repo's existing
degenerate-normal convention
(`GameCult.Geometry.CultGeometryIsoSurface.EmitOrientedTriangle`, which
guards a zero-length normal and returns the zero vector instead of the NaN
a bare `normalize` gives there) and returns the zero vector rather than NaN.
The identity of the nearest and second-nearest feature point changes
discontinuously across the `F1 = F2` set (a genuine kink, not a numerical
artifact), so `∇F1` and `∇F2` are only continuous away from that set.
`∇F2` carries no such guard: `F2 = 0` would need two distinct cells'
pcg3d-jittered feature points to land on the exact same float32 value in
every component, which the jitter's float precision makes unreachable for
any `p` this function is actually called with, so there is no reachable
case to defend. The identity of the nearest and second-nearest feature
point changes discontinuously across the `F1 = F2` set (a genuine kink, not
a numerical artifact), so `∇F1` and `∇F2` are only continuous away from
that set.
Integer hashing uses the PCG hashes from Jarzynski and Olano, "Hash Functions
for GPU Rendering" (JCGT 9(3), 2020): `pcg(uint)` is O'Neill's RXS-M-XS 32/32

View file

@ -371,8 +371,10 @@ CultCellular cultmath_cellular(float3 p)
}
}
// Only F1's degenerate point is guarded; see math.cellular's comment (math.cs) for why F2 = 0
// has no reachable case to defend.
float3 grad1 = f1 > 0.0 ? (p - c1) / f1 : float3(0.0, 0.0, 0.0);
float3 grad2 = f2 > 0.0 ? (p - c2) / f2 : float3(0.0, 0.0, 0.0);
float3 grad2 = (p - c2) / f2;
CultCellular result;
result.nearest = float4(grad1, f1);

View file

@ -747,8 +747,12 @@ public static partial class math
}
}
// Only F1's degenerate point is guarded, matching the spec: F2 = 0 needs two distinct
// integer cells' pcg3d-jittered feature points to land on the exact same float32 value in
// every component, which the jitter's ~24-bit float precision makes unreachable for any p
// this function is actually called with, so there is no reachable case for a guard to defend.
var grad1 = f1 > 0.0f ? (p - c1) / f1 : new float3(0.0f, 0.0f, 0.0f);
var grad2 = f2 > 0.0f ? (p - c2) / f2 : new float3(0.0f, 0.0f, 0.0f);
var grad2 = (p - c2) / f2;
return new CultCellular(
new float4(grad1, f1),

View file

@ -371,8 +371,10 @@ CultCellular cultmath_cellular(float3 p)
}
}
// Only F1's degenerate point is guarded; see math.cellular's comment (math.cs) for why F2 = 0
// has no reachable case to defend.
float3 grad1 = f1 > 0.0 ? (p - c1) / f1 : float3(0.0, 0.0, 0.0);
float3 grad2 = f2 > 0.0 ? (p - c2) / f2 : float3(0.0, 0.0, 0.0);
float3 grad2 = (p - c2) / f2;
CultCellular result;
result.nearest = float4(grad1, f1);