Commit graph

2,236 commits

Author SHA1 Message Date
Meta Vens
73e080d8d6 Cut 9: re-anchor cut3 and cut6 onto the mixed seed
9.1 changed the line those two harnesses pin, stranding five anchors including
cut6's own no-op control -- so cut6 was defending nothing until now. Same thing
the diagnostics refactor did to four harnesses earlier, and the second time in a
day that a change to Commit has stranded anchors elsewhere. The rules are
unchanged; only the text moved.

That maintenance cost is now a known property of pinning mutations to literal
source text. It buys a stale anchor failing loudly within one run, which is how
both instances were caught immediately, and it belongs in Cut 10's list beside
Soul's harness audit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 03:01:04 +02:00
Meta Vens
6db6dc78f3 Cut 9: the die is not a die
9.1, blocking. FireControl seeded CultMath's xorshift32 per shot and drew once.
NextFloat returns the top 24 bits of the FIRST round, and one round cannot carry
ShotId's low-bit variation into the high bits -- so every zone's hit roll spanned
about an eighth, with the threshold fixed at zone creation. A shot was a step
function of p: below the band it never hit, above it always did. An unaided
shooter hit 0 of 18,926 shots. That is the operator's "no hits registering," and
being stable per zone is why it came and went.

The seed derivation is unchanged -- still a pure function of (zone, shot id),
Cut 6b's rule -- and only its diffusion into the first output changes, via an
fmix32 finalizer. Measured across six zone seeds: hit fraction at p=.3 went from
0.0% in five of six zones to 29-31%, and at p=.6 to 58-61%, with the roll
spanning the full range.

The defect survived three cuts because nothing in the campaign measured a
distribution -- every fixture uses accuracy 1 or 0, and a constant satisfies a
determinism test perfectly. TheDieIsUniform now asserts the observed hit
fraction is within .08 of p across several zone seeds, which is the test whose
absence was the real defect.

9.2. Sigma is floored at half a cell. HullKernel approximates a continuous
Gaussian with a discrete sum over occupied cells, and that sum collapses once
sigma falls well under one cell against an aim point that is not exactly on a
cell centre -- true of every shipped hull. Past Precision ~4, pOnHull went to
zero and a better targeting system made the weapon unable to hit anything.

9.3. An active entity's Target is now always a live entity it has info on.
Cut 8's _active guard could never have fixed the operator's LockWeapon crash --
that code only runs on active entities, and cut8's own harness header says so.
The live route was dock, target dies, undock; Deactivate disposed the
subscription that nulls a stale Target and Activate reseeded EntityInfoGathered
without reconciling it. Still not a TryGetValue in LockWeapon.

229 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 02:33:22 +02:00
Meta Vens
16d8493e0c Cut 9 map: the die is not a die
A Soul pass measured the thing three cuts assumed. FireControl's per-shot seed
feeds CultMath's xorshift32, whose NextFloat returns the top 24 bits of the
FIRST round -- and one round cannot carry ShotId's low-bit variation into the
high bits. Every zone's hit roll spans 0.1249. A shot is a step function whose
threshold is fixed when the zone is created: below the band it never hits, above
it always does. An unaided shooter hit 0 of 18,926 shots.

That is the operator's "no hits registering," and being stable per zone is why
it came and went.

It is this map's own prescription, written by Self in Cut 6b, and the reason it
survived is that no test in the campaign measures a distribution -- every
fixture uses accuracy 1 or 0, and a constant satisfies a determinism test
perfectly.

Also takes pOnHull collapsing to zero above Precision ~4 (no shipped hull's
centre of mass is integral, so a tight sigma underflows every cell weight), and
the LockWeapon crash Cut 8 did not actually fix -- that code only runs on an
active entity, so the _active guard was never what would prevent it. Docking
still reaches it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 01:53:41 +02:00
Meta Vens
dcb8c448ce Merge fire control Cut 8: a removed entity is a dead entity
Fixes the crash from the operator's play smoke. Cut 5.6 moved death removal into
the simulation and did half the transition -- Zone.Entities.Remove with no
Deactivate, where TryDock has always paired the two -- so a dead ship kept live
subscriptions and stale references while every survivor dropped it, and
Zone.Update's snapshot iteration handed it one more Update after the world had
forgotten it. Entity.Update never consulted _active; the flag existed and
nothing asked it. LockWeapon's raw indexer into EntityInfoGathered was simply
where it surfaced.

Death now removes and deactivates as one transition, and a deactivated entity
does not update. Not a TryGetValue in LockWeapon: that would silence one call
site and leave every other behaviour running on a corpse.

Also re-anchors seven mutations stranded by the diagnostics refactor, two of
which were no-op controls -- four harnesses were defending nothing until now.

Still open and deliberately not patched: the heat spike, the motionless ships
and the shots that registered no hits. Unreproducible since the diagnostics
changed timing, so the map records them with the unclamped dt as the place to
probe.

226 tests; all eight mutation harnesses clean with tree-clean PASS, controls
included, run in an isolated worktree. Unity batchmode NOT run for this cut --
the operator's editor holds the project lock.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 01:14:19 +02:00
Meta Vens
8a87dec90f Cut 8: a removed entity is a dead entity, and re-anchor the stranded mutations
The operator's play smoke crashed in LockWeapon reaching EntityInfoGathered
through a raw indexer as a ship vanished. Cut 5.6 moved death removal into the
simulation and did half the transition: Zone.Entities.Remove with no Deactivate,
where TryDock has always paired the two. The dead ship kept live subscriptions
and stale references while every survivor dropped it, and because Zone.Update
iterates Entities.ToArray() it received one more Update after the world had
forgotten it. Entity.Update never consulted _active; the flag existed and
nothing asked it.

Death now removes and deactivates in one transition, and a deactivated entity
does not update. Deliberately NOT a TryGetValue in LockWeapon: that silences one
call site and leaves every other behaviour running on a corpse.

Also re-anchors the seven mutations stranded by the diagnostics refactor
(72c0109c), which routed HitProbability through Inspect and rewrote the lines
four harnesses pin -- two of them no-op controls, so those harnesses were
defending nothing. The rules are unchanged; only the text moved. Anchors stay
strict: loudly stale is what surfaced this within one run.

