Cut 7: retire a test Cut 6b made vacuous, and the invariant it claimed

Cut 3's harness reported OutOfArcConsumesNoDraw as "not yet reached" -- no
mutation could turn it red. Chasing that rather than accepting it found the
reason, and it is not a fixture defect.

The test asserted that a shot gated to zero draws nothing from ItemManager.
Random, so the shared stream advanced exactly as if the shot had never queued.
Cut 6b moved every roll onto a per-shot local generator seeded from (zone, shot
id), so Commit stopped touching that stream on EVERY path. The test then passed
for a reason unrelated to arcs -- it passed identically with the target dead
ahead -- and the rule it named stopped existing along with the stream it was
about. The short-circuit it guarded is now a cheap guard, not an invariant, and
Commit's comment was still asserting otherwise.

Rewritten as CombatNeverDrawsFromTheSharedStream, a Theory over both an
out-of-arc and an in-arc target, pinning the rule that IS live after 6b: no
combat path draws from the shared stream at all. Verified by hand -- restoring
the shared-stream read in Commit turns the in-arc case red while the out-of-arc
case stays green, which is exactly why the old test could not have caught it.

218 tests pass. All six mutation harnesses run clean with tree-clean PASS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-09-19 20:14:16 +02:00
commit 0ae8226ccb
2 changed files with 21 additions and 11 deletions

View file

@ -290,9 +290,14 @@ public static class FireControl
// R3/R4: the one roll. p_base was frozen at fire; the only thing measured live is how far the target has
// actually strayed, by now, from where a straight-line projection of its fire-time velocity said it would
// be -- forgiven by the targeting system's Tracking, likewise frozen at fire. A shot gated to zero at fire
// (out of arc, out of range, unlocked, undetected) draws nothing here (short-circuit below): the RNG
// sequence is exactly as if the shot had never queued.
// be -- forgiven by the targeting system's Tracking, likewise frozen at fire.
//
// Cut 7: the short-circuit below (`p > 0f &&`) is a guard, NOT an invariant. It used to be one -- a shot
// gated to zero at fire had to draw nothing so the shared stream advanced exactly as if it had never
// queued -- but Cut 6b gave every shot its own generator, seeded from (zone, shot id) and discarded here,
// so whether a gated shot draws is unobservable from anywhere. The claim this comment used to make is
// gone with the stream it was about, and the test that asserted it became vacuous the same moment; what
// replaced it pins the live rule instead (combat touches no shared stream, on any path).
private static ShotOutcome Commit(Zone zone, PendingShot shot, float now)
{
// Cut 6b, 6.1 (Soul finding 6): a shot's dice belong to the shot, not to whatever else happened to

View file

@ -256,15 +256,20 @@ public sealed class FireAuthorityTests : IDisposable
Assert.Equal(unaided.Items.GameplaySettings.UnaidedAccuracy, FireControl.HitProbability(unaided.Weapon, unaided.Shooter, unaided.Target), 3);
}
// The gate is a gate, not a penalty: an out-of-arc commit consumes exactly zero draws from the shared RNG
// stream. Mutation: roll first and multiply the result by zero -- that still advances the stream by one
// draw, which this test would catch by seeing a different next value than an untouched stream produces.
[Fact]
public void OutOfArcConsumesNoDraw()
// Cut 7 (docs/fire-control-cut.md): this was OutOfArcConsumesNoDraw, which asserted that a shot gated to
// zero left ItemManager.Random untouched. Cut 6b (6.1) moved the roll onto a per-shot local generator, so
// Commit stopped touching that stream on EVERY path and the old test passed for a reason that had nothing
// to do with arcs -- it would have passed just as well with the target dead ahead, and no mutation of the
// short-circuit it named could ever turn it red. Rewritten to pin the rule that is actually live: no
// combat path draws from the shared stream, in arc or out. Mutation: restore the shared-stream read in
// Commit; the in-arc case then goes red.
[Theory]
[InlineData(100f, 0f)] // directly abeam: gated to zero, outside the default arc
[InlineData(0f, 100f)] // dead ahead: a real firing solution that actually rolls
public void CombatNeverDrawsFromTheSharedStream(float x, float z)
{
var e = Build(TestSettings());
// Directly abeam -- outside even the wide 170-degree default arc dead ahead.
e.Target.Position = float3(100, 0, 0);
var e = Build(TestSettings(), accuracy: 1, resolution: 1, spread: 0);
e.Target.Position = float3(x, 0, z);
const uint seed = 4242u;
var untouched = new Random(seed);