Commit graph Aetheria/tests/Aetheria.Shared.Tests/FireControlCut12Tests.cs
Author SHA1 Message Date
Meta Vens
7eadea88ab 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>
2026-09-25 03:14:15 +02:00
Meta Vens
c2b27b10ee Cut 12.2 Soul fix batch 2, S2: pin the within-prong ratio, not just direction
TwoProngHullSplitsTheScatterAcrossBothIntervals's own within-prong check
(atNearColumn > atFarColumn) held under M12/M12b (an entire second silhouette
built at the wrong sigma) and the sigma/sqrt(2) Stryker mutants, because every
wrong sigma tried still puts more mass on the nearer column -- direction alone
cannot separate a sigma of 1.5 from 1.5*sqrt(2). Their analytic near/far mass
ratios are well apart (~2.9 true vs ~1.73 at sigma*sqrt(2)), so the test now
computes the true ratio from the same per-cell Gaussian integral it already
uses and asserts the empirical ratio (8000 shots) tracks it within a tolerance
that rejects the wrong-sigma ratio.

Manually verified against the exact M12 and M12b patches from Soul's mutation
script: both now fail this test (empirical ratios 581 and 1.68, matching a
collapsed and a sqrt(2)-shifted sigma respectively, against the analytic ~2.9).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 03:11:27 +02:00
Meta Vens
d82b6bf9ed Cut 12.2 Soul fix batch 2, S1: behavioural tests, not reflection, for the shadow fix
Replaces LateralDrawIsHalfOpenAtTheShadowsTopEdge (reflection into the private
LateralDraw) with:

- ShotAtBearingLoTouchingOnlyACellCornerHitsMetal and
  ShotAtBearingLoOnTurningArmourHitsMetal, Soul's two named-seed crash repros
  (Cut12-edge-68442709 and Cut12-edge-64078070 -- StableHash(zone name) seeds
  Zone.CombatSeed, so the exact name reproduces the exact die roll) run
  through the real public Fire/Zone.Update path, asserting the shot commits a
  hit whose cell's own projected lateral interval contains the drawn Lateral,
  instead of throwing.
- LaneNeverEmptiesAtAnIntervalEdge, the general sweep: every interval's Lo and
  the point just below its own half-open Hi, across several bearings,
  precisions and hulls (including a non-convex, holed one), must never return
  an empty lane from FireControl.Lane.

Build gained a zoneName parameter (Cut 6.1's one-fixed-name convention doesn't
serve a repro that needs an exact CombatSeed) and a tracking parameter and an
equipLMarker fixture, both used by later findings in this batch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 03:06:44 +02:00
Meta Vens
8bd25f6faa Cut 12.2 Soul fix batch: half-open clamp, split assembly, real rolls
F1 (correctness): LateralDraw clamped the drawn lateral to the closed
[Lo, Hi], not the spec's half-open [Lo, Hi). Soul enumerated all 2^24
NextFloat outputs against TurningArmourIntoTheShotTakesItOnTheArmour's own
geometry and found 3 that draw exactly Hi, which Lane's own half-open filter
then admits no cell for -- a hit that passed its roll landing nowhere (R3).
Fixed the clamp; Lane's silent int2.zero fallback on an empty lane is now a
thrown InvalidOperationException, since an empty lane is provably impossible
after the fix. Committed Soul's edge probe as
LateralDrawIsHalfOpenAtTheShadowsTopEdge.

F6/F8: HitProbability, Inspect and Fire each assembled bearing -> Silhouette
-> PSpread (or recomputed range) in their own copy; a shooter-frame bearing
bug in HitProbability's copy survived while the identical bug in Inspect's
copy was caught. Collapsed into one Forecast function and one PFire(out range)
overload; both callers, and Fire, read the one computation.

F2: FireControlCut12Tests' zone names carried a Guid, breaking every other
cut's fixed-seed convention (FireControlCut6Tests.cs:62-63) and reseeding the
die every run -- the actual source of the flake in
TurningArmourIntoTheShotTakesItOnTheArmour. Fixed to a fixed zone name.

F3/F4/F5/F7: added a two-prong hull fixture (LateralDraw's walk past the first
interval, and a wrong-sigma placement, were unreachable when every fixture
projects to one interval), a real-roll empirical test for CommitProbability
(the old one called the pure function twice and compared it to itself),
enough Penetration in TurningAfterCommitChangesNothing for ApplyHit to
actually read the bearing, and a 2x2 item fixture for ResolveAimPoint's
average (every "Marker" elsewhere is 1x1, so sum/length and sum*length agree).