The heat spike, motionless ships and shots that registered no hits remain
UNEXPLAINED and open. See the map's 8.3 -- unreproducible since the diagnostics
changed timing, so recorded with the unclamped dt as the place to probe rather
than patched speculatively.

226 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 00:50:57 +02:00
Meta Vens
f446e5c2c2 Cut 8 map: a removed entity is a dead entity
The operator's play smoke crashed in LockWeapon reaching EntityInfoGathered as a
ship vanished. Cause: Cut 5.6 added death removal from the zone but not the
matching Deactivate, so a dead entity kept living subscriptions and stale
references while every survivor dropped it -- and because Zone.Update iterates a
snapshot, it still got one more Update after the world had forgotten it. Nothing
in Entity.Update consults _active.

TryDock has always paired removal with Deactivate; the death path just did half
of it. The fix is that pairing plus an _active guard, not a TryGetValue in
LockWeapon, which would silence one call site and leave every other behaviour
running on a corpse.

Also records the symptoms this does NOT explain -- the heat spike, motionless
ships and shots landing nothing -- as open, with the unclamped dt and the
unguarded division by it as the place to probe rather than patch.

And re-anchors the seven mutations stranded by the diagnostics refactor, which
rewrote the lines four harnesses pin. Two of the seven are no-op controls, so
those harnesses currently defend nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 00:17:25 +02:00
Meta Vens
72c0109cf1 Add live fire control diagnostics 2026-09-20 21:58:44 +02:00
Meta Vens
6056327935 Merge fire control Cut 6d: where a hit lands is a dart throw, not a coin flip
Precision was a coin flip between landing exactly on the aimed item and landing
uniformly anywhere on the hull, so aiming at a stern thruster scattered its
misses across the bow -- no nearer the thing aimed at than never aiming at all.
One Gaussian kernel over the hull schematic now answers both halves: its
per-cell weights place the hit, and its on-hull mass prices the stray into
HitProbability, so a thin limb costs visible hit chance before the trigger and
centre mass is the safe play. One function, so the placement and the probability
cannot drift apart -- Cut 3's named risk was exactly two functions answering
where a shot goes.

There is still only one roll. A shot that passes it always lands on metal,
because the chance of the dart straying off the hull was charged at the roll.

Operator-facing consequences, measured rather than assumed:

- pOnHull and the existing pSpread term do NOT double-charge. pSpread prices the
  weapon's own dispersion cone against the silhouette and is 1 for any zero-
  spread weapon; pOnHull prices the targeting system's aim scatter against real
  schematic metal around the actual aim point. Both keep their jobs.
- AI willingness shifts only where it should. Unaided fire is unaffected --
  UnaidedAccuracy already caps it below AgentMinHitProbability. Aided fire at
  centre mass is barely touched. Aimed shots at thin subsystems can now fall
  below the threshold, so NPCs get pickier about sniping small targets. That is
  the tradeoff working, and AgentMinHitProbability is the operator's knob.

Also fixes two pre-existing defects the kernel exposed. FireAuthorityTests.
ShieldTakesHit fired damage 10 at a fixture whose equipped gear had durability
10, so the item absorbed the hit whole and the hull never moved -- the shield
branch could have been deleted outright and the test would have passed. And the
Cut 3 harness still named a test Cut 7 had renamed.

222 tests, all seven mutation harnesses clean with tree-clean PASS, catalog
reads 53 designs / 47 products, 0 dangling refs, Unity batchmode exit 0 with 0
error CS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 22:46:10 +02:00
Meta Vens
e7be36ddd7 Apply targeting-catalog-6d: re-author Precision for the two targeting designs 2026-09-19 22:20:44 +02:00
Meta Vens
bf4744448f Add targeting-catalog-6d migration command to AetherDb tool 2026-09-19 22:20:22 +02:00
Meta Vens
aa850a3cbe Fix ShieldTakesHit coincidental pass and cut3 harness stale test name
ShieldTakesHit: damage raised from 10 to 50, past the Reactor/Shield gear's own Durability of 10. Under the dart-throw kernel, Build's default Precision groups unaimed shots tightly enough around this small hull's centre of mass that this fixture's deterministic single shot lands on one of the two equipped Tool items. At damage 10 that happened to exactly consume the item's own durability with zero remainder, so hull durability stayed unchanged with or without the shield-absorb branch -- the mutation this test is supposed to kill was surviving by coincidence, not because the rule held.

mutation_tests_fire_control_cut3.sh: its closing section still referenced FireAuthorityTests.OutOfArcConsumesNoDraw, a test Cut 7 renamed to CombatNeverDrawsFromTheSharedStream before this branch started (verified against the merge-base commit). Replaced the stale not-yet-reached note with a real mutation against the current test: Commit reads shot.Source.ItemManager.Random once, restoring the shared-stream touch Cut 6b removed.
2026-09-19 22:16:25 +02:00
Meta Vens
8212a0149d Cut 6d: mutation harness for the dart-throw kernel
Modelled on tests/mutation_tests_fire_control_cut5.sh: a no-op control, byte-exact I/O, sha256-verified restore after every mutation, and an explicit tree-clean verdict at exit. Runs the four mutations named in the spec's Verification block plus the fifth this cut's own test file declares for EveryHitLandsOnMetal. Verified clean: all five checks behaved as declared, tree-clean PASS.
2026-09-19 21:56:14 +02:00
Meta Vens
b3d4771358 Cut 6d: tests for the dart-throw kernel invariants
Adds AimingAtTheSternHitsTheStern, ThinLimbCostsHitChance, PlacementAndProbabilityShareOneKernel and EveryHitLandsOnMetal per the spec's Verification block. Each test builds its own synthetic hull/catalog fixture. The aim marker in every fixture is authored with very high durability so it survives the whole sample of shots without dropping out of the reveal ranking partway through.
2026-09-19 21:52:56 +02:00
Meta Vens
d528d88883 Cut 6d: the dart-throw kernel replaces the coin-flip landing rule
FireControl.Commit used to place a hit with a coin flip between exactly
the aimed item and a uniform-random hull cell -- no spatial term, so a
near-miss on a stern item was as likely to land in the bow as the stern.

One Gaussian kernel, in hull-schematic cell units, now decides both
halves of "where will this shot go": w(cell) = exp(-d^2/2*sigma^2) over
every occupied cell, sigma = 1/Precision. HitProbability folds the
kernel's on-hull mass share (pOnHull) into PBase at fire time (R10/Q6
unaffected); Commit draws the landing cell from the same weights. A shot
that passes the roll always lands on metal -- the off-hull chance is
priced into that one roll, never resolved as a second miss stage. The
uniform-random branch is deleted outright, not demoted.

