Cut 12.2 Soul fix batch 2, S4: pin flight time for an invisible target

F8 (Soul's earlier fix batch, 2026-09-24) moved range computation ahead of the
visibility gate in FireControl.PFire so Fire's own flight time stays real even
when the shot prices at PFire 0, but no test observed that behaviour --
N4 (Soul's own mutation set, reverting F8) survived every existing test.
InvisibleTargetStillResolvesAtItsRealFlightTime fires at a target removed from
VisibleEntities and checks the shot does not resolve before its real 5s
arrival (N4's bug would resolve it instantly, at range 0) and resolves as a
miss once it does.

Manually verified against the exact N4 patch: it now fails (arrival at ~1s
instead of ~6s, i.e. range treated as 0).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-09-25 03:14:15 +02:00
commit 7eadea88ab

View file

@ -446,6 +446,33 @@ public sealed class FireControlCut12Tests : IDisposable
Assert.Equal(expected, p, 4);
}
// S4 fix batch (Hands, 2026-09-25): F8 (Soul's earlier fix batch, 2026-09-24) moved range/flightTime
// computation ahead of the visibility gate, so Fire's own flight time reflects the real distance even when
// PFire prices the shot at 0 -- but nothing behavioural pinned that. N4 (Soul's mutation set: "range after
// visibility gate", reverting F8) survived every existing test. A shot at a target the shooter cannot
// currently see must still resolve, as a miss (PFire gates it to 0), at the arrival time the real
// range/velocity imply -- not immediately (range 0) and not never (flight time 0 means instant resolve).
[Fact]
public void InvisibleTargetStillResolvesAtItsRealFlightTime()
{
var e = Build(TestSettings(), SolidShape(5, 5), precision: 1f, velocity: 20); // range 100 -> 5s flight
e.Shooter.VisibleEntities.Remove(e.Target);
var shotId = FireControl.Fire(e.Weapon, e.WeaponItem, e.Shooter);
var pending = e.Zone.PendingShots.Single(s => s.ShotId == shotId);
Assert.Equal(0f, pending.PFire); // fixture precondition: the visibility gate is actually closed
var expectedArrival = pending.FireTime + 100f / 20f; // the real range (100) over the real velocity (20)
Assert.Equal(expectedArrival, pending.ArrivalTime, 3);
ShotOutcome outcome = null;
using var r = e.Zone.ShotResolved.Subscribe(o => outcome = o);
e.Zone.Update(4.9f);
Assert.Null(outcome); // not yet: N4's bug (range 0) would have resolved this instantly
e.Zone.Update(.2f); // past the real 5s arrival
Assert.NotNull(outcome);
Assert.False(outcome.Hit, "fixture: PFire 0 (not visible) must resolve as a miss");
}
// Stryker survivors: ResolveAimPoint's fallback (aimed but currently occupying zero cells -> hull centre
// of mass) and its multi-cell average were both untested -- every existing marker fixture uses the
// catalog's 1x1 "Marker" stub, where sum/length and sum*length agree at length 1, and no fixture ever