F9: Lane's own comment claimed no allocation while its sort used
Comparer<T>.Create over a closure; LaneCell now carries its own precomputed
projection and Lane sorts with a stateless singleton comparer. Corrected an
overclaiming comment in FireAuthorityTests (SigmaFloor makes the aimed-lane
draw ~68% per shot, not deterministic; the fixture's fixed zone name is what
actually makes it reproduce).

Cost: Silhouette takes an optional caller-rented Interval[] buffer; the
forecast path (HitProbability/Inspect, via Forecast) rents from
ArrayPool<Interval>.Shared and returns it before returning, since neither
reads sil.Intervals after the call. Commit/CommitProbability still get an
owned array, since LateralDraw needs it alive past the call. Measured on
LonginusX (66 cells, Release): unaimed 672 B -> 184 B/call (1512-1637ns before,
1482-1560ns after); aimed 1160 B -> 672 B/call (2123-2140ns before,
2094-2162ns after) -- the remaining ~488B/call gap between aimed and unaimed
is ResolveAimPoint's own CellsOf allocation (a List<int2>+ToArray per call),
not in this batch's scope.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 23:08:19 +02:00
Meta Vens
efe4f7c954 Cut 12.2 Stryker fix batch: one range, five real gaps
The first Stryker pass (375 mutants, 69 survivors on this cut's own changed
lines) found one real split-authority bug and several untested edges:

- HitProbability recomputed target.Position - source.Position a second time
  for PSpread's range, a second place the same subtraction could flip sign.
  PFire now returns its own gate-computed range by out parameter; HitProbability
  reads that one value instead of a second copy.
- TravelDirection's point-blank fallback and Fire's null-target guard around it
  were only ever exercised at shooter/target pairs where the real direction
  happened to equal the (0,1) fallback by coincidence.
- diagnostic.PFire (presentation) was only ever checked at PSensor == 1, where
  Accuracy * PSensor and Accuracy / PSensor agree.
- CommitProbability's four-factor product was only ever checked with
  pDeviation == 1 (no fixture moved a target between fire and commit), where
  * and / by 1 agree.
- ResolveAimPoint's zero-cells fallback (an aimed item that no longer occupies
  any cell) was never reached; every fixture's marker survives to be hit.

New tests: TravelDirectionMatchesThePredictedInterceptAndFallsBackAtPointBlank,
DiagnosticPFireIsAccuracyTimesPSensor, CommitProbabilityIsTheExactProductOfItsFourFactors,
AimPointFallsBackWhenTheAimedItemNoLongerOccupiesAnyCell.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 21:25:15 +02:00
Meta Vens
7dbb3bcf7a Cut 12.2: where a hit lands is priced and drawn from one silhouette
FireControl.Silhouette replaces HullKernel/WeightedPick/POnHull: the hull's
lateral shadow at a bearing, merged into intervals, integrated exactly with
CultMath.erf/erfinv instead of a discrete per-cell Gaussian sum. FireControl.Lane
does exact slab traversal to pick the impact cell from a lateral offset; only
its first element (the entry-nearest occupied cell) is used here, per the cut
map -- the rest is 12.3's armour-first absorption march.

Bearing timing: PendingShot.PBase is renamed PFire and now carries only the
shooter-side factor (Accuracy x PSensor) behind the gates; TravelDirection,
Spread and FireRange are frozen alongside it. The target's facing is read live
at Commit through FireControl.CommitProbability (steps 1-5 of the spec's
"Bearing timing"), not frozen at Fire, so a target that turns its armour into
an incoming shot takes the hit there. ShotOutcome gains Bearing/Lateral,
frozen at commit; Apply reads only those, never a live position or facing.
Entity.ApplyHit takes an already-schematic bearing instead of a world
direction, and the ToSchematic call 12.1 put inside it is gone.

Superseded tests: PlacementAndProbabilityShareOneKernel, EveryHitLandsOnMetal
and AimingAtTheSternHitsTheStern are replaced by
FireControlCut12Tests.PlacementAndProbabilityShareOneSilhouette,
HitsLandOnTheFacingEdge and AimingAtAnItemCentresTheScatterOnItsLane.
FireAuthorityTests.AimedHitLandsOnSelectedItem and PenetrationMarchIsPlanar are
updated for ApplyHit's new bearing parameter and Lane's entry-nearest-cell
placement (a solid hull's Tool item is never itself the entry cell; reaching
it now needs the same penetration march the new tests exercise).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 20:40:50 +02:00