`Precision` was declared `const float`, so `Assert.Equal(a, b, Precision)`
bound to xunit's absolute-tolerance overload (allowing any two values
within 4.0 of each other) instead of the intended 4-decimal-place
overload. Hand-mutating the `t2 > 0 ? min(t1, t2) : t1` ternary to always
return `t1` (math.cs:287) exposed it: the assertion silently accepted a
result almost 0.27 away from the expected value. Declaring it `const int`
selects the correct overload; confirmed the same hand mutation now fails.
Also add the float2-overload euler-integration length-validation test the
mutation report showed missing (BatchMath.cs:208's ValidateEqualLengths
call had no test at all).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Soul-style self-check against the actual mutation report (not just green
tests) found several fixtures whose values coincidentally produced the
same output on the buggy path as on the correct one:
- BatchMath's scalar-tail fixtures put the one remainder particle/step
either outside the falloff radius or mask-frozen, so the y-axis
arithmetic a mutation would touch was never reached with an observable
effect. Pin the remainder particle in range (radial falloff) and dynamic
(Euler integration) so the scalar tail's own math is exercised, not just
the vector body's.
- The float2-overload of AddRadialFalloffAcceleration2D had no no-op
(radius <= 0 / strength == 0) test at all.
- first_order_intercept_time's negative-determinant test used a
perpendicular relative velocity, making b = 2*dot(vel, pos) exactly
zero; a mutant that drops the "no solution" branch entirely and falls
through to the linear fallback coincidentally computes 0 too. Use a
non-perpendicular fixture so the fallback's nonzero value is
distinguishable from the correct zero.
- No fixture ever reached the `t1 > 0` branch at all (a > 0, both
quadratic roots real and positive), so the boundary/negate checks
guarding it and the `t2 > 0 ? min(t1, t2) : t1` line were never
covered. Add that case.
- bool2.@false/@true, bool3.@true and bool4.@false have no consumer
anywhere in the suite (bool3.@false and bool4.@true are read by
HlslSemanticsTests); pin all six vector constants directly.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
math.first_order_intercept_time had a single closing-target test. Add
coverage for zero relative velocity, the a-approx-zero linear fallback, a
target that outruns the projectile (negative determinant), a receding
target with both quadratic roots non-positive, and a closing case with
off-origin, off-axis shooter and target state. The closing cases assert
that the projectile (inheriting the shooter's velocity) and the target
actually coincide at the returned time; the no-solution branches pin the
value the source's own contract specifies.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>