Commit graph

2,330 commits

Author SHA1 Message Date
Meta Vens
4ba4bbaf2e Compose validated mod ships into a derived catalog 2026-09-26 18:30:02 +02:00
Meta Vens
42eb5214ce Add parallel typed ship authoring and Blender LineArt capture 2026-09-26 18:14:36 +02:00
Meta Vens
4b594e116c Define collection-based ship prefab authoring 2026-09-26 15:26:46 +02:00
Meta Vens
354e2f05ba Record restored hull content as merged 2026-09-26 13:25:23 +02:00
Meta Vens
e160bbe5e8 Merge restored hull and thruster content 2026-09-26 13:19:42 +02:00
Meta Vens
12656b92d2 Retire stale Aetheria Persona priority cue 2026-09-26 13:06:52 +02:00
Meta Vens
d0022d9f5d Cut 1 fix batch 3 (F7): replace the vacuous turret-distance check with a real Phase-offset bound
TutorialEntranceWithNoLagrangeCandidateStillSeatsAStationWithFiniteNearbyTurrets
asserted a turret orbit's Distance matched its station's -- true unconditionally,
since PlaceTurret sets Distance from orbit.Distance directly the same way
CreateLagrangeOrbit does for the station itself, so the check could never
fail regardless of placement. Replaced with a bound on PlaceTurrets' actual
"near their station" claim: the Phase offset (20 * distanceMultiplier /
orbit.Distance), generous over PlaceTurrets' own max distanceMultiplier.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 02:37:25 +02:00
Meta Vens
c3f52086a7 Cut 1 fix batch 3 (F5, F6 N1/N3/N9): throw reachability, per-seed and multi-root pins, station-offset pin
F5: committed ThrowReachableWithAuthoredSettings (from Soul's SoulL3Census.cs)
against a real seed-1 tutorial entrance -- RosetteProbability 0 and MassFloor
1e9 leave every planet a parentless root, so even the widened candidate set
(F3's fix) is empty and the throw fires through public construction, not a
hypothetical.

F6/N1: EntranceStationSitsOnAGenuineLagrangeOrbitWheneverOneExists strengthens
the existing across-seeds test with a per-seed pin: whenever a seed's entrance
actually has a genuine (non-rosette) Lagrange candidate, its station must sit
on one, not the widened fallback.

F6/N3: MultiRootEntranceStationOrbitsAreAlwaysParentedWithPositiveDistance
authors a fragmented, heavily-rosette entrance (RosetteProbability 1, a fixed
SubZoneCount of 8) and pins that every resulting station orbit is a real,
positioned orbit -- a set Parent and a positive Distance -- regardless of
which candidate selection produced it.

F6/N9: EntranceStationPhaseDiffersFromEveryPlanetAtItsOwnDistanceAcrossSeeds
pins CreateLagrangeOrbit's own Phase offset: a docked station's Phase must
differ from every planet's Phase at the station's own (Parent, Distance),
catching a mutant that seats a station directly on its source planet's orbit
object instead of an offset copy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 02:31:32 +02:00
Meta Vens
95394f786f Cut 1 fix batch 3 (F6 N2, F7): entrance-widening leak test, robust orbit counting, real enemy-count check
F6/N2: added WideningDoesNotLeakToANonEntranceNoLagrangeZoneThatRollsAStation.
Seed 3's "EAC-7089" (the same no-Lagrange fixture the widened-candidate test
uses) naturally has no genuine Lagrange candidate; boosting one faction's
presence there via the same public SavedGame round trip
BuildGalaxyWithForcedEntrance uses makes its station-count roll nonzero
without touching ZoneGenerator's private RNG. Verified directly: dropping
ZoneGenerator's isTutorialEntrance guard on the widening condition turns
this fixture's station count from 0 into 2, so the fixture genuinely
exercises the leak path this test pins closed.

F7: fixed the leak test's comment (the widened-candidate fixture is seed 3,
not seed 1, so it no longer claims to share a fixture with a test that
doesn't). ForcedTutorialEntranceStationDoesNotInflateEnemyCount asserted
shipCount == 2, which was pinning TutorialZoneSettings' own default
NeutralWandererCount (2), not the enemy roll baseStationCount gates -- it
now filters ShipPacks by the same nearest-faction ZoneGenerator computes
internally (EntityPack.Faction), asserting the real enemy count (0) instead.

Also (Soul's finding): IsRosetteMember's planet-orbit count
(pack.Orbits.Count minus the OrbitalEntityPack count) over-counts whenever
a station or turret loadout generator returns null, since
CreateLagrangeOrbit already appended that orbit before the null check
skips adding the entity. It now derives the set from pack.Planets' own
Orbit refs directly (Census.cs's own approach), which sidesteps the bug
entirely.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 02:15:24 +02:00
Meta Vens
a5fb3b84d2 Cut 1 fix batch 3 (F1): load real GameplaySettings, fix the speed-cap test
RestoredHullsTests.Settings() hand-set thermal radiation/conduction at
their bare-class defaults (1/1/1) against the shipped 3 / 1e-8 / 0.01
(Assets/Resources/Settings.asset), so equipped parts froze toward 0 K on
real physics and every test built on that fixture ran on thermal behaviour
nobody ships. It now loads the real GameplaySettings through
AuthoredSettings (tools/AetherDb/AuthoredSettings.cs), the same headless
YAML reader tools/AetherDb already uses to inspect Settings.asset outside
Unity -- the tests project now references AetherDb.csproj to reuse it
instead of hand-copying the same fields a second time.

LonginusNeverExceedsItsRestoredTopSpeedUnderFullThrust no longer needs
OverrideShutdown to keep the ship flying (that was compensating for the
broken fixture shutting equipment down within ~1s); it now drives a
generated Longinus (seed-pinned, including ItemManager.Random, which
defaults to a DateTime.Now seed and was making item quality -- and so
the measured speed -- non-deterministic) for 5s, the window Soul measured
before this loadout's thrusters cross 386 K and shut down. The tolerance
is now asymmetric instead of the old +-10: +4 on the ceiling (the mixer's
own tick order settles real, unforced flight at the cap plus one tick's
acceleration, measured at ~103.3 for this loadout) and -3 on the floor
(tight enough to catch a nerfed TopSpeed catalog value, which +-10 or a
wider ceiling would not).

Every other test in the file now runs on real settings instead of the
frozen fixture; all 15 tests in RestoredHullsTests pass unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 01:48:58 +02:00
Meta Vens
7e63d7448a Cut 12.4(b): behavioural tests for Detonate and the fuse switch
Geometry, absorption order, symmetry/frame, shields and fuse tests from the
cut doc's own verification list, plus Soul's three fold-in probes (a
radius-without-a-fuse damage-outcome test, a moving-target proximity-burst
pin, and Detonate's nonpositive-radius guard) and the two catalog probes
(shipped-catalog neutrality on BlastRadius/Fuse, and a WeaponFuse/BlastRadius
round-trip through a written and reopened catalog copy).

Fixture: SchematicCellSize 2, target off the origin, hull centre of mass not
an integer cell, one facing with |fx| > |fy| -- the rules the cut doc names
for every blast test. Geometry expectations come from exact half-split
symmetry where possible (TheShareOffTheHullIsLost, ShieldPaysForWhatReachesIt)
and from an independent numeric area integral otherwise (EachCellTakesItsShare
OfTheDisc, TheFusePointStaysOnMetal), never from the SUT's own overlap code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 01:37:53 +02:00
Meta Vens
072e50d275 Cut 1 fix batch 3 (F3, F4): fix the entrance-throw condition, delete the dead FixedPosition copy
F3: the throw fired on selectedStationOrbits.Length == 0, which is also true
whenever stationCount rolls 0 -- an unrelated variable. It now checks
lagrangeCandidates.Length == 0 directly, the actual "no candidate orbit"
case, and the message says so.

F4: CreateLagrangeOrbit's FixedPosition copy and its comment described a
fallback orbit that no longer exists (widening always returns a real,
parented planet orbit). Deleted; nothing reads FixedPosition off a
lagrange/turret orbit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 01:23:43 +02:00
Meta Vens
0edbe0b455 Locomotion map: correct the restored TopSpeed values, record the VelocityLimit hull-gating hazard
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 01:19:07 +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
77be5cf433 Cut 12.4 map: slot 32 for Fuse, inert rule arrives with (b), measured drift
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 00:52:16 +02:00
Meta Vens
2bb245bc34 Cut 12 map: Q12-9 ruled A (the blast decides every shield)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 00:31:22 +02:00
Meta Vens
615008523e Cut 1 fix batch 2 (F1, F2, F3): commit Soul's behaviour tests
F1 (speed cap): assert against Longinus's legacy TopSpeed literal (100)
instead of reading the bound back out of the same catalog value being
tested -- the old assertion passed under TopSpeed=1000 because the
bound moved with the mutation. Also fixes the false "drag/thrust
balance" comment: there is no such balance near 100. At full throttle
Longinus's own thrusters heat their hull past ThermalOnline's shutdown
threshold within ~1s, and once the hull's own cell goes offline the
same way, VelocityLimit (a hull behaviour, gated identically) shuts off
with it, so the cap was only ever exercised for the first ~15 ticks.
OverrideShutdown -- a real, first-class field (the cockpit's "Override
Shutdown" control) -- forces the hull and both firing thrusters to keep
running for the whole 12s scenario, so the cap is what actually holds
speed down throughout, not an unreached ceiling.

F2 (entrance station): EntranceZoneAlwaysGetsADockedStationAcrossSeeds
now also requires the normal case to land on a genuine (non-rosette)
Lagrange orbit. Adds TutorialEntranceWithNoLagrangeCandidateStillSeats...,
built via a public SavedGame/SavedZone round trip (no reflection) that
re-targets a real galaxy's Entrance to a real zone already known to
have no Lagrange candidate (seed 3's "EAC-7089": four planets, one
rosette, no captured satellites) -- pins a docked station whose turrets
land on a real, finite, nearby orbit. Adds the leak checks (override
must not reach other tutorial zones or a non-tutorial game's own
entrance) and the enemy-count check (the override must not also raise
the enemy roll, which stays keyed to the pre-override station count).

F3 (asset identity): EveryAssetReferenceResolvesToAMetaGuid only checks
that a referenced GUID exists somewhere under Assets/, so it passed
when Djinni's Prefab was swapped for Longinus's. The new test compares
each reference against the specific legacy file's own GUID (Djinni's
own .prefab.meta; Thruster 1.prefab.meta for every restored thruster's
ParticlesPrefab).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 20:15:24 +02:00
Meta Vens
0fe7156d8b Cut 1 fix batch 2 (F2): collapse the entrance station to one orbit selection
Two writers decided the tutorial entrance's station orbit: the floor
(stationCount < 1 -> 1) and a synthesized fallback orbit, each masking
bugs in the other (removing either alone still passed the entrance
test). Collapse to one rule: the floor still guarantees stationCount
>= 1, but its orbit always comes from the same Lagrange selection. When
that selection has no candidate (a zone that is entirely one rosette),
widen it to every parented planet orbit ordinary placement draws from
before the rosette filter, instead of inventing an unparented,
zero-distance orbit -- which gave PlaceTurret's phase a division by
zero and left an orphan orbit record. If even the widened set is empty,
throw loudly instead of silently omitting the entrance's station.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 20:15:00 +02:00
Meta Vens
2625768f86 Cut 12.4(a): rename AirburstRange to BlastRadius, add WeaponFuse
WeaponItemData.AirburstRange (slot 29) becomes BlastRadius, same slot and
type so CultCache opens the catalog as compatible drift. A new nullable
slot 32 (30 and 31 belong to EquippableItemData) carries WeaponFuse?
Fuse. FireControl.Fire now decides "detonates" once, from BlastRadius
and Fuse directly, and freezes Fuse only when BlastRadius > 0 -- the
WeaponModifiers.Airburst label no longer decides anything (Q12-6).
PendingShot.BurstRadius is renamed BlastRadius and gains a Fuse field
that nothing reads yet; Step's Splash/Apply dispatch is unchanged.

No shipped weapon carries a blast radius or the Airburst label (Cut 12's
Probe4, reconfirmed against GameData/Aetheria.cc), so this is
behaviour-neutral on shipped content. Apply's fuse switch and deleting
Splash are Cut 12.4(b).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 19:15:26 +02:00
Meta Vens
27318cb39f Cut 12.4 map: re-anchor at d402283c, areas on the one-path absorption, Q12-9
Imagination pass: every anchor is re-probed; a disc runs armour per cell, then pools
each item through ItemAbsorb; Entity.Absorb dies with Splash; the penetrator's
fuse point reads the same Reach owner as the direct-hit walk; the ledger now
carries actuals for 12.1-12.3; Q12-9 (the host's shield for a blast) is opened.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 18:58:58 +02:00
Meta Vens
d402283c06 Cut 12.3 status: closed
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 18:38:21 +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
c07b4fee65 Cut 12.3 fix batch (F4, F6): delete unreachable guards, fix stale ArmorAbsorb comment
Entity.ItemAbsorb's `item == null` guard is unreachable: both callers (Absorb,
and FireControl.ApplyPooled's Resolve) already guarantee a non-null item
before calling in. Its `total > 0f ? ... : 0f` divide guard is unreachable
too -- the function already returned above whenever total <= 0.1f, so the
divide never sees zero. Both deleted; the surrounding comment now says why
instead of describing a guard that no longer exists.

ArmorAbsorb's own comment was stale from before the "one code path" ruling:
it described the item phase as deferred only for a cell shared with another
lane, when every occupied cell now defers to ItemAbsorb's pool, a lane that
turns out to be the sole contributor included (a pool of one, not a separate
solo path).

Soul's findings (docs/fire-control-cut.md, Cut 12.3): F4, F6.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 18:33:23 +02:00
Meta Vens
64d17a290d Cut 12.3 fix batch (F4): one writer for lane completion, delete dead reachability offset
ApplyPooled.Resolve no longer calls FinishLane directly -- AdvanceLane is now
the single place that checks pointer >= walked and finishes a lane, so lane
completion has one writer instead of two. StillReachableByAnotherLane's own
`blocked ? pointer+1 : pointer` collapses to plain `pointer[li2]`: the
alreadyContributed check just above it already excludes a lane that is
blocked on the very item being asked about, so `pointer[li2]` is never that
item's own cell by the time the +1 would have mattered.

Soul's findings (docs/fire-control-cut.md, Cut 12.3): F4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 18:33:09 +02:00
Meta Vens
0dd16713b6 Cut 12.3 map: record the cycle rule as Self's default
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 18:01:38 +02:00
Meta Vens
aafc90aced Cut 1 fix batch: commit Soul's behaviour tests (asset refs, entrance station, speed cap, loadout fill)
Four tests against the real shipped catalog and the tutorial construction path, each pinning one
of this campaign's rules:

- EveryAssetReferenceResolvesToAMetaGuid: every hull's Prefab, every ThrusterData.ParticlesPrefab,
  and every non-empty Schematic names a GUID an Assets/**/*.meta actually carries. Adapted from
  Soul's own probe (AssetGuidsResolve); it is what caught Djinni's fabricated Prefab GUID and the
  five restored thrusters' null ParticlesPrefab.
- EntranceZoneAlwaysGetsADockedStationAcrossSeeds: replaces
  TutorialZoneGenerationCanProduceADockedStation, which never used the tutorial construction path
  and only claimed a docking bay shows up somewhere in 25 zone-name variants. This builds the real
  tutorial Galaxy with Assets/Resources/Settings.asset's own values and generates the Entrance zone
  the way ActionGameManager.StartGame/PopulateLevel does, requiring a docked station in every run
  that gets that far. Galaxy zone-graph generation has its own pre-existing, seed-dependent
  flakiness (a disconnected zone graph throws inside the Galaxy constructor on roughly 1 seed in
  15) confirmed unrelated to this cut by reproducing identically against the pre-fix-batch catalog;
  that failure belongs to Galaxy.cs and is skipped rather than asserted on, with a floor requiring
  at least 15 of 25 seeds to actually produce a Galaxy.
- LonginusNeverExceedsItsRestoredTopSpeedUnderFullThrust: straight-line full-thrust run against the
  restored VelocityLimitData cap (100).
- LoadoutGeneratorFillsEveryThrusterHardpointAcrossSeeds: LoadoutGenerator, filtered to each
  restored hull, fills every thruster hardpoint across ten seeds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 18:01:17 +02:00
Meta Vens
3a0b5892c9 Tutorial station: ZoneGenerator.GenerateZone always seats the entrance zone (operator ruling)
Operator, 2026-09-25 (docs/locomotion-cut.md): the tutorial's entrance zone always gets a
generated faction station with a docking bay, deterministically. Structurally, three defects made
that false on every seed: Galaxy.cs halves influence and picks the lowest-influence unowned zone
for Entrance, so stationCount rolled 0; a Lagrange orbit is only available when a planet has an
orbiting sibling, which a boring or empty system never has; and a null station loadout silently
skipped the slot.

GenerateZone now identifies the entrance zone by reference against the galaxy parameter it already
takes (isTutorial && galaxyZone == galaxy.Entrance), never by name:
- stationCount is floored to 1 for the entrance zone, after the roll that produced it.
- When no Lagrange orbit exists, a fallback orbit fixed at half the zone radius from centre is used
  instead of leaving selectedStationOrbits empty. CreateLagrangeOrbit now carries FixedPosition
  through so a Parent-less base orbit (the fallback) still positions correctly.
- A null station loadout throws for the entrance zone instead of continuing past it.
- The enemy count is based on the pre-override station count, so forcing the entrance's station
  doesn't also hand it a bonus enemy.

The story-station path (Locations/LocationStory) is not used: it isn't saved, and
EntitySerializer.cs:77 would break Continue, per the map's own ruling.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 17:40:31 +02:00
Meta Vens
54cfe9d4ba Cut 1 fix batch: delete fix-loco1 now that its records have landed
Same pattern as restore-hulls itself: the one-shot command's carrying cost is
zero once it has run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 17:40:08 +02:00
Meta Vens
dd4375801c Cut 1 fix batch: Soul's catalog findings (Djinni asset refs, thruster curves, ThermalResilience, legacy keys)
Landed via `dotnet run --project tools/AetherDb -- fix-loco1 apply`, then deleted the command in
the same batch (Cut 9/Cut 1 precedent). Fixes, each against the decoded 2021-04-14 legacy record
or a named catalog precedent:

- Djinni.Prefab was a fabricated/truncated GUID; corrected to Djinni.prefab.meta's own GUID.
- All five restored thrusters' ParticlesPrefab (null) -> Thruster 1.prefab's GUID, the same one
  Large Drive uses (legacy record names the same prefab path for all of them).
- All five thrusters' invented OptimalTemperature/PlateauWidth (300/100) -> Large Drive's own
  200-400 precedent, 251/24 (docs/stats-power-cut1-migration.md).
- ThermalResilience: Medium Drive/Small Drive -> 10, Victoire/Talaria/RevvITup 2.0 -> 15 (legacy
  record). Victoire's Durability 100 -> 30 (legacy). Victoire and Medium Drive's legacy
  descriptions restored (Talaria/RevvITup 2.0's legacy descriptions are the literal placeholder
  "[TODO]" and are not).
- Djinni.Schematic (null) -> djinni_schematic_transparent.png's GUID, already in the tree, unused.
- Djinni's OptimalTemperature 250 -> 251, correcting a rounding slip in restore-hulls's own
  "apply LonginusX's optimal-fraction to Djinni's band" derivation ((275.15-173.15)/400 = .255 of
  band applied to Djinni's 200-unit band rounds to 251, not 250).
- All seven restored records (Longinus, Djinni, five thrusters) re-keyed from freshly minted GUIDs
  to their own legacy GUIDs, decoded directly from GameData/Legacy/AetherDB.2021-04-14.msgpack,
  matching the Cut 9 import precedent (tools/AetherDb/Import.cs, git history). Each record's own
  FactionProductData.Design ref repointed at the new key.

Verified: dry run then apply, second apply refused (Longinus already carries its legacy key),
`dangling` reports 0, `census` still names both hulls, `loadout` generates every hull cleanly
across seeds 1-5.

Re-keying changed GetAll<GearData> enumeration order, which
RestoredHullMovesAndTurnsUnderMovementAndLookDirection's design-selection (thrusterDesigns.First())
depended on implicitly: it started landing every Djinni thruster hardpoint on Victoire, the one
restored thruster with a real EnergyUsage draw, and the fixture ship carries no reactor. Fixed by
equipping the fixture ship's own Reactor hardpoint, which every restored hull actually has, instead
of relying on incidental unpowered-item ordering.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 17:38:44 +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
7ca2f64f43 Cut 1 fix batch: revert stray blank-line deletion in Program.cs
The Cut 1 restore-hulls command deletion also dropped a blank line separating
FitCommand from the dangling-refs command block. Restore it so the file is
byte-identical to base (git diff against pre-Cut-1 now empty).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 17:15:19 +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
e9339a5673 Cut 12.3 fix batch: one apply path for every item (operator ruling)
Replace the survey-then-branch (ApplySequential vs. ApplyWithSharedItems)
with a single pooled walk. Lanes are clipped to penetration once, right
after Lanes returns; every item -- single-cell or multi-cell -- pools
whatever lanes reach it and resolves the moment no other unfinished lane
can still reach it down its own remaining clipped walk, with no separate
reachability survey. A cyclic dependency between two non-convex items
resolves every currently open pool at once, order-independent by
construction. Entity.ItemAbsorb becomes the one absorption function,
gated on the pooled total; Absorb (Splash's only remaining caller) now
funnels through it with a one-contribution pool.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 17:01:05 +02:00
Meta Vens
c339304000 Locomotion map: tutorial station ruling (faction station in the entrance zone)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 16:40:34 +02:00
Meta Vens
819bd6b8b9 Cut 1: delete restore-hulls now that its records have landed
Same contract Cut 10 applied to the Cut 9 importer: a one-shot restore
carries zero ongoing cost once its records are in the catalog. The
authored-choices review list this command printed lives in the cut's
report, not in source.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 16:36:28 +02:00
Meta Vens
6b5bb87e91 Cut 1: RestoredHullsTests, against the real restored catalog
Read-only against GameData/Aetheria.cc: each hull exists with the
expected thruster hardpoint count (Longinus 4, Djinni 8), every thruster
hardpoint accepts a catalog thruster design through TryEquip at its own
position, and each hull moves and turns for a few ticks under
MovementDirection/LookDirection on the pre-Cut-3 mixer -- content proof,
not controller proof.

Also covers the operator's tutorial-station addition (2026-09-25):
Zenith (the live Station hull) is confirmed working already -- station-fit,
hardpoint-fit and `loadout` all pass for it, and the legacy record's only
station design ("Zenith Station") is that same hull, so nothing needed
restoring. ZoneGenerator's story-station path is dead code today (Galaxy's
tutorial constructor has its StoryProcessor call commented out), so
TutorialZoneGenerationCanProduceADockedStation drives the background
station path directly (real ZoneGenerator.GenerateZone call, real
catalog, a minimal SavedGame/Galaxy fixture) across 25 zone/seed
attempts and asserts at least one produces a station with a docking bay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 16:33:47 +02:00
Meta Vens
4f218e8534 Cut 1: land Longinus, Djinni and five thruster designs in Aetheria.cc
Landed via `dotnet run --project tools/AetherDb -- restore-hulls apply`.
Longinus (13 hardpoints, Alakrita) and Djinni (22 hardpoints, Rossum &
Douglas), plus Medium Drive, Small Drive, Victoire, Talaria and RevvITup
2.0 (approved 2026-09-22), each with a FactionProductData. Verified:
census names both hulls with makers, dangling reports nothing new,
LoadoutGenerator/`loadout` generates every hull in the catalog cleanly
across seeds 1-5, second apply refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 16:33:35 +02:00
Meta Vens
3cb6b1546e Cut 1: add the restore-hulls command (docs/locomotion-cut.md)
One-shot restore of Longinus, Djinni and their five thruster designs from
the decoded 2021-04-14 legacy record, in the shape of the existing
*-migrate [apply] commands: dry run by default, writes every record in one
Commit, refuses a second apply. Longinus's scalar fields and ReflectorData
come from LonginusX (confirmed byte-identical against the decoded legacy
record); its hardpoint layout is authored directly from the legacy decode,
since LonginusX's own interior was rearranged to fit its AetherDrive.
Djinni is authored entirely from the decode (no live twin exists).

Every legacy PerformanceStat's [Min, Max, HeatExp, DurabilityExp,
QualityExp] is translated into today's Min/Max/Terms shape, never copied.
Authored post-breach fields (roles, FactionProductData, PowerSupply terms,
FiringArc) are covered in the command's own header comment for operator
review. Deleted in a later commit of this same cut once landed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 16:33:24 +02:00
Meta Vens
14ee2a52ca Cut 12.3: one apply path for all items (operator ruling)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 16:22:44 +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
567579136a Cut 12.3 fix batch: proportional absorption for multi-cell items shared between lanes
Operator ruling (2026-09-25): "proportional absorption; go". A multi-cell item
struck by several lanes of one shot now absorbs their combined incoming damage
(up to its own remaining durability) and each lane's leftover is in proportion
to what it brought, replacing the old behaviour where whichever lane Apply
happened to visit first drained the item and the next lane inherited whatever
was left.

Entity.Absorb is split into ArmorAbsorb (per-cell, never shared) and ItemAbsorb
(the single-lane path), so FireControl.Apply can call armour immediately and
defer item resolution only for a cell whose item is reachable by more than one
lane this shot. FireControl.Apply gains a geometric survey (which items are
reachable by >1 lane, decided before any damage is applied) and takes the
untouched sequential walk (ApplySequential) whenever nothing is shared, or a
new coordinated walk (ApplyWithSharedItems) that deposits each lane's
post-armour remainder into a shared item's pool and resolves it once every
contributing lane has arrived, splitting the leftover proportionally
(sum-then-split, order-independent by construction). A deterministic tie-break
(smallest first-contact Entry) breaks the rare "opposite order" deadlock two
non-convex shared items can create between two lanes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 16:16:53 +02:00
Meta Vens
d1cc047a51 Cut 12.3: shared multi-cell items absorb proportionally (operator ruling)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 15:28:42 +02:00
Meta Vens
2d13541b9f Cut 12.3 status: closed apart from the multi-cell item ruling
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 15:17:25 +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
4e6495dd63 Cut 12.3 fix batch: F1, decide lane membership once per cell
Soul's second pass (CONFIRMED): even after the F2 shadow-width fix, Apply's
per-lane loop still called Lane() once per lane index, each call re-deriving
its own lateral s(li) = Lateral + (li - n) * laneSpacing by forward float
addition, then testing that value against a cell's Extent interval
independently of every other lane. Two lane lines are only exactly
laneSpacing apart in real arithmetic; accumulating the offset by repeated
addition can shift a side lane by an ulp or two, letting it land inside a
neighbouring cell's half-open interval that its own unrounded value would
never reach -- the same "two derivations of one quantity" defect Cut 12.2's
own fix batch already removed from Lane itself, recurring one layer up in
Apply.

FireControl.Apply now calls a new Lanes() that decides each candidate cell's
lane index with one computation: k = ceil((cell.Lo - centreLateral) /
laneSpacing) is the unique integer for which centreLateral + k * laneSpacing
falls in the cell's own [Lo, Hi) -- derived once, from values no other lane's
arithmetic has touched. A single Ceiling call returns exactly one integer, so
a cell can be assigned to at most one lane by construction, not by float
luck. When k == 0 this is the same condition Lane() tests for centreLateral
itself, so the centre lane's cell set is unchanged (the 12.2 placement
contract, HitsLandOnTheFacingEdge, still holds). laneSpacing is still used,
unchanged, to place each admitted lane's own line for AlongBearing's slab
solve -- geometry, not membership.

Lane() itself is untouched (still used directly by Commit and by
FireControlCut12Tests' edge-seed tests).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 14:07:36 +02:00
Meta Vens
d301c0fbbd 12.3 map: lanes are disjoint by construction, not by float spacing
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 13:22:09 +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
078b1ff36d Cut 12.3 fix batch: F10, an independent AlongBearing expectation at several bearings
The previous AlongBearingReturnsTheExactSlabIntersection wrote out SlabAxis's
own threshold (1e-9f) and clamp a second time in its ExactSlab helper, so a
mutant that only moved that threshold (C7b, 1e-9 to .2) agreed with both
copies identically and survived.

AlongBearingReturnsTheExactSlabIntersectionAtSeveralBearings replaces it: the
expected entry/exit now come from an exact ray-box intersection that never
special-cases a near-zero bearing component -- IEEE division by a signed zero
already yields the correctly signed infinity, so an axis with no motion is
unconstrained without a hand-written epsilon to disagree with SlabAxis's own.
Checked at the original fixture's bearing plus two within about 11 degrees of
an axis, inside C7b's mutated .2f threshold but nowhere near the real 1e-9f
one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 11:53:47 +02:00