Precision now feeds every shot's pOnHull, aimed or not, so its unaided
fallback (FireControl.Precision) reads GameplaySettings.UnaidedPrecision
instead of a bare 0 -- the same shape as the existing UnaidedAccuracy/
UnaidedTracking floors. pOnHull is saturated: the discrete sum over-
estimates the continuous kernel's integral once sigma drops below about
one cell, and a mass share cannot exceed the whole kernel.

Existing fixtures across FireAuthorityTests, FireControlCut4/5/6Tests and
TargetingSystemTests authored Precision as 0 (or relied on the old 0
fallback) purely as a don't-care value under the coin-flip rule, where it
only gated the aimed-item branch. Under the kernel it drives every shot's
sigma, so those fixtures are re-authored to keep pOnHull near its
ceiling and stay pinned on what they actually test.

mutation_tests_fire_control_cut3.sh's two anchors into HitProbability's
return line and the deleted coin-flip branch are updated to the
equivalent lines/mutations on the new code.
2026-09-19 21:37:39 +02:00
Meta Vens
077b08e662 Merge fire control Cut 7: the harnesses that were prose
Cuts 1-3 declared mutation tables in this document and nobody ever wrote them,
which is why a Soul pass could write five mutations against rules the campaign
claims to pin and watch all five survive a 197-green suite. Those tables now
exist as committed harnesses -- six in the repo where there was one this
morning, each with a no-op control and a sha256-verified tree-clean verdict.

Three findings came out of writing them, all the same shape: a test whose
fixture could not tell the mutant from the original.

- ShotResolvesOnArrival stepped the zone from t=4 to t=6 in one tick, crossing
  both the commit horizon and arrival, so it could not tell an outcome applied
  on arrival from one applied at commit -- R4's central distinction, undefended.
- OutOfArcConsumesNoDraw could not be killed at all, because Cut 6b had made it
  vacuous: the shared RNG stream it guarded no longer exists, so it passed
  identically with the target in arc. Rewritten to pin the rule that is live.
- 7.1 pins the reveal re-check at the shot's freeze, the fifth mutation Soul
  left standing.

And 7.4: the shipped catalog is now opened by a test that enumerates every
document type. Verified by reintroducing the exact defect that broke the 6b/6c
merge -- it fails with the same MessagePack nil error, so the one hole that let
a branch brick the catalog while passing every gate is closed.

218 tests, all six harnesses clean, Unity batchmode exit 0 with 0 error CS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 20:16:12 +02:00
Meta Vens
0ae8226ccb 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>
2026-09-19 20:14:16 +02:00
Meta Vens
76758dae5d Cut 7.2: the Cut 3 harness, and the fixture that let commit-is-not-arrival slip
ShotResolvesOnArrival stepped the zone from t=4 straight to t=6, crossing both
the commit horizon (4.5s) and arrival (5s) inside one Zone.Update. A mutation
collapsing Step's arrival gate to `if (shot.Committed)` therefore survived it:
one wide tick crosses both thresholds either way, so the test could not tell
"applied on arrival" from "applied as soon as committed". R4's central
distinction -- the commit decides the outcome, arrival performs it -- was
declared in Cut 3's prose, pinned by nothing, and this is the third rule in this
campaign found undefended by a fixture too coarse to distinguish the mutant.

Adds a tick at t=4.55, past commit and short of arrival, where the durability
must still be untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 19:56:39 +02:00
Meta Vens
d937364354 Cut 7.2: mutation harnesses for Cuts 1 and 2
tests/mutation_tests_fire_control_cut1.sh and cut2.sh, modelled on
cut5.sh: byte-exact I/O, sha256-verified restore per mutation, explicit
tree-clean verdict. Each runs the mutation table its own cut already
declares in docs/fire-control-cut.md.

