Commit graph Aetheria/tests/Aetheria.Shared.Tests/FireControlCut123Tests.cs
Author SHA1 Message Date
Meta Vens
15723f4037 Mining Cut 3: a chunk is a target, detected by reflected light
- Entity.Target is one slot of TargetRef, read-only from outside
  (IReadOnlyReactiveProperty); Entity.SetTarget is its one writer. A chunk is
  taken only while the entity can see it, so a rock in darkness cannot be
  picked. Every handler, agent, turret and test writes through SetTarget.
- Entity.ChunkInfo (Q14 A): the value the per-tick sensor rule settles at,
  computed on demand through Sensor.PassiveRate/Sensor.Gain -- sum of the
  active sensors' passive rates over (sensors x TargetInfoDecay), capped at 1.
  Stored nowhere; no per-tick loop over chunks. ChunkVisible is the detection
  threshold through the chunk path.
- The lost-track rule reads through the chunk path: Activate's reconciliation
  and a per-tick check of the one slot drop a chunk target that has gone dark.
- Entity.VisibleChunksInReach (Q13 A): visible chunks within the longest range
  among active weapons -- the one reach rule for cycling and programmatic picks.
- Entity-only readers take .Entity: TargetedBy/TargetedByCount, target items,
  the stance gate (a chunk has no stance), lock (Q12 A: never on a chunk),
  turrets, Combat, Minion, FireControl (a chunk fires untargeted until Cut 4).
  TargetRange and the arc gate read a chunk's planar position.
- ActionGameManager: the reticle and next/previous offer visible ships and
  visible chunks in reach; nearest stays enemies-only; the target panel,
  indicator and fire-control debug take TargetRef. EntityInstance reads .Entity.
- MiningCut3Tests pin parity, darkness, monotone brightness, wear and break,
  reach, the chunk query, target survival and loss, TargetRef equality, lock,
  and field-kind assignment at generation and first load.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 12:56:39 +02:00
Meta Vens
7c99909d2b Cut 12.4(b): Detonate replaces Splash and Entity.Absorb
FireControl.Apply now switches on the frozen shot.Fuse: a null fuse keeps
12.3's lane path (ApplyDirectHit), a proximity fuse detonates at arrival with
no roll, and a contact or delayed fuse detonates only on a committed hit
(ApplyBlastHit), locating P through Lane and the new Reach owner (moved
verbatim from Apply's own inline penetration clip; the delayed fuse point
reads the identical rule). Step has no damage branch left.

Detonate (FireControl.cs) is the one owner of blast damage: candidates by
centre-plus-half-diagonal, exact circle/cell overlap shares computed once,
every blast's shield decided live per Q12-9 = A (including the host of a
contact/delayed blast -- Commit now gates its own shield decision on
shot.Fuse == null), armour absorbed per cell, and each item's covered cells
pooled into one ItemAbsorb call. Entity.Absorb and FireControl.Splash are
deleted; Entity gains ToSchematicPoint/ToWorldPoint for the point conversion
Detonate and the delayed fuse point need. Mine.Explode now calls Detonate.

Existing Splash/Absorb-era tests across Cut4/5/6/11/123 are rewritten or
retargeted to the new public owners; behavioural coverage for the blast
model itself lands separately.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 01:17:36 +02:00
Meta Vens
cccf3a2ac4 Cut 12.3 fix batch (F5, F1, F2): model sweep never meets a cycle; commit P3-P5
FixedPointItemResolution now throws "the model never meets a cycle" if a pool
is still open after its fixed point settles, instead of silently
force-settling in the reverse of first-encountered order. The sweep already
gates its own draws (LanesReaching) so a genuine cross-item cycle is never
actually presented to the model; a thrown exception here means the gating
missed a real case, not a numbers question the model should answer with its
own competing tie-break. The stale "opposite tie-break" comment describing
the deleted behaviour is gone with it.

Three of Soul's five probes (soul125-FireControlCut123Tests.probe.cs) are
committed here, ported from reflection onto the public Fire/Commit/Apply path
this file's own tests already use -- FireCentre/FireUntilHit retries until
the committed hit lands on the expected column, the same idiom
ZeroDepositDoesNotStall and PooledPathHonoursTheItemThresholdLikeTheSoloPath
already use, never a direct call into the private Apply:

- ZeroDepositContributorFiresNoItemDamageEvent (P3): a zero-deposit
  contributor must not fire a phantom ItemDamage event. Kills Entity.cs's
  `incoming[i] > 0f` boundary flipped to `>=`, and a swap that reports the
  post-split share instead of the incoming one (F2).
- ClippedLaneDoesNotHoldThePool (P4): an item beyond every lane's own
  penetration clip must never be waited on.
- OwnRepeatVisitDoesNotHoldThePool (P5): a lane's own later visit to an item
  must not hold that item's pool open -- only other lanes are future
  reachers. Kills FireControl.cs's `alreadyContributed` guard being deleted
  (F1).

The other two probes (Soul5_AngledMirrorAndConservationWithSharedItems,
Soul5_AngledMirrorCycleShapes) are NOT committed. They drive Apply directly
through reflection with a hand-built PendingShot.Outcome so they can sweep
1400 exact, mirrored (bearing, lateral) pairs per test and compare against an
independent model -- Commit's own bearing is reachable through the public
path (shooter/target positioning), but its lateral is a random draw from the
per-shot RNG, not a value either test can set or predict without either
control of that RNG's seed and Silhouette's exact sigma, or a decoy Apply
entry point. Reaching them without reflection needs one of:
(a) `[InternalsVisibleTo]` for Aetheria.Shared.Tests on FireControl.Apply, or
(b) a public/internal test seam that takes a pre-built ShotOutcome (bearing,
lateral) and runs Apply's own walk against it.
Neither is authorized by this fix batch's map; this is reported rather than
silently reflected. The Soul5_Alloc benchmark (also reflection-based, and not
one of P1-P5) is likewise left uncommitted.

