Soul's pass on 41440cb found BatchMath.cs:115
(AddRadialFalloffAcceleration2D(ReadOnlySpan<float2>, ...)) accumulates
onto the caller's acceleration buffer, but no test ever seeded that
buffer with nonzero values: mutating += to = passed all 133 tests. The
float-array overload already had this guard
(RadialFalloffAccumulatesOntoExistingAcceleration); add the float2
equivalent, with a nonzero, per-element-distinct starting buffer, mixing
in-range and out-of-range particles so both the accumulated and
unchanged-seed cases are checked.
Checked every other += site in BatchMath.cs for the same gap: the
array-overload's scalar tail and vector body, and both
IntegrateSemiImplicitEuler2D overloads, all already seed their
accumulator buffers (vx/vy/px/py, velocity/position) with nonzero,
per-element values in their existing tests. Only this one site was bare.
Verified by hand-mutating BatchMath.cs:115 (+= to =): the new test fails
(expected 6.78, actual 0.48); reverted the source byte-exactly after.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`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>
Both existing BatchMath tests used 2-3 particles while LaneCount >= 4, so
only the scalar tail ever ran, and both held y at 0 for every particle, so
every y-axis mutation was invisible. Add fixtures sized LaneCount * 3 + 1
with distinct nonzero x/y per particle, pinned against a scalar reference
computed independently in the test, plus explicit coverage of the early
return, negative deltaTime, length-mismatch, and empty-span guards on all
four BatchMath overloads.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>