Two adaptations forced by the Body having moved since those cuts were
written (both cut2.sh's own table entries, and its header comment):
Cut 1's CombatStateStepsHeadless mutation targets FireControl.MountDirection
instead of the now-deleted HardpointTransforms readback, since Cut 3 moved
the AI's aim-direction path there; Cut 2's EveryArmedLoadoutGetsATargetingSystem
mutation targets the surviving acquisition predicate instead of a
`required: true` flag Cut 6c.2 already turned into a deliberate
degrade-and-log path.

TargetingSystemTests.cs's RevealOrder fixture equipped its three items in
the same order FireControl.IsRevealed's ranking would put them in, so
dropping that ranking entirely was unobservable -- Entity.Equipment's raw
enumeration order already matched by coincidence. Reordered the fixture's
equip calls to the opposite order (size-ascending, hardpoint-last) so the
mutation is actually distinguishable; assertions are unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 19:43:57 +02:00
Meta Vens
fd7ef71c15 Cut 7.1/7.4: the reveal re-check pin and the live-catalog test
FireControlCut7Tests.cs adds the fifth mutation Soul found survived 197
green tests -- FireControl.Fire freezing source.TargetItem.Value instead
of the reveal-re-checked ResolvedTargetItem (7.1) -- and the campaign's
first assertion that the shipped GameData/Aetheria.cc catalog is actually
readable by the shipped code and can generate an armed LonginusX loadout
(7.3/7.4). The live-catalog test builds its own scoped CultDocumentRegistry
rather than AetheriaStores.Open's default shared one, because the shared
registry auto-discovers every [CultDocument] type loaded anywhere in the
test process -- including AetheriaStoresTests.cs's own TestCatalogGlobal,
a fixture type the real catalog has never heard of and was never meant to
satisfy.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 19:33:09 +02:00
Meta Vens
ef223c1d9b Cut 7 amendment: the live-catalog test is the campaign's only readability check
Merging 6b and 6c bricked the catalog -- a new non-nullable float at a new
MessagePack key cannot read records written before it existed, so
AetheriaStores.Open threw and the game could not load its own data. Both
branches were green: one never opened the real catalog, the other never had the
field.

Cut 7's live-catalog test therefore has to enumerate every document type rather
than just look for a design, because a lazy read that never touches
WeaponItemData would have passed the broken merge too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 19:13:04 +02:00
Meta Vens
c896489b39 Fix the merge of Cuts 6b and 6c: a new float field made the catalog unreadable
Cut 6b added WeaponItemData.AirburstRange as a non-nullable float at Key(29).
Every WeaponItemData record in GameData/Aetheria.cc was serialized before that
key existed, so the reader is handed nil for the absent slot and a float cannot
take nil -- AetheriaStores.Open threw MessagePackSerializationException and the
game could not read its own catalog. AirburstRange becomes float?, null meaning
no authored burst radius, which is also the more honest reading of a value no
product sets yet.

Neither cut could have caught this alone. 6b verified with unit tests over
synthetic fixtures and never opened the shipped catalog; 6c opened the catalog
constantly but on a branch without the new field. It appears only in the merge,
and only when something actually reads GameData.

The general rule, which will recur: a new non-nullable value-type field on a
persisted CultCache document cannot read records written before it existed.
Nullable, or a migration that rewrites every affected record -- and a migration
cannot run if the read already throws, so tolerant deserialization comes first.

Verified on the merged tree: catalog reads (53 designs, 47 products), 0 dangling
refs, 215 tests pass, and loadout with all three targeting sellers excluded
generates every hull with 1 armed hull falling back to unaided fire.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 18:56:53 +02:00
Meta Vens
6636e757c7 Merge fire control Cut 6c: the formula gets fixed, and the single point of failure
Cut 6a authored the premium targeting system strictly worse than the cheap one,
because Resolution is consumed as a cost while its name promises a benefit.
Operator ruling: fix the formula, not the handle. HitProbability now derives its
ceiling reciprocally, so higher Resolution is a sharper instrument and the
unaided fallback of 1 becomes the floor by construction rather than by accident.
No rename.

Both designs also shipped from a single seller, and galaxy generation draws a
random subset of factions -- so ship generation still threw in any galaxy that
missed that draw, and Cut 6a's ten green seeds only sampled the die. Targeting
now comes from the roster's own sensor factions, Finch and Lucent, alongside
NiteLife; and FillInterior degrades to unaided fire instead of throwing when no
seller is present, which is only safe because Cut 5.1 gave unaided fire a floor.

Also records that the existing loadout command runs with a null galaxy, where
IsAvailable passes everything -- so availability had never been exercised by any
verification in this campaign until the exclude-sellers mode this cut adds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 18:52:26 +02:00
Meta Vens
06a9098244 Fire control Cut 6c data: Resolution re-authored, Finch and Lucent join NiteLife as targeting sellers
AetherDb targeting-catalog-6c apply, landed:

- Targeting Computer: Resolution .3 -> 2 (sensor-limited until info reaches
  .55 under the new reciprocal reading)
- Fire Control Array: Resolution .75 -> 4 (until .325) -- now unambiguously
  ahead of the 1-cell design at every info level, fixing the strictly-worse
  regression Cut 6a's review found
- 4 new products: Panopticon / Panopticon Prime (Finch Cybernetics, passive
  sensors) and ClapBack / ClapBack Ultra (Lucent Media, active sensors/lasers)
  -- each design now has three sellers instead of one

Census: 53 designs (unchanged), 47 products (was 43). AetherDb dangling: 0.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 18:50:21 +02:00
Meta Vens
dcc38af6fd Fire control Cut 6c tooling: targeting-catalog-6c migration, loadout exclude-sellers, mutation harness
AetherDb targeting-catalog-6c (dry run unless "apply"): re-authors Targeting
Computer/Fire Control Array's Resolution onto the reciprocal-reading scale
(.3->2, .75->4, matching Cut 6c.1's operator ruling) and adds a Finch
Cybernetics and a Lucent Media product for each design, alongside the
existing NiteLife ones, so a galaxy missing any one seller still has two more
chances to sell a targeting system (Cut 6c.2).

AetherDb loadout gains "exclude-sellers:Short1,Short2,..." -- the only way to
actually exercise Galaxy.ContainsFaction from this command, since the default
null-Galaxy run treats every product as available and can never reproduce the
single point of failure 6c.2 fixed. Builds a minimal one-zone Galaxy via the
same SavedGame/Galaxy round trip as FireControlCut6cTests, backed by a scratch
run store under the OS temp directory so GameData/run.cc is never touched.

Adds tests/mutation_tests_fire_control_cut6c.sh, modelled on Cut 5's harness:
no-op control, byte-exact I/O, sha256-verified restore after every mutation,
explicit tree-clean verdict.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 18:50:11 +02:00
Meta Vens
507faf6e84 Merge fire control Cut 6b: a shot's dice belong to the shot
Commit drew from ItemManager.Random, a stream shared with wormhole exit
velocity, loadout generation, travel and every AI's own DPS sampling -- so
adding an NPC or opening a shop reordered every subsequent die, and Q5's
reproducible fight held only inside a hermetic fixture. The roll is now a pure
function of (zone identity, shot id): no stream, no ordering dependence, and
nothing outside Commit can seed or advance a combat draw.

Airburst was deleted with the Physics.OverlapSphere path in Cut 4 and never
rewired, so a flak round flew and did nothing. It now resolves in the simulation
-- Splash instead of Apply at arrival, never both -- rather than going back into
the Projectile MonoBehaviour, which is the authority Cut 3 spent itself
deleting. No catalog weapon authors a burst radius yet, so the flag is wired and
inert until an authoring pass sets one.

Verified independently of the agent report: 213 tests pass, all four mutations
die, the no-op control stays green, both negative greps are clean, and every
target file is byte-identical to sha256 hashes taken before the run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 18:44:18 +02:00
Meta Vens
1a1cb8f3d1 Fire control Cut 6b: mutation harness, and comments that leaked past the negative greps
tests/mutation_tests_fire_control_cut6.sh, modelled on Cut 5's harness: a no-op
control, four mutations (6.1's shared-stream generator and dropped-ShotId seed;
6.2's double-application and unconditional-splash), byte-exact I/O with
sha256-verified restore after every mutation, and the two negative greps the
Cut 6 spec names. All five mutations behave as declared and the tree-clean
verdict is PASS.

Fixes two of Commit's and Projectile's own explanatory comments that quoted the
literal strings the negative greps check for (ItemManager.Random, Airburst),
which the first harness run caught as false failures -- reworded, no behavior
change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 18:41:38 +02:00
Meta Vens
39ad4bfc80 Fire control Cut 6b: a shot's dice belong to the shot, and airburst resolves in the simulation
6.1 (Soul finding 6): Commit builds a local CultMath.Random from (Zone.CombatSeed,
shot.ShotId) instead of reading and writing the shared ItemManager.Random stream.
Zone exposes CombatSeed (the same expression that already seeds its own _random),
so a shot's outcome is a pure function of zone identity and shot id -- immune to
whatever else drew from the shared stream in between.

6.2 (Soul finding 5): airburst is wired into the simulation instead of back into
Projectile.cs. PendingShot gains BurstPosition/BurstRadius, frozen at Fire time
from PredictedIntercept and the weapon's authored WeaponItemData.AirburstRange
when WeaponModifiers carries Airburst. FireControl.Step resolves such a shot via
Splash instead of Apply, never both. Projectile.cs/ProjectileManager.cs drop the
dead AirburstDistance/AirburstRange fields and write-back.

Tests: FireControlCut6Tests.cs pins both rules (208 baseline + 5 new, all green).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 18:34:04 +02:00
Meta Vens
29fcc3ee9e Fire control Cut 6c.1/6c.2: Resolution becomes reciprocal, targeting degrades instead of throwing
6c.1 (operator ruling 2026-09-19): Resolution stays a benefit -- higher is
better -- so HitProbability derives its sensor ceiling as the reciprocal of
Resolution instead of reading Resolution as the ceiling directly. The
authored 1-cell/2-cell targeting systems' Resolution values move to the new
scale in the next (data) commit.

6c.2: LoadoutGenerator.FillInterior no longer throws InvalidLoadoutException
when no targeting-system product is available to a galaxy -- it degrades to
no targeting system and logs the gap, safe only because Cut 5.1 already gave
unaided fire a floor (GameplaySettings.UnaidedTracking).

Adds FireControlCut6cTests.cs pinning both: HigherResolutionYieldsHigherOrEqualHitProbability
(dies if Resolution is read as the ceiling directly) and
NoSellerDegradesToUnaidedInsteadOfThrowing (dies if the throw is restored).

dotnet test tests/Aetheria.Shared.Tests: 210 passed, 0 failed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 18:31:46 +02:00
Meta Vens
063804801f Merge fire control Cut 6a: the catalog data Cut 2 never shipped
Cut 2 landed LoadoutGenerator's required-targeting-system rule and no targeting
system to satisfy it, so AetherDb loadout threw InvalidLoadoutException and the
player's own hull could not be generated. The turret firing-arc migration was
likewise written and never run, leaving a turret engaging only within 60 degrees
of its spawn facing.

Two targeting designs with products, four products for previously unsold designs,
and the arc migration applied: 53 designs, 43 products.

Cut 6c amends two defects in this authoring -- Resolution reads as a cost so the
premium system is worse than the cheap one, and a single seller makes ship
generation depend on whether a galaxy happens to draw that faction.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 18:14:14 +02:00
Meta Vens
cf47f9d26e Merge fire control Cut 5: close the rules that landed on one path and not its twin
Seven code defects Soul confirmed across the campaign, every one the same shape:
a rule implemented on one path and left off the path beside it. Tracking gets a
floor instead of a cliff; a shot the shield cannot absorb now breaks it on the
discrete path as well as in Splash; a beam obeys its firing arc; one bearing
test owns the point-blank case; a destroyed subsystem stops being the aim point;
the simulation removes its own corpses instead of relying on Unity's loot-drop
subscription; and PendingShot drops two fields nothing read.

Cut 5b then repaired the mutation harness, which had been leaking un-restored
mutations into the working tree -- that leak, not a matcher bug, was also why two
anchors appeared unmatchable. Restore is now hash-verified with an explicit
tree-clean verdict, and ConstantWeapon's PowerRequest gate is pinned separately
from Execute's, which is where the one surviving mutation had been hiding.

Verified independently of the agent reports: 208 tests pass, all eleven
mutations die, the no-op control stays green, and every target file is
byte-identical to sha256 hashes taken before the run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 17:35:31 +02:00
Meta Vens
3bf1e40795 Fire control Cut 6d map: where a hit lands is a dart throw, not a coin flip
Precision is currently a coin flip between landing exactly on the aimed item and
landing uniformly anywhere on the hull, so aiming at a stern thruster scatters
its misses across the bow. Operator direction: misses should cluster near what
you aimed at, like throwing a dart at the grid -- and aiming should therefore be
a real tradeoff, where a thin limb risks missing the ship entirely and centre
mass is the safe play.

One Gaussian kernel over the hull schematic answers both halves: its on-hull
mass prices the stray into HitProbability, so the risk is legible on the HUD
before the trigger, and its per-cell weights place the hit. One function, so the
probability and the placement cannot drift apart -- Cut 3's named risk was
exactly two functions answering where a shot goes.

Records the rejected draft that clamped a stray dart to the nearest hull cell,
which would have quietly removed the tradeoff, and the two consequences worth
catching early: pOnHull may double-charge with the existing pSpread term, and
every AI gates on this number so they will hold fire more once it lands.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 17:30:39 +02:00
Meta Vens
35b8b6a550 Fire control Cut 5b.3: make the mutation harness's restore provable
Three runs of tests/mutation_tests_fire_control_cut5.sh left mutated files on
disk (ArcAllowsFire hardcoded true in Weapon.cs, InstantWeapon.cs's arc gate
deleted, FireControl.cs still carrying 5.1's own mutation) despite its
`trap restore_all EXIT INT TERM`: a trap cannot survive a SIGKILL of the
process group, and nothing before this cut ever checked that a `cp -p`
restore actually reproduced the original bytes.

Records each target file's sha256 before any mutation runs. Every restore --
per-mutation, immediately after each check_mutation, and at exit -- re-hashes
and aborts loudly on a mismatch instead of trusting `cp -p` silently. The
final summary and the exit-time trap both print an explicit tree-clean
PASS/FAIL verdict so a reader never has to infer it. The no-op control stays
first, unchanged.

Investigated the two anchors reported as "found 0" (Zone.cs's Death
subscription, Weapon.cs's InArc line) before touching anything, per the
mandate not to hand-edit them into a shape that happens to match: on this
worktree at HEAD, both anchors are single-line literals with no embedded
newline, so CRLF-vs-LF in the surrounding file cannot affect whether the
substring is found -- the CRLF theory does not hold up. Both anchors match
exactly once against the current, clean tree (verified directly with the
same perl matcher this script uses, and confirmed by a full harness run).
The mutated replacement text for each of those two anchors is byte-for-byte
identical to the exact leaked mutation the task description names as having
been left on disk by an earlier run (Weapon.cs's `return true;`, Zone.cs's
deleted Death subscription) -- i.e. the "anchor never matches" failures were
not an independent matching-mechanism bug, but a direct symptom of the same
leaked-mutation-state problem this cut's hash verification now closes. No
anchor literal was changed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 17:28:48 +02:00
Meta Vens
0020d597f6 Fire control Cut 5b.1: pin ConstantWeapon's PowerRequest arc gate, not just Execute's
ConstantWeaponObeysArc only asserted PowerRequest()==0 after Zone.Update had
already run Execute, which safes _firing off for its own (also-correct)
reason first. PowerRequest short-circuits on _firing regardless of what
ArcAllowsFire mutates to, so a mutation dropping ArcAllowsFire from
PowerRequest alone was an unpinned survivor even though the harness's own
Execute-line mutation was already caught. Added the same assertion before
Zone.Update, while _firing is still true and StanceAllowsFire is true, so
ArcAllowsFire is the only thing it can be testing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 17:28:32 +02:00
Meta Vens
9b69286783 Fire control Cut 5: make TrackingIsAFloorNotACliff deterministic
The multi-tick, many-Update-call version of this test (5.1) was flaky under
repeated runs: stepping a 5s flight in 50 separate .1s ticks left the exact
commit-time reading of `now` sensitive to float-accumulation/JIT-tiering
drift, occasionally producing zero hits across all 30 shots even on
unmutated code. Rewritten with velocity 0 so each shot's flight time (and so
its commit horizon) is zero -- one Fire call, one Update call, no
accumulated ticks to drift. Confirmed clean across repeated standalone runs
and still kills the 5.1 fallback-to-0f mutation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 17:11:22 +02:00
Meta Vens
c4c241e999 Fire control Cuts 6, 6c and 7 maps
Cut 6b: the combat roll stops drawing from the shared ItemManager.Random stream
and becomes a pure function of (zone identity, shot id), so adding an NPC or
opening a shop can no longer reorder an existing fight's dice. Airburst resolves
in the simulation rather than going back into Projectile, which would be exactly
the presentation authority Cut 3 spent itself deleting.

Cut 6c: reviewing Cut 6a's landed catalog found the premium targeting system
strictly worse than the cheap one, because Resolution reads as a cost while its
name promises a benefit. Operator ruling: fix the formula, not the handle -- it
becomes reciprocal, higher stays better, no rename. Also: both designs ship from
one seller, and galaxy generation draws a random subset of factions, so ship
generation still throws in any galaxy that misses that draw.

Cut 7: the mutation harnesses for Cuts 1-3 that the map declared and nobody ever
wrote, plus the fifth mutation Soul left standing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 16:58:22 +02:00
Meta Vens
df5f763187 Fire control Cut 5: close the rules that landed on one path and not its twin
Soul's pass over the whole campaign confirmed eleven findings. Seven were code
defects of one shape -- a rule implemented on one path and left off the path
beside it -- and this is all seven.

5.1 Tracking falls back to GameplaySettings.UnaidedTracking (new, 10) instead of
    zero, and Commit drops the zero branch that turned a Tracking-less shooter
    into a hard wall. Unaided fire was meant to be really, really bad; it was
    334 shots and no hits against a target drifting at 5 u/s.
5.2 A shot the shield cannot absorb now breaks it. The decision is frozen into
    ShotOutcome.ShieldBroken at commit and performed in Apply, so a shield that
    recharges mid-flight cannot retroactively survive. Splash already did this;
    the discrete roll never did, which made the whole shield-break mechanic
    unreachable for every weapon in the catalog.
5.3 ConstantWeapon gates on ArcAllowsFire alongside StanceAllowsFire. A
    side-mounted beam was firing forward.
5.4 The point-blank bearing case moves out of Weapon.ArcAllowsFire and into
    InArc, which used to normalize the zero vector into NaN. The trigger said
    fire and the probability said impossible.
5.5 A destroyed item drops out of IsRevealed's ranking, so Precision stops
    concentrating hits onto a dead subsystem's footprint.
5.6 Zone subscribes Entity.Death and removes the corpse itself. That removal
    lived only in Unity's loot-drop subscription, so headless nothing ever left
    the zone, shots landed on corpses and no fight could end.
5.7 PendingShot drops PredictedIntercept and FlightTime, written and read
    nowhere.

208 tests pass (197 plus eleven new).

Known defect, recorded rather than hidden: the committed mutation harness is
unreliable. Across two runs it failed to restore FireControl.cs and Weapon.cs,
leaving live mutations in the working tree -- including its own 5.1 mutation,
which is how a green suite briefly reported two failures. Its verdicts are
therefore evidence only where re-run from a verified-clean tree. Two anchors
(Zone.cs's ObserveAdd line, Weapon.cs's InArc return) fail to match at all, and
5.3's ConstantWeaponObeysArc survives its own mutation, so that rule is written
but not pinned. Cut 5b owns the repair; nothing here is trusted on the harness's
word, and the suite result above was taken by hand from a hash-verified tree.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 16:56:40 +02:00
Meta Vens
e5f1318a0c Fire control Cut 6a: land the targeting-catalog authoring output
Runs targeting-catalog apply: two new designs (Targeting Computer, Fire
Control Array) and six new products (LockOn / LockOn Pro by NiteLife Energy;
DragOnBreath by Lucent Media for plight; Leonid by Aeronautics Unlimited for
pswarm; Steadfast by Rossum & Douglas for Core Power; True North by Lightsail
Express for Large Drive).

Verified: census 53 designs/43 products (was 51/37); loadout over ten seeds
no longer throws for LonginusX and each gains exactly one interior targeting
system; dangling 0; a fresh census reopen carries 0 schema issues;
firing-arc-migrate dry run reports nothing left to migrate; dotnet test
tests/Aetheria.Shared.Tests is 197/197.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 16:36:22 +02:00
Meta Vens
365c20d82e Fire control Cut 6a: add the targeting-catalog authoring command
Cut 2 shipped TargetingSystemData and its FireControl/LoadoutGenerator
plumbing but never authored the catalog data it needs: no targeting-system
design exists, so tools/AetherDb loadout throws InvalidLoadoutException for
the player's own hull. This adds targeting-catalog, in the shape of the
existing *-migrate commands (dry run unless "apply"), which authors:

- Two GearData designs, "Targeting Computer" (1-cell) and "Fire Control
  Array" (2-cell), each with EnergyDrawData/HeatData behaviours alongside
  TargetingSystemData (the behaviour supplies no power machinery of its own),
  argued from the capacitor (the catalog's only other bare Tool gadget).
- One product per design from NiteLife Energy, the one manufacturer that
  already sells the capacitor, so IsAvailable finds one without falling back
  to "any manufacturer".
- One product each for the four designs nothing currently sells (plight,
  pswarm, Core Power, Large Drive), matched to the manufacturer the brief
  and the corporate roster already imply.

Not yet run against the catalog; the next commit lands its output.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 16:36:00 +02:00
Meta Vens
3cb077918c Fire control Cut 6a: run the firing-arc migration
tools/AetherDb firing-arc-migrate was written in Cut 2 and never run: the
Turret hull's two Ballistic hardpoints still carried a 0-degree arc, so a
turret only ever engaged within 60 degrees of its spawn facing. Applying it
sets both to 360, matching the operator ruling (fire-control-cut.md Q1) that a
turret's arc is authored data, not a new hardpoint type. A fresh dry run now
reports both hardpoints already authored.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 16:17:33 +02:00
Meta Vens
a44549133c Fire control Cut 5 map: the rules that landed on one path and not its twin
Soul's campaign pass confirmed eleven findings. Seven of them are code defects
of one shape -- a rule implemented on one path and left off the path beside it
-- and belong to this cut. Catalog authoring is Cut 6, alongside airburst and
the shared combat RNG; the missing mutation harnesses are Cut 7.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 15:44:36 +02:00
Meta Vens
ca57819d5c Merge fire control Cut 4: no physics query decides damage anywhere
The last five weapon kinds route through FireControl; HullCollider's hit
and splash reporting is deleted. A beam rolls once per resolve interval.
Mines keep their proximity trigger (Q7) and resolve damage through the
same path. Also fixes a real defect: OnFire?.Invoke(FireControl.Fire(...))
never fired at all when nothing subscribed, which is the headless case.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 15:18:50 +02:00
Meta Vens
034e8d3e78 Fire control Cut 4: pin the beam roll, splash, and the OnFire short-circuit fix
tests/Aetheria.Shared.Tests/FireControlCut4Tests.cs, builds its own minimal
fixtures rather than reusing FireAuthorityTests.cs's InstantWeapon-shaped
Engagement:

- BeamRollsPerInterval: over one second a beam produces exactly
  1/BeamResolveInterval outcomes totalling Damage. Interval (.25s) and tick
  (.125s) are both exact dyadic fractions so float accumulation cannot drift
  the count, and the tick is strictly smaller than the interval so a "roll
  every tick" mutant and a "roll once for the burst" mutant both fail.
- InstantWeaponFiresWithoutAnyOnFireSubscriber: pins the InstantWeapon half
  of the null-conditional short-circuit fix, which no existing test could
  catch (FireAuthorityTests.cs always calls FireControl.Fire directly, never
  through Trigger()/Execute()).
- SplashHitsEveryEntityInRadius / SplashIsDirectional: FireControl.Splash
  called directly against a zone of bare Ship entities (Entity.MapEntity,
  run from the constructor, is enough to give a Ship Armor/GearOccupancy/
  Hull without Activate() or a weapon catalog).

tests/mutation_tests_fire_control_cut4.py is the committed harness: a no-op
control plus one mutation per rule above, run against
tests/Aetheria.Shared.Tests via `dotnet test -p:CultLibRoot=...`. All seven
mutations behaved as declared in this pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 15:17:35 +02:00
Meta Vens
78a4cf36bf Fire control Cut 4, part 2: the last five weapon kinds join FireControl
Deletes the last live Physics.* hit queries and every remaining SendHit/
SendSplash call in Assets/Scripts/Gameplay/Weapons: ConstantLaser's
RaycastAll, ConstantLightning's SphereCastAll, ConstantParticleWeapon's
trigger-collider plumbing, HitscanEffect's RaycastAll, and Mine's blast
OverlapSphere. Each becomes presentation only, the same convention Cut 3
gave Projectile/GuidedProjectile/Laser/Lightning: ConstantLaser/
ConstantLightning/HitscanEffect draw to TargetTransform (or out to Range with
no target); ConstantParticleWeapon keeps only its visual emission.
ConstantWeapon.Execute (previous commit) already rolled the damage before any
of these ever draw.

Mine keeps its proximity-arming OverlapSphere (Q7: contact detection, not hit
detection) but Explode() now calls FireControl.Splash instead of walking
colliders and calling SendSplash itself.

HullCollider.Hit/Splash/SendHit/SendSplash and their event-arg classes are
deleted outright -- their last callers are gone. HullCollider is now purely a
physics surface for ship-on-ship collision impulse; no Physics.* query
decides damage anywhere in Assets/Scripts/Gameplay/Weapons except Mine's own
named exception.

Every manager (ConstantLaserManager, ConstantLightningManager,
ConstantParticleWeaponManager, HitscanManager) is updated to match: no more
Damage/Penetration/Spread/DamageType plumbing into a presentation component
that no longer decides anything with it, and HitscanManager now passes
ShotId and TargetTransform the way LaserManager already does.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 15:17:17 +02:00
Meta Vens
482c47bffe Fire control Cut 4, part 1: FireControl gains Splash and a beam roll
FireControl.Fire takes an optional damageOverride so a continuous weapon can
queue a shot for less than its full Damage. FireControl.Splash is the one new
rule this cut adds: an unconditional area effect (mines, airburst) applying
the shield-absorb-or-hull-takes-it branch per target, then DamageSchematic
over the directional half of each target's own hull that faces the blast --
the rule moved verbatim from the Splash subscription EntityInstance.cs
carried before Cut 3 deleted it (git show b7743789^).

ConstantWeapon.Execute rolls once per GameplaySettings.BeamResolveInterval
(new setting) for Damage*interval, through the same FireControl.Fire/Step
pair a discrete shot uses -- a beam's authored Velocity is 0, so the flight
commits and resolves in the same tick it fires (R4's short-flight
degradation). A beam is a sequence of rolls, not a continuous truth.

Fixes a real defect found while testing the above: both InstantWeapon.Execute
(Cut 3) and the new ConstantWeapon.Execute wrote `OnFire?.Invoke(FireControl.
Fire(...))` inline. C#'s null-conditional short-circuits the whole
expression, argument included, whenever the event has no subscriber -- true
of any headless run with no Unity EntityInstance wired up
(docs/headless-playground-cut.md) -- so a shot silently never fired. Both
now evaluate FireControl.Fire into a local before the null-conditional
invoke.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 15:16:57 +02:00
Meta Vens
8e719d6dba Add the CultMath bridge using that Cut 3 dropped
Cut 3 landed 39 compile errors in GuidedProjectile: it uses ToCultMath
throughout and lost the using directive. Caught by batchmode on merge, not
by the pass that wrote it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 14:10:59 +02:00
Meta Vens
a7c09e06fc Merge fire control Cut 3: dice decide hits, not physics
The raycast hit path is gone from Projectile, GuidedProjectile, Laser and
Lightning; FireControl owns whether a shot lands. A shot freezes its gun's
stats at the trigger, commits its outcome a fixed time before impact, and
resolves on arrival against how far the target deviated. Rolls are seeded,
so a seeded engagement reproduces. Five weapon kinds still on the old path
belong to Cut 4.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 14:08:52 +02:00
Meta Vens
b7743789c8 Fire control Cut 3: hit authority moves to FireControl
Deletes the Unity physics hit path (HullCollider.Hit/Splash, and the
raycast/shield/SendHit blocks in Projectile, GuidedProjectile, Laser and
Lightning) and replaces it with a single owner in the same cut: FireControl
now decides whether a shot hits, Zone owns the pending-shot lifecycle, and
Entity.DamageSchematic/ApplyHit (moved verbatim from EntityInstance) apply the
result. Presentation subscribes to Zone.ShotCommitted/ShotResolved and applies
nothing.

FireControl.HitProbability is the pure targeting solution (Accuracy * pSensor
* pSpread, gated by detection/range/lock/arc). FireControl.Fire freezes the
weapon's payload and the shooter's resolved targeting stats at the instant the
trigger is pulled (Q6), computes the predicted intercept through the one
shared PredictedIntercept function (CombatState now calls the same function
instead of duplicating first_order_intercept), and queues a PendingShot.
FireControl.Step commits each shot's outcome at ArrivalTime - CommitHorizon --
deviation from the predicted intercept, measured once, live, at that instant
-- and applies it at ArrivalTime. A shot whose flight is shorter than the
horizon commits and resolves in the same tick it fires. Rolls draw from the
shooter's own ItemManager.Random (Q5, seeded and reproducible); an out-of-arc
or otherwise gated shot draws nothing.

Player fire is arc-gated through the same predicate AI and turret fire use
(Weapon.ArcAllowsFire, checked in InstantWeapon.Trigger -- the one point every
shooter's trigger passes through), closing Q2.

Adds GameplaySettings.CommitHorizon, SchematicCellSize and
AgentMinHitProbability (first guesses; the headless suite is the tuning
harness, operator play smoke is the arbiter).

FORK reported, not worked around silently: HullCollider.SendHit/SendSplash
cannot be deleted yet without breaking ConstantLaser.cs, ConstantLightning.cs,
ConstantParticleWeapon.cs, HitscanEffect.cs and Mine.cs, which still call them
from their own live Physics queries -- that's Cut 4's migration. They fire
into the void today: nothing subscribes to Hit/Splash any more for the four
weapon kinds this cut owns.

tests/Aetheria.Shared.Tests/FireAuthorityTests.cs pins the roll's seeding, the
gate-consumes-no-draw rule, resolve-on-arrival, the commit horizon and its
evasion window, short-flight degradation, the frozen-payload snapshot, aimed
hits, the item-then-hull damage order, the planar penetration march and the
shield-absorbs branch. FireControlTests.cs and InputCapacitorTests.cs are
updated for the new AI gate and OnFire(int) signature.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 14:07:07 +02:00
Meta Vens
3cf4603475 Merge fire control Cut 2: targeting subsystem and subsystem reveal
A new interior Tool behaviour carrying accuracy, resolution, precision and
tracking; unaided fire falls back to one authored setting. The two info
thresholds are finally read: a target's equipment reveals as target data
accumulates, and one predicate and one writer serve both the player and
the AI when aiming at a revealed subsystem.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 13:04:43 +02:00
Meta Vens
1875065477 Pin the targeting subsystem, reveal rule and Q4's required-equipment rule
TargetingSystemTests: selection needs a real reveal (and drops it again
on decay, with no clearing loop), the reveal ranking (hardpoint before
interior, larger before smaller), the two thresholds are the ones
actually read, unaided fire falls back correctly for no/destroyed/
disabled systems, a starved system's Accuracy halves with the grant
(a real Reactor + PowerBus tick, since PowerSupply's setter is internal
to a different assembly here), and every armed generated loadout gets a
targeting system across ten seeds.

LoadoutTests' shared fixture now authors a targeting-system product too:
its "Twin" hull can randomly draw the weapon-carrying "Gun" onto its
Sensors hardpoint, and FillInterior's new required item would otherwise
throw for a fixture that predates this cut.

174 tests before this cut; 180 after (174 unchanged + 6 new), all green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 13:03:12 +02:00
Meta Vens
04c0943fa0 Wire subsystem selection and reveal into player input and the HUD
A new "Cycle Target Item" action (hand-authored into Aetheria.inputactions;
no Unity editor here to regenerate AetheriaInput.cs, so ActionGameManager
looks it up by name through Input.asset.FindAction instead of a typed
accessor) cycles the local player's aim point among the current target's
revealed items via TrySelectTargetItem. SchematicDisplay's enemy view now
filters to what FireControl.IsRevealed actually resolves for the viewer,
and marks whichever row is the current ResolvedTargetItem through a new
SchematicListElement.SelectedIndicator (unwired in any prefab yet -- a
no-op until an editor pass assigns one).

Presentation only; decides nothing. Unverified here -- Unity batchmode
is unavailable in this environment.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 13:02:54 +02:00