Mutation verification (docs/fire-control-cut.md, Cut 12.3, one-apply-path):
F1 and both F2 mutants confirmed killed by the new tests; the earlier
fix batch's own forced-sequential, resolve-one-pool-only, and cycle-throws
mutants (Soul's M3/M4/M6 family) still die against CycleTieBreakIsMirrorSymmetric
and friends.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 18:33:50 +02:00
Meta Vens
166b6202b1 Cut 12.3 fix batch: the model sweep is now an independent fixed point
Rewrite the shared-item model sweep instead of leaving it a follow-up:
the earlier ModelWithSharedItems copied FireControl.Apply's own
survey-then-walk shape closely enough that reverting production to a
plain sequential per-lane absorb (no pooling at all) still passed it.

FixedPointItemResolution settles items in a different order from
production: it batches every lane's own armour-only walk into full
passes, then checks every currently open pool for readiness once per
whole pass (a dynamic, re-derived-each-time check, not a table built
once up front -- a precomputed-once version deadlocked on a non-convex
item crossed twice by the same lane). Lane geometry is read from the
real FireControl.Lanes instead of a second hand-rolled walk, removing
a class of disagreement that had nothing to do with absorption order.
Durabilities are now drawn below a lane's own share so every shared hit
overflows, hull damage is compared alongside armour and items, and the
tolerance is tightened now that lane geometry can no longer drift.
Also fixes: rejected attempts in the retry loop were mutating real
state without resetting it, corrupting the "before" snapshot; the
model was missing Entity.ItemAbsorb's own pooled .1f threshold.

Reran M0 (control, green) through M4: M1 (forced sequential absorb)
now kills the sweep as well as the mirror/proportional tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 17:38:08 +02:00
Meta Vens
ab14552a1e Cut 12.3 fix batch: tests for the one-apply-path pooling rules
Commit Soul's probes as behaviour tests against the pooled walk:
CycleTieBreakIsMirrorSymmetric (a genuine two-item cyclic dependency,
mirror-symmetric and damage-conserving by construction),
PooledPathHonoursTheItemThresholdLikeTheSoloPath (the .1f threshold is
decided once on the pool total, never per contribution, and pins the
pooled ItemDamage event count), and ZeroDepositDoesNotStall (a lane
whose armour ate its whole share still deposits and never blocks the
pool it belongs to). Adds the Custom fixture hook (an arbitrary named
item shape) the cycle test needs to build two crossing non-convex items.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 17:02:11 +02:00
Meta Vens
b55894a02a Cut 12.3 fix batch: tests for proportional multi-lane item absorption
F8 tests, through the real Fire/Commit/Apply pipeline (no reflection):
- ProportionalAbsorptionSplitsTheLeftoverBetweenTheLanes /
  ...LeavesNothingWhenTheItemAbsorbsEverything: the ruling's own literal case
  (two lanes bringing D1=25/D2=15 onto one 2x1 item), both the partial- and
  full-absorption branches.
- MirrorSymmetryHoldsWithASharedMultiCellItem: a symmetric hull and its own
  mirror-image fixture must produce the same sorted leftover pair. Fails on
  the pre-fix-batch code (commit d1cc047a) at itemDurability 20 ({0,20} vs
  {5,15}); a durability equal to one of the two D values turned out
  degenerate and was rejected first (see the test's own comment).
- EveryAxisAlignedSpreadShotStrikesExactlyTwoNPlusOneFacingCells: Soul's own
  owed regression guard on lane spacing, independent of item sharing. Killed
  by hand-mutating FireControl.cs's laneSpacing by *.99f (R1b): 28/400
  interior-column hits drop a facing column.
- ModelSweepAgreesWithProductionIncludingSharedMultiCellItems: an independent
  model (armour-then-item-then-hull per lane, extended with the same
  survey/sum-then-split shape production uses) checked against production
  over a sweep of two hull sizes, three bearings and both 2x1 bars and
  non-convex L-shaped items (Build gains lBars/LShape for this).

Tool-hardpoint items (Bar, Marker) can only occupy interior cells
(Entity.ItemFits), so every fixture above needed a hull at least 3-4 cells
deep -- shallower hulls (used in an earlier draft) fail marker/bar equip
outright.

Hand-verified against the M13 "lane_order_reversed" mutant Soul recorded as
surviving on the pre-fix code (FireControl.cs, Lanes' own `var k = li - n`):
MirrorSymmetryHoldsWithASharedMultiCellItem fails on the pre-fix code under
that mutant and passes on this cut's code under the same mutant, confirming
the new resolution is order-independent of which physical lane a buffer index
happens to hold, not only of Apply's own loop order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 16:17:13 +02:00
Meta Vens
cb71a3cfad Cut 12.3 fix batch: unify Lane and Lanes on one membership query
Self's review found that commit 4e6495dd's fix for F1 replaced the per-lane
forward-arithmetic defect with a second, still-independent membership
computation: Lane's own half-open admission (Lo <= s < Hi, from a cell's own
Extent) versus Lanes' ceil((cell.Lo - centreLateral) / laneSpacing) lane
index. The two agree only when a cell's own (Hi - Lo) equals the externally
supplied laneSpacing exactly in float -- the identical two-derivations trap
Cut 12.2's own fix batch already found and removed from Lane itself,
reintroduced one layer up. Confirmed reachable: on commit 4e6495dd, solid5
b=(0,1) s=0.49999997 n=1 leaves the ceil-based centre lane EMPTY while Lane(s)
itself finds all 5 cells of the row -- breaking R3 ("the centre lane always
meets metal", Commit's own guard) and the 12.2 placement contract.

FireControl.Lanes is now built entirely out of FireControl.Lane: it calls
Lane(hull, b, centreLateral + k * laneSpacing, ...) once per lane index, so
Lane(s) is literally this function's n = 0 case rather than a parallel
re-implementation of its rule. Commit's placement and Apply's damage split
now read one function. The narrower question left -- can two individually
correct Lane() calls, at two different lines, still walk the same cell? --
is resolved by ownership (the lane closest to the centre keeps a contested
cell; the centre lane, k = 0, never loses one), not by a competing formula,
so the centre lane's final cell set is always exactly Lane(centreLateral)'s
own output.

Lane() itself is unchanged (still called directly by Commit and by
FireControlCut12Tests' edge-seed and interval-edge tests, all still green:
HitsLandOnTheFacingEdge, LaneSeamIsHalfOpen, LaneNeverEmptiesAtAnIntervalEdge,
Cut12-edge-68442709, Cut12-edge-64078070).

Adds LanesQueryIsDisjointMatchesLaneAtCentreAndNeverEmptyOnHull: now that
Lanes is a public, pure geometric query, the boundary sweep is a behavioural
test of its own contract, no reflection -- over every cell's own shadow edge
and float neighbours, at exact-axis, 45-degree, |bx| > |by| bearings and
Soul's own two reproduction cases, spread 1-2: (a) no cell is walked by two
lanes, (b) the centre lane equals a plain Lane(s) call, (c) the centre lane
is never empty when the centre lateral is inside the hull's own shadow. Hand-
confirmed to fail on 4e6495dd's ceil-based Lanes (property (b), the same
input above) and to pass on this commit (fc123fix2-mut3.log).

Adds CommittedCellIsTheFirstCellTheCentreLaneDamages: an end-to-end check
through Fire -> Commit -> Apply (damageSpread 0, a single lane) that the
committed outcome.Cell is the first cell Apply damages.

Full suite: 286 tests, identical across 3 runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 14:24:55 +02:00
Meta Vens
742354c67a Cut 12.3 fix batch: F3 rework, F4 delete unreachable mirror test
F3 (medium, confirmed): NoCellIsStruckByTwoLanesAtAnAngledBearing used
penetration 0 (impact cells only) and one fixed-seed shot per bearing, at
only two bearings, so it survived R1e (one-cell spacing when |bx| > |by|),
R1h (spacing 1/max(|bx|,|by|)), R1c (spacing * 0.9), and R1b (spacing *
0.99). Its own comment also falsely claimed a plain revert "fails this at
both bearings" without ever checking a real revert.

Reworked as NoCellIsStruckByTwoLanesInOneShotAtAnyBearing: seven bearings
(exact axis, both 45-degree diagonals, |bx| > |by| and |bx| < |by| on both
sides, all within the fixture's own FiringArc), 300 hits sampled per bearing
at a wide targeting spread (so the committed Lateral's draw visits many
points along the hull's shadow), and ample penetration.

Hand-verified against fc123fix2-mut.log: reverting to one-cell spacing (R0)
and R1c/R1e/R1h are all caught by the full suite -- but not by this test.
F1's structural fix (this branch's earlier commit, FireControl.Lanes) assigns
each cell to exactly one lane index by a single computation, so a wrong
laneSpacing can misassign or drop a cell but can no longer make two lanes
claim the same one; R0/R1c/R1e/R1h are instead caught by the pre-existing
damage-shape tests (DirectHitAtAnAngledBearingMatchesAnIndependentModel,
FootprintWidensAtAnAngledBearingComparedToAxisAligned, and others). R1b (a
1% narrowing) survives the whole 97-test FireControl suite under any of
those tests, including this one, even at 5000 hits per bearing -- recorded
here as an open survivor for Soul's own mutation pass, not claimed dead.
This test's own job is now a standing regression guard on the disjointness
invariant through the real Fire/Commit/Apply pipeline, not a killer of
spacing-magnitude mutants (that job now belongs to the damage-shape tests).

F4: deletes MirrorImageShotsProduceStatisticallyMirroredHullDamage. Soul
showed it cannot fail on spacing -- it survives the full revert and every
spacing mutant, because no per-shot mirror pair is reachable through Fire
(LateralDraw's distribution reverses under mirroring, so a shot and its
"mirror" are independently drawn samples, not a matched pair). It was meant
to pin lane processing order under a shared-cell race; that question is
folded into the F2 disjointness rule (a shared cell can no longer arise) and
the pending F2 ruling on multi-cell item lane order, which remains on hold
per this fix batch's brief.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 14:07:38 +02:00
Meta Vens
be5382170d Cut 12.3 fix batch: rework the F2 mirror test to compare hull totals, not per-cell sums
MirrorImageShotsProduceStatisticallyMirroredDamage compared per-cell armour
damage SUMS between a bearing and its mirror, using heavy (1000) uniform
armour. That armour never depleted, so no cell's absorption ever competed
with another lane's, and the test could not see the one thing the operator's
ruling is about: two lanes racing for one cell's finite armour under the old
one-cell spacing. Renamed to MirrorImageShotsProduceStatisticallyMirroredHullDamage,
it now resets a small, finite armour value before every individual shot (so
the race is live on every trial) and compares aggregate HULL damage, where a
lost race's leftover actually lands.

Finding, reported rather than hidden: even with this fix, the test does not
fail under the reverted one-cell spacing. Reasoning checked against measured
behaviour: FireControl.Apply always processes lanes in a fixed index order
(li ascending, i.e. lateral increasing), never based on the bearing's sign.
Over many INDEPENDENTLY random lateral draws, a bearing and its mirror sample
statistically equivalent distributions of "which side's lane goes first" --
the fixed-index tie-break rule respects the mirror symmetry in aggregate, so
independently-drawn trials wash out the order-dependent bias that Soul's own
LaneOrderDemo probe found (that probe compares ascending vs descending
processing of the SAME committed shot, not independently-drawn mirrored
bearings). A production-level test that reliably fails under the old spacing
for this specific property would need to force an exact, deterministic
lateral for two geometrically mirrored shots, which is not reachable through
Fire/Commit without reflection (Commit's lateral draw is randomised, and
Apply is private). NoCellIsStruckByTwoLanesAtAnAngledBearing and
FootprintWidensAtAnAngledBearingComparedToAxisAligned remain the two tests
that actually discriminate the ruling behaviourally (confirmed failing under
a hand-reverted one-cell spacing). The mirror test stays in the suite as a
true, currently-passing invariant and useful regression coverage, not as a
defense of this specific ruling.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 12:17:59 +02:00
Meta Vens
c2e7a2166f Cut 12.3 fix batch: F1 angled-bearing parity, F2 spread-lane behaviour
F1: no existing test through Fire commits at a non-axis bearing.
DirectHitAtAnAngledBearingMatchesAnIndependentModel checks every cell's
armour, every item's durability and the hull's own total against an
independent double-precision ray/box model (ported from the Soul probe that
verified this cut's arithmetic to 14,984/15,000 shots), at 45 degrees, a
shallow angle and a bearing with |bx| > |by|.
DiagonalDirectHitReachesEveryCornerCellInOrderThroughFireCommitApply pins the
spec's own diagonal case (Probe1's corner-crossing lane) through the full
Fire -> Commit -> Apply path rather than a direct Lane call: the lane reaches
(0,0), then (1,1), then (2,2), in that order (a bearing this close to 45
degrees also admits the diagonal's row/column neighbours, so the walked lane
is usually five cells, not three -- the spec only requires the three corner
cells present and in order, the same standard the existing direct-Lane test
already pins).

F2 (operator ruling, 2026-09-25: spread lanes should diffuse sideways rather
than double up): three behavioural tests, deliberately not asserting the
spacing value itself (operator: "test behavior, not shape").
NoCellIsStruckByTwoLanesAtAnAngledBearing fires at 45 degrees and at a bearing
with |bx| > |by|, spread 2, heavy uniform armour, and checks no cell fires
ArmorDamage twice. MirrorImageShotsProduceStatisticallyMirroredDamage compares
300 hits at a bearing against 300 at its mirror image, and asserts the
per-cell damage totals are mirrored within tolerance.
FootprintWidensAtAnAngledBearingComparedToAxisAligned compares the lateral
span of hit cells for the same spread at 45 degrees against axis-aligned, and
asserts the angled footprint is wider.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 12:08:29 +02:00
Meta Vens
435f905e8b Cut 12.3 fix batch: F4 first-gap, F5 side-lane remainders, F6 armour/item order, F7 handedness
F4: AnAPRoundStopsAtTheFirstGapAndLeavesTheFarSideOfTheNotchUntouched, a notched
U-shaped hull, pins Lane's own contiguity break (an AP round with ample
penetration must stop at the first gap, not reach the far side).

F5: EverySideLanesRemainderReachesTheHullNotOnlyTheCentreLanes, an
axis-aligned spread-1 fixture (lanes share no cells, so order cannot matter),
pins that every lane's own leftover reaches the hull, not only the centre
lane's.

F6: AbsorbSpendsArmourBeforeTheItemOnTheSameCell, direct Absorb calls (this
file's existing style for the function's own arithmetic) with armour and an
item on the SAME cell for the first time -- no prior fixture combined them.

F7: ADirectHitFromStarboardDamagesStarboardCellsWhenFacingIsMostlyLateral, a
direct-hit test at a facing with |fx| > |fy|, computing "starboard" as the
independent right-perpendicular of the facing rather than reading it from
Entity.ToSchematic. Cut 11's Splash tests are the only thing guarding this
handedness today, and 12.4 deletes Splash.

Both F4 and F5 track hull damage per-attempt (reset on each retry), not as a
before/after total across every retry: this fixture's off-target lanes carry
no armour, so a stray hit on the wrong column during a retry spends real
damage on the hull and would otherwise contaminate a cross-attempt total.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 11:39:53 +02:00
Meta Vens
b82b7eab21 Cut 12.3 fix batch: F2 lane spacing by shadow width, F3 penetration boundary fixture
Operator ruling (2026-09-25): overlapping spread lanes should diffuse sideways
rather than double up. FireControl.Apply now spaces lanes by the shadow width
a cell casts at the committed bearing (Extent's own h, doubled) instead of by
one cell -- 1 at axis-aligned bearings (unchanged), up to sqrt(2) at 45
degrees. Reuses Extent, the one function that already owns h for Silhouette
and Lane, rather than re-deriving it a second way.

F3: PenetrationExcludesACellExactlyAtItsOwnBoundary's 1e6-durability marker
absorbed the whole shot before the walk could reach the boundary cell, so the
test could not fail under the `>=`-to-`>` mutant. The marker now carries 10
durability, leaving a remainder that would visibly damage the boundary cell if
the walk incorrectly included it. Adds
PenetrationExcludesCellsBeyondANonIntegerDepth, pinning the ordinary
"< penetration" comparison at a depth (1.5) that isn't an exact-integer
boundary.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 11:32:02 +02:00
Meta Vens
73b15d9f48 Cut 12.3 Stryker fix batch: exact-threshold and lane-count survivors
First Stryker pass over the changed lines (Entity.cs: Absorb/DamageHull;
FireControl.cs: Apply, Splash) killed 584/844 candidate mutants; these
six survivors on the cut's own diff get real behavioural tests instead:

- Entity.Absorb's `d > 0f` and `d > 0.1f` guards, and DamageHull's
  `damage > .1f` guard, each had their boundary flipped to `>=` and
  survived -- at the exact boundary (0 incoming, or exactly the .1f
  carry-forward threshold), real code does nothing while the mutant
  fires an extra event or spends an extra fraction. Not a rare float
  coincidence: a spent lane routinely reaches a cell with exactly 0
  remaining, and an authored penetration/armour value can land exactly
  on .1f.
- FireControl.Apply's `target.IncomingHit.OnNext(shot.Source)` (moved
  here from the deleted Entity.ApplyHit) had no test that it still fires.
- FireControl.Apply's two `walked[li] > 0` checks (metalLanes counting,
  and the absorb-loop entry) both flipped to `>= 0` and survived, since a
  lane count can never be negative -- a real bug class (a lane entirely
  off the hull would dilute the split and leak a damage share to the
  hull) that a hull no wider than the spread now catches.
- The penetration cutoff's `>= shot.Penetration` flipped to `>` survived
  at the exact boundary -- an axis-aligned lane's entry values are exact
  integers, so an authored integer penetration routinely lands exactly on
  a cell's own entry difference.

Recorded, not chased: `shot.Outcome.Lateral - (li - n)` (the lane-offset
sign) is an equivalent mutant -- 2n+1 lanes built symmetrically around n
cover the same SET of offsets either way, just enumerated in reverse, and
the loop's own per-lane work is order-independent. The missing
`ArrayPool<LaneCell>.Return` survivor is a perf-hygiene gap (GC pressure,
not a spec'd invariant -- only the forecast path is zero-allocation), not
yet reached by a dedicated allocation test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 09:09:16 +02:00
Meta Vens
7f1fde8e71 Cut 12.3: verification for the direct-hit lane's own new rules
DamageIsAbsorbedInOrderAlongTheRay, ArmourFacesTheShot,
BuriedCockpitNeedsPenetration, SpreadWidensTheImpactAcrossLanes,
ARayThroughACornerCrossesTheCornerCell and LaneRemainderReachesTheHull,
per docs/fire-control-cut.md's Cut 12.3 verification list. Builds its own
fixtures (the file's own Build), matching every other cut's own test file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 05:56:45 +02:00