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:
parent
08b0dced53
commit
1add078eea
4 changed files with 22 additions and 9 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue