From 1d2db9acd55eff4991b8ddda29d194645f84791b Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Tue, 29 Sep 2026 13:09:45 +0200 Subject: [PATCH] Record operator rulings on the mining map (2026-09-30) Co-Authored-By: Claude Opus 5.5 --- docs/mining-cut-refresh.md | 1067 +++++++++++++++++++++++------------- 1 file changed, 684 insertions(+), 383 deletions(-) diff --git a/docs/mining-cut-refresh.md b/docs/mining-cut-refresh.md index 2e99d88f6..c9901868e 100644 --- a/docs/mining-cut-refresh.md +++ b/docs/mining-cut-refresh.md @@ -1,8 +1,44 @@ -# Aetheria Codex assimilation: refreshed cut maps (mining, moddable ships) +# Mining: refreshed cut map (v2, operator rulings of 2026-09-30) -Date 2026-09-29. Imagination output for Self. It re-anchors two Codex lanes to the Body as it will stand after -`codex/fire-control-12` merges to `master`. The Codex maps are raw material, not authority. Where this file and -a Codex map disagree, this file is the proposal and the Codex map is the input. +v1 dated 2026-09-29; this is v2, 2026-09-30. Imagination output for Self. It re-anchors the Codex mining lane to +the Body as it will stand after `codex/fire-control-12` merges to `master`, and it incorporates the operator's +rulings of 2026-09-30: + +| Question | Ruling | +|---|---| +| Q4 | A | +| Q5 | A | +| Q6a | A | +| Q6b | A | +| Q7 | A′ | +| Q8 | **B** | +| Q9 | **C** | +| Q10 | A | +| Q11 | authored catalog records that yield simple commodities only | +| Q12 | A | +| Q13 | A, plus the detection correction | + +The Codex map (`docs/mining-cut.md`) is raw material, not authority. Where the two disagree, this file is the +proposal. + +**What changed from v1:** +- **Chunk detection** now goes through the existing reflectance rule. It is no longer unconditional, and it is not + deferred to EW (Cut 3). +- **Cut 6 is deleted.** AI mining is deferred, and the same-path proof is a test agent in Cut 5. +- **Cut 7 authors Corrosive and Ionizing weapons.** +- **The field-kind record arrives in Cut 3** (for cross-section), not Cut 5. +- **Two new forks:** Q14 (how chunk sensor info integrates) and Q16 (how a belt gets its kind). +- **The CultMath-root claim** is settled in the shared precondition. + +The moddable-ships half of v1 lives in `docs/moddable-ships-cut.md` on `codex/moddable-ships`. Section 0's ship +rows are kept for the combined test count. + +## Rulings added 2026-09-30 (Self, after v2) + +- **Q14 A.** A ship's knowledge of a rock is the value the per-tick sensor rule would settle at, computed on + demand through the shared `Sensor.Gain`. It is stateless and costs nothing per tick. +- **Q16 B.** A belt's field kind is chosen at generation and saved. A belt from an older run with no kind gets + one assigned once on first load, by the same function, and that assignment is saved. ## How this was established @@ -72,7 +108,7 @@ in the merged tree; the catalog oid `2aa79c0a` is the same in all three merges a | Turret | 7 | 3 | 5 | | Zenith | 5 | 1 | 5 | - All five hulls use `Prefab`. This matters to ship migration (S5). + All five hulls use `Prefab`. This matters to ship migration (`docs/moddable-ships-cut.md` S5). ## Shared precondition for both campaigns @@ -89,6 +125,39 @@ git switch codex/moddable-ships && git merge --no-ff master and the ships doc cites its commits' results. A rebase rewrites every one of those SHAs, which strands the maps' evidence. The merge has no conflicts (below), so it costs one merge commit per branch. +### Building fire-control-12 headless needs two CultLib checkouts + +Soul (ships branch) reported that fire-control-12 does not compile against its pinned CultLib `45c2f40`, with +`erf` and `erfinv` missing. v1's merges ran green on `45c2f40`. **Both claims are true. They describe two +different build invocations.** + +- `Directory.Build.props` @`b66ba524` pins two revisions of the same repository: `CultLibRevision` `45c2f40` + (CultCache) and `CultMathRevision` `6d5e209` (CultMath, tag `cultmath-unity-v0.2.4`). +- `CultMathRoot` defaults to `CultLibRoot` when it is not passed. +- `erf` and `erfinv` exist in CultMath at `6d5e209` (`packages/cultmath/src/CultMath/math.cs:528`). They do not + exist at `45c2f40`: `git grep` finds neither there. +- `FireControl.cs` calls them at `:1323` and `:1348` @`b66ba524` (Cut 12.0 added them to CultMath). + +Probed on Yggdrasil at `b66ba524`, `dotnet build Aetheria.Shared`: + +| Invocation | Result | +|---|---| +| A. `-p:CultLibRoot=/CultLib` only (at `45c2f40`) | **fails**: `FireControl.cs(1323,54) CS0103 'erf'`, `(1348,53) 'erfinv'` | +| B. as A, plus `-p:SkipCultLibRevisionCheck=true` | the same failure | +| C. `-p:CultLibRoot=/CultLib -p:CultMathRoot=/CultMath` (at `6d5e209`) | **builds** (v1's 379/381 runs used this) | + +**The rule for every Hands and Soul job:** pass both roots, each checked out clean at its own pin. Soul's run was +invocation A: a missing CultMath root, not a broken branch. + +**A second defect this surfaced (not a mining cut).** Case A should never have reached the compiler. The +`VerifyCultLibRevision` target in `Directory.Build.targets` exists to refuse a CultMath root at the wrong +revision, and here it stayed silent. Its `Condition` compares `ProjectReference` identities against a path +spelled with backslashes (`$(CultMathRoot)\packages\cultmath\...`). I infer that on Linux the identity no longer +matches that spelling, so the guard never runs. I have not verified this on Windows. + +This is fire control's or the build owner's to fix. A pin guard that turns itself off on the verification host +is a guard in name only. Hand it to Self as a spawn-worthy defect. + --- # Part A. Mining @@ -122,19 +191,27 @@ tree the suite is 379, of which 17 are mining's. Every one passes (section 0). in the map (`:396-412`, `:669-721`) is re-anchored in A.3 below. 3. **`:24-27` "Fire control is mid-rewrite" is stale.** Cut 12.4 closed 2026-09-29 (`c50029d5`), and 362 tests passed at that close (`docs/fire-control-cut.md` 12.4 status on `b66ba524`). -4. **Cut 3's spec contradicts the Q2 ruling recorded in the same file.** - - Q2's EW deferral (`:176-185`) says a chunk passes the visibility gate unconditionally with PSensor 1, that - targeting is an on-demand field query, and that `ResourceScanner` parks. - - Cut 3 (`:667-668`, `:693`) still specifies `FireControl.ChunkDetectionRange` from the scanner's `Range`, an - `UnaidedChunkRange` fallback, and a test `AScannerExtendsChunkDetection`. - - Cut 7 (`:834-836`) still restores "the Resource Scanner as the chunk sensor" and authors `UnaidedChunkRange`. - - All of that is superseded and is rewritten in A.3. +4. **Chunk detection: both the Codex map and v1 of this map got the ruling wrong.** + - The Codex map's Cut 3 (`:667-668`, `:693`) specifies `FireControl.ChunkDetectionRange` from the scanner's + `Range`, an `UnaidedChunkRange` fallback, and a test `AScannerExtendsChunkDetection`. Its Cut 7 + (`:834-836`) restores "the Resource Scanner as the chunk sensor". + - The Codex Q2 text (`:176-185`) makes a chunk visible unconditionally with PSensor 1, and defers detection to + the EW campaign. v1 of this map followed that text. + - The operator's correction, 2026-09-30: "We can just assign a reflectivity and cross section to each chunk + just like we do with entities, then their visibility depends on how much light is shining on them, no new + mechanism needed ... This is what I actually intended with my ruling about deferring detection." + - So chunk detection is **in this campaign**, through the existing reflectance rule (Cut 3). All three earlier + readings are superseded: + - the scanner as chunk sensor; + - unconditional visibility; + - PSensor fixed at 1. + - `ResourceScanner` stays parked. 5. **Q2's blast consequence (`:221-223`) was written against `Splash`.** Cut 12.4 replaced it with `Detonate(Zone, float2 worldPlanar, float radius, float damage, DamageType)` (`FireControl.cs:1074` @`b66ba524`). - `Detonate` takes no shooter. - - It walks `zone.Entities` only (`:1085`). + - It walks `zone.Entities` only (`:1084`). - So a proximity burst beside a rock does nothing to the rock today, and nothing could route its loot. - - This is new question Q10. + - This was new question Q10, ruled A on 2026-09-30: a blast covers every chunk it overlaps (Cut 4). 6. **`:135`, and the `SimpleCommodityData` comment it copies, are wrong about the retired keys.** Both claim keys 6, 7 and 8 are retired. They are live: `ItemData` declares them for every item, `SpecificHeat` `Key(6)`, `Conductivity` `Key(7)` and `Price` `Key(8)` (`ItemData.cs:291-298` @`b66ba524`), and `SimpleCommodityData` @@ -143,7 +220,7 @@ tree the suite is 379, of which 17 are mining's. Every one passes (section 0). 7. **Q7's recommendation (`:298-310`) predates the Q2 ruling.** Q2 (`:171-174`) says composition and loot tables are **per field kind**, with debris rolling salvage through the same roll. Q7 recommends per-ore `Abundance` on `SimpleCommodityData`, which has one table for every field kind. The two are unreconciled. Q7 is restated - below, behind a new question about what a field kind is. + below. Resolved 2026-09-30: Q11 (kinds are authored records yielding simple commodities) and Q7 A′. 8. **The `Target` blast-radius count (`:193-196`, "about 60 reads in 12 files") has grown.** - At `b66ba524`, `git grep` finds 28 target reads in `ActionGameManager.cs` and 22 in `FireControl.cs`, where the map counted 20 and 11. @@ -185,11 +262,14 @@ The Codex numbering is kept so the map's history still reads, and Soul fix slots |---|---|---| | M0 | Placement: merge master, correct the map | fire-control-12 on master | | M-Soul | Fix batch from the parallel Soul pass over Cuts 1-2 (content unknown here) | Soul's report | -| 3 | A chunk is a target (rewritten for the EW deferral) | M0; Q12; Q13 | -| 4 | A shot at a chunk goes through FireControl (re-anchored to 12.4) | 3; Q10; the fire-control no-target fuse ruling | -| 5 | Composition, loot, the mined lot | 4; Q4, Q5, Q6, Q7, Q11 | -| 6 | The AI mines on the same path | 5; Q9; locomotion Cut 4 | -| 7 | Content | 5; Q1, Q8; variants C4 for any family authored as variants | +| 3 | A chunk is a target, detected by reflected light; the field-kind record appears | M0; **Q14**, **Q16** | +| 4 | A shot at a chunk goes through FireControl (re-anchored to 12.4); blasts cover chunks | 3; the fire-control no-target fuse ruling | +| 5 | Composition, loot, the mined lot, and the same-path test agent | 4 | +| ~~6~~ | **Deleted** (Q9 = C). The test agent moved into Cut 5. | — | +| 7 | Content: field kinds, ores, the Drill Bit, and new Corrosive and Ionizing weapons | 5; variants C4 for families authored as variants | + +Every mining question except Q14 and Q16 is ruled. Neither of those blocks Cut 4 or 5 if Cut 3 lands on the +recommendations. ### M0. Placement and map correction @@ -201,10 +281,19 @@ The Codex numbering is kept so the map's history still reads, and Soul fix slots - Replace Cut 3 and Cut 4 with the versions below. - Strike `UnaidedChunkRange` from Cut 5's and Cut 7's tunables, and "Resource Scanner as the chunk sensor" from Cut 7. + - Replace Q2's "detection is deferred to the electronic-warfare campaign" paragraph (`:176-185`) with the + operator's 2026-09-30 correction, verbatim. + - Record the 2026-09-30 rulings in the map's status header. + - Delete Cut 6 from the map; its history note points here. - Mark the `:396-412` table as history, anchored at `8bd25f6f`. - **Verification:** - Yggdrasil, the same job that section 0 ran: `ygg-verify.sh dotnet ''`. + Both roots are mandatory (shared precondition). + - The job mounts the Yggdrasil mirrors read-only with + `DOCKER_ARGS="-v /home/gamecultadmin/eureka-verify/repos:/repos:ro -v /home/gamecultadmin/eureka-verify/lfs-aetheria:/lfs:ro"`. + - It runs `git config --global --add safe.directory "*"` first. The `GIT_CONFIG_*` safe-directory setting that + `ygg-verify.sh` exports does not reach a local fetch from `/repos`. Rule pinned: fire control and mining compose without a behaviour change on either side. Every pre-existing test passes unchanged. - The verify mirror has no LFS (the push from `F:\Projects\Aetheria` fails in the LFS pre-push hook on an @@ -223,418 +312,630 @@ The Codex numbering is kept so the map's history still reads, and Soul fix slots `93bfdebd`. This map does not predict it. Cut 3 does not start until this slot is closed or empty, because Cut 3 builds on `Zone.Wear`, `ChunkExists` and `ChunkRadius`. -### Cut 3. A chunk is a target (rewritten for the Q2 ruling) +### Cut 3. A chunk is a target, and is detected by reflected light - **Repo/branch:** Aetheria, `codex/mining`, after M-Soul. -- **Deletes first:** - - The scanner-as-sensor plan: no `ChunkDetectionRange`, no `UnaidedChunkRange`, no scanner read in the target - path. - - Optional, and Self's call on what the ruling's "parks" means: the `ResourceScanner` behaviour still runs a - per-tick `Update` that evaluates three stats no code reads (`ResourceScanner.cs:35-58` @`283ce7dc`, - `IAlwaysUpdatedBehavior`). Either keep it as is, or delete the behaviour's `Update` and properties and keep - `ResourceScannerData` authored. No live item carries it (see the census). -- **Adds:** - - `TargetRef`: a readonly struct holding `Entity Entity` and `ChunkId? Chunk`, with value equality and - `IsNone`. It lives beside `ChunkId` (`Zone.cs:527` @`283ce7dc`). - - `Zone.ChunksNear(float2 position, float range, List into)`: the one chunk query. Belts whose - annulus cannot reach are skipped. The rest are evaluated through `ChunkPose` (`Zone.cs:285`), and broken - chunks are skipped through `ChunkExists` (`:231`). - - `Zone.ChunkVelocity(ChunkId)`: analytic velocity plus the parent `Orbit.Velocity`. - - **Visibility, per the Q2 ruling:** a chunk is always visible with info 1. How far the player's cycling and - the AI's query reach is Q13. -- **Per-file changes** (`b66ba524` unless marked): - - `Entity.cs:46`: `Target` becomes `ReactiveProperty`. - - `Entity.cs:201`, `:227`: entity removal clears the slot only when it holds that entity. - - `Entity.cs:229`: `TargetedBy` fires for entities only. - - `Entity.cs:233`: `TargetedByCount` counts entity targets only. - - **`Entity.cs:298-299`:** it clears the target when the target is not in `EntityInfoGathered`. Unchanged, it - clears every chunk target on the next pass, because a chunk is never in that dictionary. The chunk branch - clears only when `!Zone.ChunkExists`, or leaves a broken chunk held as the map's `:139` says. Pick one and - pin it. - - `Entity.cs:317`: `TrySelectTargetItem` is entity-only. - - `Entity.cs:1078`: `TargetRange` reads the chunk position for a chunk. - - `ActionGameManager.cs:365-377` (reticle pick): include the chunk under the reticle, from `ChunksNear` at the - reticle. - - `ActionGameManager.cs:381-388` (`TargetNearest`): stays enemies-only. It drives lock. - - `ActionGameManager.cs:390-404` (next and previous): include chunks within the Q13 reach. - - `ActionGameManager.cs:412-414` (Cycle Target Item): entity-only. - - `ActionGameManager.cs:582`, `:745`, `:769`, `:1046`, `:1224`, `:1239`, `:1266-1267`, `:1355-1373`: `.Entity` - reads, or the chunk position for the indicator. - - `ActionGameManager.cs:1285`: `UpdateFireControlDebug` takes `TargetRef`. - - `Weapon.cs:101` `StanceAllowsFire`: true for a chunk. `Weapon.cs:110` `ArcAllowsFire`: the position through the ref. - - `LockWeapon.cs:86`: a chunk never locks (Q12). - - `TurretController`, `Combat.cs`, `Minion.cs`, `Ship.cs`, `EntityInstance.cs`, `PropertiesPanel.cs`, - `FieldDriver.cs`, `ActionBarSlot.cs`: `.Entity` and null checks. -- **Authority map:** - - Owner: `Entity.Target`, the one slot. - - Inputs: the player's handlers and AI states (Cut 6). - - Outputs: the current `TargetRef`. - - Derived state: `TargetItem`, which is entity-only and nulled on any change as today; `TargetRange`. - - Forbidden writers: any second target field; any chunk pick that bypasses `ChunksNear`; any chunk visibility - or detection stored per tick. - - Shared paths: reticle, cycling, AI, and dock or undock re-activation (`Entity.cs:298-299`). - - Deletion line: no `ReactiveProperty Target` remains. -- **Verification** (Yggdrasil, the M0 command with `--filter FullyQualifiedName~MiningCut3`, then the full suite): - - `ChunksNearSkipsUnreachableBeltsAndBrokenChunks`: pins the one query and the break rule. - - `AChunkTargetSurvivesTheInfoPass`: the negative for `Entity.cs:298-299`. It fails on the naive retype. - - `TargetingAChunkClearsTheAimedItem`: pins the derived `TargetItem` rule. - - `TargetRefEquality`: the same chunk held twice is equal, and a chunk never equals an entity. - - `ChunkTargetIsNeverLocked`: pins Q12's default. - - Every existing fire-control test passes unchanged. That is the negative that ship targeting did not move. - - Negative grep: `git grep -n "ReactiveProperty Target\b"` is empty. - - Stryker over `TargetRef` and `Zone.ChunksNear` (`dotnet tool install -g dotnet-stryker` inside the same job). - - Unity compile: Self. - - Operator: target a rock with the reticle; cycle through rocks and ships; dock and undock with a rock held. -- **Subtraction estimate:** about 0 removed and about 70 added, plus about 90 mechanical site edits (up from the - map's 60, correction 8). +- **Written for:** Q13 = A with the detection correction, Q14 = A, Q16 = B (recommended). -### Cut 4. A shot at a chunk goes through FireControl (re-anchored to 12.4) +#### How detection works today (read at `b66ba524`) + +1. **Emitters write into `Entity.VisibilitySources`** (`Entity.cs:63`, a `Dictionary`). Every + entry is one source's contribution: + - `Reflector.cs:46`: `CrossSection × Zone.GetLight(Position.xz)`. This is the rule the operator named. + - Black-body radiation: `Entity.cs:1269`. + - Weapons: `InstantWeapon.cs:257`, `ConstantWeapon.cs:166`. + - Thrusters: `Thruster.cs:110-111`. + - Sensor pings: `Sensor.cs:117`. + - `Visibility.cs:44` and `Radiator.cs:121`. + + Sources decay each tick (`Entity.cs:1083-1087`, `VisibilityDecay` 0.5). +2. **`Entity.Visibility`** (`Entity.cs:136`) is the sum of the sources. +3. **Each `Sensor` behaviour on an observer** loops over every entity in the zone, every tick + (`Sensor.cs:157-189`). + - It adds a gain into `EntityInfoGathered[target]` (`Entity.cs:64`, a `ReactiveDictionary`). + - Passive gain is `target.Visibility × Sensitivity × SensitivityCurve(angle/π) × dt / dist` (`:179-184`). + - Ping gain is `target.Visibility × Sensitivity × PingBoost × dist`, once per ping (`:170-175`). + - It then decays: `next *= 1 − TargetInfoDecay × dt` (`:186`; `TargetInfoDecay` 0.5 in shipped + `Settings.asset`). +4. **Crossing `TargetDetectionInfoThreshold`** (0.1) adds the target to or removes it from `VisibleEntities`, via + the `ObserveReplace` subscriber at `Entity.cs:237-253`. +5. **Fire control reads both.** `PFire` gates on `VisibleEntities.Contains` (`FireControl.cs:173`) and prices + `PSensor` from the info value (`:179`). The same info also gates the armour and gear reveal thresholds + (0.5 and 0.8) through `IsRevealed`. +6. **Light** is `Zone.GetLight(float2)` (`Zone.cs:369` @`283ce7dc`). It sums `PowerPulse(d / LightRadius, 8)` + over every sun within its `LightRadius`. Beyond every sun's radius, light is 0. + +#### How a chunk plugs in, without becoming an entity + +Two things are needed: +- **A chunk's visibility**, `V(chunk, t)`: `cross-section(chunk) × GetLight(pose(chunk, t))`. This is + `Reflector.cs:46`'s rule, applied to a pose that is already a pure function of time (Cut 1). No stored source, + and no decay: a chunk has only the reflected term, because rocks carry no thrusters, weapons or heat + (black-body emission is out of scope, per the operator's correction). +- **An observer's info on a chunk.** The gain rule is the existing one. Q14 decides how it integrates over time. + Recommended: the closed-form equilibrium of the per-tick rule, evaluated on demand, with no per-tick state. + +**No adapter, and why that is the goal.** The adapter would be a pseudo-entity per chunk, or an `IDetectable` +wrapper, so that `Sensor.Execute`'s loop over `Zone.Entities` sees rocks. That is rejected option C of the Codex +Q2 in a costume: +- It puts every chunk into every sensor's per-tick loop. The map measured 184 ns per observed body per tick, + which is 149 µs per sensor-bearing ship for the 809-chunk probe zone. +- It puts chunks into `EntityInfoGathered`, whose `ObserveReplace` subscriber builds `VisibleEntities`, + `VisibleEnemies` and `VisibleFriendlies`. Every consumer of those collections would then have to learn that + some "entities" are rocks. + +The shared owner has to be **the rule, not the container**. The cut extracts the gain terms at `Sensor.cs:170-184` +into one static function, and both paths call it: +- the entity loop, per tick, unchanged in behaviour; +- the chunk query, on demand. + +Nothing about a chunk is stored per observer. + +#### Deletes first + +- The Codex scanner-as-sensor plan: no `ChunkDetectionRange`, no `UnaidedChunkRange`, and no scanner read + anywhere in the target path. +- The v1 placeholders: unconditional chunk visibility, and PSensor fixed at 1. +- Optional, Self's call: the parked `ResourceScanner` behaviour still runs a per-tick `Update` over three stats no + code reads (`ResourceScanner.cs:35-58` @`283ce7dc`). Delete the `Update` and the properties, and keep + `ResourceScannerData` authored. + +#### Adds + +- **`Sensor.Gain(...)`**, a static function extracted verbatim from `Sensor.cs:170-175` (ping) and `:179-184` + (passive). Inputs: target visibility, sensitivity, the curve at the angle, distance, dt, and whether this is a + ping. `Sensor.Execute` calls it. This is a pure extraction. +- **`FieldKindData`**, a new catalog document type (Q11: authored catalog records). Cut 3 introduces it with only + what detection needs: + - `Name` (`[CultName]`); + - `CrossSection` (float): reflectivity per schematic cell of chunk area; + - `GenerationWeight` (float), for Q16. + + Cut 5 adds the yield entries. Only one kind exists at first, "Asteroid". +- **The belt names its kind.** `AsteroidBeltData` gains a nullable `CultRecordRef Kind`, at the next + free key after `Asteroids` `Key(9)` (`ZoneData.cs:101-105` @`283ce7dc`). + - `ZoneGenerator` writes it at generation, through one function `FieldKinds.Assign(belt key, catalog)`: a + `StableHash` pick weighted by `GenerationWeight`. + - Under Q16 = B, a belt loaded without a kind is assigned by the same function on first load and written back. + One derivation serves both the generation path and the load path. +- **`Zone.ChunkVisibility(ChunkId)`** = `kind.CrossSection × π(ChunkRadius / SchematicCellSize)² × GetLight(ChunkPose.xy)`. + - Area in schematic cells keeps the numbers comparable to hull reflectors. The census has Longinus at 500-150 + over 66 cells and Djinni at 2500-500 over 166, so about 2-15 per cell. + - A worn chunk shrinks and dims. A broken chunk has radius 0, so it is invisible. +- **`Entity.ChunkInfo(ChunkId)`**: the info this observer has on the chunk, from its active `Sensor` behaviours + through `Sensor.Gain`, integrated per Q14. Visible means `ChunkInfo > TargetDetectionInfoThreshold`. +- **`TargetRef`**: a readonly struct holding `Entity Entity` and `ChunkId? Chunk`, with value equality and `IsNone`. + It lives beside `ChunkId` (`Zone.cs:527` @`283ce7dc`). +- **`Zone.ChunksNear(float2 position, float range, List into)`**: the one chunk query. + - Belts whose annulus cannot reach are skipped. + - The rest are evaluated through `ChunkPose` (`Zone.cs:285`). + - Broken chunks are skipped through `ChunkExists` (`:231`). + - It knows nothing about detection. Callers filter by `ChunkInfo`. +- **`Zone.ChunkVelocity(ChunkId)`**: analytic velocity plus the parent `Orbit.Velocity`. +- **`Entity.SetTarget(TargetRef)`**: the one writer. The player's handlers and the Cut 5 test agent both call it + (target invariant 3, "set by one writer path that both the player and the AI call"). Today every handler writes + `Target.Value` directly (`ActionGameManager.cs:377,385,395,403`), so there is no one writer to share. +- **Reach (Q13 = A):** cycling and any programmatic chunk pick use `ChunksNear(position, longest active weapon + Range.Max)`, then filter to chunks the observer can see. + +#### Per-file changes (`b66ba524` unless marked) + +- `Sensor.cs:170-184`: call `Sensor.Gain`. Behaviour must stay identical; the whole existing suite is the check. +- `Entity.cs:46`: `Target` becomes `ReactiveProperty`, written only through `SetTarget`. +- `Entity.cs:201`, `:227`: entity removal clears the slot only when it holds that entity. +- `Entity.cs:229`: `TargetedBy` fires for entities only. +- `Entity.cs:233`: `TargetedByCount` counts entity targets only. +- **`Entity.cs:298-299`:** today it clears a target missing from `EntityInfoGathered`, which would clear every + chunk target on the next pass. For a chunk, it clears when `ChunkInfo ≤ TargetDetectionInfoThreshold`, the same + "lost track" rule expressed through the chunk path. A broken chunk has 0 visibility, so it is lost the same way. +- `Entity.cs:317`: `TrySelectTargetItem` is entity-only. +- `Entity.cs:1078`: `TargetRange` reads the chunk position for a chunk. +- `ActionGameManager.cs:365-377` (reticle pick): pick the chunk under the reticle from `ChunksNear` at the + reticle, if visible. +- `ActionGameManager.cs:381-388` (`TargetNearest`): stays enemies-only. It drives lock. +- `ActionGameManager.cs:390-404` (next and previous): include visible chunks within Q13's reach. +- `ActionGameManager.cs:412-414` (Cycle Target Item): entity-only. +- `ActionGameManager.cs:582`, `:745`, `:769`, `:1046`, `:1224`, `:1239`, `:1266-1267`, `:1355-1373`: `.Entity` + reads, or the chunk position for the indicator. +- `ActionGameManager.cs:1285`: `UpdateFireControlDebug` takes `TargetRef`. +- `Weapon.cs:101` `StanceAllowsFire`: true for a chunk. `Weapon.cs:110` `ArcAllowsFire`: position through the ref. +- `LockWeapon.cs:86`: a chunk never locks (Q12 = A, ruled). +- `TurretController`, `Combat.cs`, `Minion.cs`, `Ship.cs`, `EntityInstance.cs`, `PropertiesPanel.cs`, + `FieldDriver.cs`, `ActionBarSlot.cs`: `.Entity` and null checks. + +#### Authority map + +- **Owners:** + - `Entity.Target` is the one slot, and `SetTarget` is its one writer. + - `Sensor.Gain` is the one detection gain rule. + - `FieldKindData` owns a chunk's reflectivity. + - `FieldKinds.Assign` owns which kind a belt is. +- **Inputs:** player handlers and the test agent; sensor stats; chunk pose and radius; sun light. +- **Outputs:** the current `TargetRef`; `ChunkVisibility`; `ChunkInfo`. +- **Derived state:** + - `ChunkVisibility` and `ChunkInfo` are pure functions of time and are never stored. + - `TargetItem` is entity-only. + - `TargetRange`. +- **Forbidden writers:** + - any second target field; + - any direct `Target.Value =` outside `SetTarget`; + - any chunk in `Zone.Entities` or `EntityInfoGathered`; + - any per-tick chunk detection loop; + - any chunk-specific copy of the gain arithmetic; + - any unconditional visibility for chunks. +- **Shared paths:** reticle, cycling, the test agent, dock and undock re-activation, and belt generation and load + (through `FieldKinds.Assign`). +- **Deletion line:** no `ReactiveProperty Target` remains; no `Target.Value =` outside `SetTarget`; no + gain arithmetic outside `Sensor.Gain`. + +#### Verification + +Yggdrasil, the M0 command with `--filter FullyQualifiedName~MiningCut3`, then the full suite. + +| Test | Rule it pins | +|---|---| +| `SensorGainExtractionChangesNothing` | The existing detection tests, and a recorded `EntityInfoGathered` trace over N ticks for a fixed scene, are byte-equal before and after the extraction. | +| `ChunkInfoMatchesAnEntityHeldStill` | Q14's parity. An entity with a `Reflector` of the same cross-section, at the chunk's position and held still, converges under the per-tick loop to the value `ChunkInfo` returns (tolerance from the discrete fixed point). **One rule, two integrators.** | +| `AChunkInDarknessIsNotVisible` | A chunk outside every sun's `LightRadius` has `ChunkVisibility` 0 and cannot be targeted or fired on. | +| `ABrighterOrBiggerChunkIsSeenFurther` | Monotone in cross-section, radius and light. | +| `AWornChunkDims`, `ABrokenChunkIsInvisible` | Visibility follows `ChunkRadius`. | +| `CyclingReachIsTheLongestActiveWeaponRange` | Q13. A visible chunk beyond it is not offered. | +| `AChunkTargetSurvivesTheInfoPassWhileSeen` | The negative for `Entity.cs:298-299`. It fails on the naive retype. | +| `AChunkTargetIsDroppedWhenItGoesDark` | The lost-track rule, through the chunk path. | +| `TargetRefEquality` | Value equality; a chunk never equals an entity. | +| `ChunkTargetIsNeverLocked` | Q12 = A. | +| `FieldKindAssignmentIsOneFunction` | Generation and first-load assignment give the same kind for the same belt key; the kind is written back once. | +| every existing fire-control and sensor test | Pass unchanged. This is the negative that ship detection and targeting did not move. | + +Also: +- Negative greps: `git grep -n "ReactiveProperty Target\b"` and `git grep -n "Target.Value ="` outside + `Entity.SetTarget` are empty. +- Stryker over `Sensor.Gain`, `Zone.ChunkVisibility`, `Entity.ChunkInfo`, `ChunksNear` and `TargetRef`. +- Census before and after: `AsteroidBeltData` is a run-store type and is absent from the catalog, and + `FieldKindData` shows one record once Cut 7's content lands. Until then, Cut 3 ships a test fixture kind and a + one-record catalog write through `AetherDb`, flagged for operator review. +- Unity compile: Self. +- Operator: + - target a lit rock with the reticle, and cycle through rocks and ships; + - fly into a belt far from the sun and confirm its rocks cannot be picked; + - dock and undock with a rock held. + +#### Subtraction estimate + +About 0 removed (optionally about 15 for the scanner `Update`). About 140 added: +- `Sensor.Gain` extraction nets about 0; +- `FieldKindData` plus assignment, about 40; +- `ChunkVisibility` and `ChunkInfo`, about 30; +- `TargetRef`, `ChunksNear`, `SetTarget`, about 70. + +Plus about 95 mechanical site edits. One new catalog document type (`FieldKindData`), and one nullable +run-store slot (`AsteroidBeltData.Kind`). + +### Cut 4. A shot at a chunk goes through FireControl, and blasts cover chunks (re-anchored to 12.4) - **Repo/branch:** `codex/mining`, after Cut 3. +- **Written for:** Q10 = A, Q12 = A (both ruled). - **Lands after:** the fire-control no-target fuse ruling (`docs/fire-control-cut.md`, 12.4 status, "a fused weapon fired with no target bursts at the shooter's own position", `FireControl.cs:389`). That ruling's fix edits the same `target != null` branches this cut retypes (`:367`, `:372-378`, `:389`). If the ruling is still open, this cut preserves today's behaviour for the no-target case and says so in its commit. -- **Deletes first:** none. `PendingShot.Target` (`:1468`) and `ShotOutcome.Target` (`:1598`) become `TargetRef`. -- **Adds:** - - `Silhouette.Disc(float radiusCells, float precision)`. - - The shared integrator is extracted from `Silhouette` `:1302-1308` (the `Phi` sum over merged intervals, and - `span`). The hull path and the disc path then call one function. No second `Phi` summation exists. -- **Per-file changes** (`FireControl.cs` @`b66ba524`): - - `PFire` `:161-181`: for a chunk, no `VisibleEntities` gate (`:173`) and info 1 (`:179`). A `LockWeapon` is - gated out (`:175`, Q12). The arc stays (`:176`). - - `Forecast` `:206`, `HitProbability` `:228`, `Inspect` `:243-283`: the disc silhouette. `Inspect`'s - `Visible` is true for a chunk. - - `PredictedIntercept` `:133`, `TravelDirection` `:307`: position and velocity through the ref. `Bearing` - `:316` is not called for a chunk, because a disc has no facing. - - `DeviationProbability` `:336-341`: the live chunk position is `Zone.ChunkPose` at `now`. - - `Fire` `:355-418`: freezes the ref. - - `Step` `:439`: `targetGone` includes `!ChunkExists`. - - `CommitProbability` `:506-520`: for a chunk, the disc (no `GetData(Hull)` at `:516`). - - `Commit` `:550-600`: for a chunk hit, no `Lane` (`:563-578`) and no shield block (`:585-592`). `Cell`, - `Bearing` and `Lateral` stay zero in `MakeOutcome` (`:1028`). - - `Apply` `:651-678`: - - Null fuse on a chunk: `Zone.Wear(chunk, Damage)` (`Zone.cs:260` @`283ce7dc`). - - Contact or delayed fuse on a chunk: no `ApplyBlastHit` (it needs a hull, `:747-760`). The fuse point is - the chunk centre at arrival. The chunk's pose is a pure function of time, so the point is exact. - - Proximity fuse: `Detonate` as today. What it does to chunks is Q10. - - `Detonate` `:1074-1150`: unchanged unless Q10 says otherwise. - - Unity presentation: `EntityInstance.cs` (4 target reads) and the four weapon managers aim at the chunk's - rendered position. Presentation only. -- **Authority map:** - - Owner: `FireControl`, unchanged. - - New inputs: chunk pose, velocity and radius through `Zone`. - - Forbidden writers: any chunk hit test outside `Commit`; any chunk pose read in `Apply` except the fuse point - of a blast; any second `Phi` integrator. - - Shared paths: player trigger, AI `Activate` (`Combat.cs:121`), turrets (which never hold a chunk). - - Deletion line: none. -- **Verification** (Yggdrasil, `--filter FullyQualifiedName~MiningCut4`, then the full suite): - - `AChunkShotUsesTheSameFactors`: `Accuracy`, `PSpread` and `Sigma` equal a ship shot's at equal range and - precision. Pins one fire path. - - `DiscMassIsTheClosedForm`: `POnHull = Φ(R/σ) − Φ(−R/σ)`. Pins the shared integrator. - - `HullSilhouetteUnchangedByTheExtraction`: every existing `Silhouette` test and the `ProbeC` equivalence - still pass. This is the negative for the extraction. - - `AChunkThatBreaksMidFlightIsAMiss`: pins `Step`'s gone rule. - - `ChunkHitsWearTheChunk`, and `AHitOnABrokenChunkChangesNothing`, which re-pins `34994732` through the real - path. - - `LaunchersCannotFireOnAChunk`: pins Q12. - - `ChunkDiceAreTheShotsOwn`: the same galaxy and shot id give the same outcome. - - Stryker over the new branches. - - Operator: shoot a rock with each damage type. It shrinks and breaks, and misses at range look plausible. -- **Subtraction estimate:** about 0 removed and about 80 added. The integrator extraction nets about −5. -### Cut 5. Composition, the loot roll, and the mined lot +**Deletes first:** none. `PendingShot.Target` (`:1468`) and `ShotOutcome.Target` (`:1598`) become `TargetRef`. -- **Repo/branch:** `codex/mining`, after Cut 4. Written for the recommendations in Q4-Q7 and Q11. Rewrite the - cut if a ruling differs. -- **Deletes first:** `BodyData.Resources` (`ZoneData.cs:78-79` @`283ce7dc`, key 4) becomes a retired-key comment. - Its only reader, `MineAsteroid`, died in Cut 2. -- **Adds, under Q7 = A′ (see Q7):** - - A catalog document type per field kind, holding a list of yield entries: commodity reference, abundance, - and affinity per damage type. - - A nullable kind reference on the field, written by `ZoneGenerator` for new fields. Existing runs resolve a - null kind as Q11 rules. - - `Zone.ChunkComposition(ChunkId, …)`, derived from the kind's entries and a `StableHash` jitter. - - The loot roll inside `Commit`, after the hit draw, from the shot's own generator. Frozen into - `ShotOutcome.Loot`. - - `ItemManager.ExtractedLot(zone, field, commodity)`: find-or-mint, with a source-to-lot index derived at - construction. - - `Extracted` (`Provenance.cs:116` @`b66ba524`) gains `Body` at key 2. - - `Lot` moves from `CraftedItemInstance` (`ItemInstance.cs:38`) to `ItemInstance`, keeping key 11. Coordinate - with variants C4 (Dependencies). -- **Per-file changes** (`b66ba524`): - - `FireControl.Apply`: after `Zone.Wear`, mint or find the lot and `TryStore` it (`Entity.cs:1805`). - - `Entity.cs:1856` (the stack merge matches `Data.Key` only): it must also match `Lot`. - - `Entity.cs:1934` (the split constructs a `SimpleCommodity` without `Lot`): the split copies `Lot`. - - `SavedGame.cs:113` (`.OfType()` as the ledger roots): every `ItemInstance` with - `Lot != 0`. - - `ItemManager.cs:72` (`GetLot(CraftedItemInstance)`): takes `ItemInstance`. -- **Authority map:** - - Owners: - - `FireControl.Commit` decides the loot. - - `ItemManager` mints lots. - - `EquippedCargoBay` places units. - - The field-kind record owns what a field yields. - - Inputs: frozen damage, damage type and penetration; the composition at commit; remaining hit points at commit. - - Outputs: `ShotOutcome.Loot`, units in cargo, ledger lots. - - Derived state: composition (never stored); the source-to-lot index. - - Forbidden writers: - - any loot draw outside `Commit`, except the blast branch Q10 names; - - any mined `SimpleCommodity` with `Lot == 0`; - - any shared random stream; - - per-ore tables on `SimpleCommodityData` (the Q7 alternative that competes with the kind record). - - Deletion line: `BodyData.Resources` is retired before the composition function is added. -- **Verification:** the map's list (`:780-792` @`283ce7dc`) stands, with the fixture rebuilt on the kind record, - plus: - - `ADebrisKindAndAnAsteroidKindRollDifferentTables`: pins "per field kind" (Q2). - - `AMinedStackSurvivesSaveGc`: the negative for `SavedGame.cs:113`. It fails on the old roots. - - `StacksMergeOnlyWithinALot`: the negative for `Entity.cs:1856`. - - Census before and after: the new slots read as `defaulted_missing_slot` until Cut 7. - - Operator: mine one rock with a Thermal gun and one with a Kinetic gun, and read the cargo and its brand - rows. -- **Subtraction estimate:** 2 removed, about 170 added (the kind record costs about 20 more than per-ore fields). +**Adds:** +- **`Silhouette.Disc(float radiusCells, float precision)`.** The shared integrator is extracted from `Silhouette` + `:1302-1308` (the `Phi` sum over merged intervals, and `span`). The hull path and the disc path call one + function. No second `Phi` summation exists. +- **A chunk pass inside `Detonate`** (Q10 = A). After the entity pass (`:1084-1149`): + - `ChunksNear(worldPlanar, radius + max chunk radius)`. + - Each covered chunk takes `damage × CircleCircleOverlap(blast, chunk) / (π r_blast²)`, the same normaliser the + cell shares use (`:1092`). + - `CircleCircleOverlap` is the closed-form lens area, about 12 lines, beside `CircleSquareOverlap` (`:1153`). + - Each share goes to `Zone.Wear`. + - **One area rule:** a blast's disc is divided over whatever it covers, whether cells or chunks. Conservation + holds across both. +- **`Detonate` gains an optional loot sink**, the detonating shot, so the shooter receives the ore (Q10 = A). + - A sourceless blast passes null. None exists today, but a mine is one (F12-6). A null-sink blast wears chunks + and yields nothing. + - This is the only new parameter. `Detonate` still reads no host (12.4's rule). -### Cut 6. The AI mines on the same path +**Per-file changes** (`FireControl.cs` @`b66ba524`): +- `PFire` `:161-181`: + - For a chunk, the visibility gate (`:173`) is `ChunkInfo > TargetDetectionInfoThreshold`, and `info` (`:179`) + is `ChunkInfo` (Cut 3). **This replaces v1's "no gate, info 1".** + - `PSensor` prices a chunk exactly as it prices a ship at the same info. + - A `LockWeapon` is gated out (`:175`, Q12 = A). The arc stays (`:176`). +- `Forecast` `:206`, `HitProbability` `:228`, `Inspect` `:243-283`: the disc silhouette. `Inspect`'s `Visible` and + `Info` come from the chunk path. +- `PredictedIntercept` `:133`, `TravelDirection` `:307`: position and velocity through the ref. `Bearing` `:316` + is not called for a chunk, because a disc has no facing. +- `DeviationProbability` `:336-341`: the live chunk position is `Zone.ChunkPose` at `now`. +- `Fire` `:355-418`: freezes the ref. +- `Step` `:439`: `targetGone` includes `!ChunkExists`. +- `CommitProbability` `:506-520`: for a chunk, the disc (no `GetData(Hull)` at `:516`). +- `Commit` `:550-600`: for a chunk hit, no `Lane` (`:563-578`) and no shield block (`:585-592`). `Cell`, `Bearing` + and `Lateral` stay zero in `MakeOutcome` (`:1028`). +- `Apply` `:651-678`: + - Null fuse on a chunk: `Zone.Wear(chunk, Damage)` (`Zone.cs:260` @`283ce7dc`). + - Contact or delayed fuse on a chunk: no `ApplyBlastHit`, which needs a hull (`:747-760`). `Detonate` at the + chunk's centre at arrival. The pose is a pure function of time, so the point is exact. + - Proximity: `Detonate` as today, now with the chunk pass. + - Contact or delayed on a ship: `ApplyBlastHit` as today, and its `Detonate` now also covers nearby chunks. +- Unity presentation: `EntityInstance.cs` (4 target reads) and the four weapon managers aim at the chunk's + rendered position. Presentation only. -The map's Cut 6 (`:800-822` @`283ce7dc`) stands, re-anchored: -- the agent factory is `Zone.CreateAgent` at `Zone.cs:126-133` (`283ce7dc`), which assigns `PatrolOrbitsTask` - at `:129`; -- the `Minion` transitions are at `Minion.cs:10-21` (`b66ba524`); -- the `Activate` call to copy is `Combat.cs:121`. +**Authority map:** +- **Owner:** `FireControl`, unchanged. `Detonate` stays the one area owner. +- **New inputs:** chunk pose, velocity, radius and info, through `Zone` and `Entity.ChunkInfo`. +- **When blast damage to chunks is decided:** at detonation. Each covered chunk is judged where it is at arrival, + which is 12.4's own bystander rule (`ApplyBlastHit`'s comment at `:773-777`: "every bystander is judged against + where it actually is at arrival"). A direct hit on a chunk is still decided at `Commit`. +- **Forbidden writers:** + - any chunk hit test outside `Commit`, except `Detonate`'s area pass; + - any second area-share formula; + - any second `Phi` integrator; + - `Detonate` reading a host. +- **Shared paths:** player trigger, the Cut 5 test agent, AI `Activate` (`Combat.cs:121`), and turrets (which + never hold a chunk). Any blast from any source goes through `Detonate`. +- **Deletion line:** none. -It waits on locomotion Cut 4 (not landed; locomotion Cut 1 is the only locomotion cut on fire-control-12, per -`docs/merge-to-master-checklist.md:93`). It inherits fire control's F12-5 ("`Combat.cs:117` gates a proximity -weapon on a direct-hit forecast"): an AI miner with a proximity weapon is gated by the wrong forecast until F12-5 -lands. Name it in the cut and do not fix it there. +**Verification** (Yggdrasil, `--filter FullyQualifiedName~MiningCut4`, then the full suite): -### Cut 7. Content +| Test | Rule it pins | +|---|---| +| `AChunkShotUsesTheSameFactors` | `Accuracy`, `PSpread`, `Sigma` and `PSensor` equal a ship shot's at equal range, precision and info. One fire path. | +| `AnUnseenChunkCannotBeFiredOn` | The gate is the detection rule, not a constant. | +| `DiscMassIsTheClosedForm` | `POnHull = Φ(R/σ) − Φ(−R/σ)`: the shared integrator. | +| `HullSilhouetteUnchangedByTheExtraction` | Every existing `Silhouette` test and the `ProbeC` equivalence still pass. The negative for the extraction. | +| `AChunkThatBreaksMidFlightIsAMiss` | `Step`'s gone rule. | +| `ChunkHitsWearTheChunk`, `AHitOnABrokenChunkChangesNothing` | Wear; re-pins `34994732` through the real path. | +| `ABlastConservesAcrossCellsAndChunks` | The shares delivered to cells plus chunks, plus the uncovered area, equal the damage. Same lattice style as 12.4's 1,944-disc conservation probe. | +| `ABlastBesideAnUntargetedRockWearsIt` | Q10 = A. | +| `CircleCircleOverlapIsTheLens` | Closed form, with the tangent, contained and disjoint cases (12.4's F1 tangent lesson). | +| `LaunchersCannotFireOnAChunk` | Q12 = A. | +| `ChunkDiceAreTheShotsOwn` | The same galaxy and shot id give the same outcome. | -The map's Cut 7 (`:826-839`) stands, with three changes: -- It drops the Resource Scanner as the chunk sensor (correction 4). -- It authors the field-kind records (Q7 = A′) instead of per-ore fields. -- It runs on the adopted variants release, if any family is authored as variants (Dependencies). +Also: Stryker over the new branches. Operator: shoot a lit rock with each damage type; it shrinks and breaks, +and misses at range look plausible. -Its `AetherDb mining-content` command must honour variants Q1a: no plain `Upsert` at a variant key. +**Subtraction estimate:** about 5 removed (the integrator extraction) and about 115 added (the chunk pass and the +lens area add about 35 over v1). + +### Cut 5. Composition, the loot roll, the mined lot, and the same-path test agent + +- **Repo/branch:** `codex/mining`, after Cut 4. +- **Written for:** Q4 A, Q5 A, Q6a A, Q6b A, Q7 A′, Q9 C, Q10 A and Q11, all ruled on 2026-09-30. + +**Deletes first:** +- `BodyData.Resources` (`ZoneData.cs:78-79` @`283ce7dc`, key 4) becomes a retired-key comment. Its only reader, + `MineAsteroid`, died in Cut 2. +- `Agents/Tasks/Mining.cs` and its `.meta` (@`283ce7dc`: 19 lines, no reader, not persisted per `AgentTask.cs:11`, + and not registered in any union). AI mining is deferred (Q9 = C), and the campaign that picks it up writes its own + task. + - Leave the `TaskType.Mine` enum member (`AgentTask.cs:27`). Removing it would renumber the members after it for + no gain. + +**Adds:** +- **`FieldKindData` gains `Entries`**, a `List`. Each `FieldYield` is: + - `Commodity`: a `CultRecordRef` (simple commodities only, Q11); + - `Abundance` (float); + - `Affinity`: an object list of `(DamageType, float)` pairs, absent meaning 1. + + **Every list here is an object list, not a dictionary.** Variants address object-list elements by id and replace + dictionaries whole (variants Q7). So a "rich belt" variant can override one entry's abundance. +- **`Zone.ChunkComposition(ChunkId, Span<…>)`**: derived. `Abundance_o × exp(ChunkCompositionVariance × g(StableHash(field key, index, o)))`, + normalized, from the belt's kind (Cut 3). Never stored. +- **The loot roll (Q4 A, Q5 A)**, for one hit with damage *D* on a chunk of size *s*: + - units = `min(D, remaining HP) / HP(s) × Yield(s)`, the floor plus one Bernoulli draw on the remainder; + - the weight of ore *o* = `Affinity_o(d) × Comp(o)^(1/Depth)`, with `Depth = 1 + P × MiningDepthPerPenetration`; + - one draw per unit. +- **Where each roll happens:** + - **Direct hit:** inside `Commit`, after the hit draw, from the shot's own generator (`FireControl.cs:540` + @`b66ba524`). Frozen into `ShotOutcome.Loot` (commodity and count pairs). + - **Blast (Q10 = A):** inside `Detonate`'s chunk pass, per covered chunk, with that chunk's share as *D*. The + draws come from a generator seeded by the same `(zone, shot)` pair as the shot's commit dice, plus a fixed + blast salt, walked in `ChunksNear` order (belt key, then index). That keeps the dice the shot's own, and + deterministic. + - **This amends target invariant 5.** The old wording was "the loot is decided at Commit". The new wording: + "loot is decided by the owner that decides the damage, from the shot's own dice". That is `Commit` for a direct + hit, and detonation for a blast, following 12.4's bystander rule. I state it here, not as a new fork: it follows + from Q10 = A plus 12.4's ruled model. +- **`ItemManager.ExtractedLot(zone, field, commodity)`** (Q6b A): find-or-mint, with a source-to-lot index derived + at construction and never persisted. +- **`Extracted`** (`Provenance.cs:116` @`b66ba524`) gains `Body` at key 2 (`CultRecordRef`, nil on older + records). +- **`Lot` moves** from `CraftedItemInstance` (`ItemInstance.cs:38`) to `ItemInstance`, keeping key 11. Coordinate + with variants C4 (A.6). +- **The deposit (Q6a A):** units go straight into the shooter's cargo at arrival (a direct hit) or at detonation (a + blast), through `TryStore` (`Entity.cs:1805`). A full hold loses the overflow. +- **Tunables:** `MiningDepthPerPenetration` on `GameplaySettings`; `AsteroidYield` (`ExponentialLerp`) and + `ChunkCompositionVariance` on `PlanetSettings`. None may be CultMath-typed while settings are Unity-serialized. +- **The same-path test agent (Q9 = C, replacing Cut 6).** A headless test drives a ship through exactly the + player's calls: + 1. `Entity.SetTarget(TargetRef(chunk))` (Cut 3); + 2. `weapon.Activate()` (the call `Combat.cs:121` and the player's trigger make); + 3. `Zone.Update` ticks. + + There is no `Agent`, no `AgentTask` and no `Minion` state. It proves the path an AI will later drive, without + building the AI. + +**Per-file changes** (`b66ba524`): +- `FireControl.Commit`, `ApplyDirectHit` and `Detonate`: the roll and the deposit, as above. +- `Entity.cs:1856`: the stack merge matches `Data.Key` only today; it must also match `Lot`. +- `Entity.cs:1934`: the split constructs a `SimpleCommodity` without `Lot`; it must copy `Lot`. +- `SavedGame.cs:113`: the ledger roots are `.OfType()` today; they become every + `ItemInstance` with `Lot != 0`. +- `ItemManager.cs:72`: `GetLot(CraftedItemInstance)` takes `ItemInstance`. + +**Authority map:** +- **Owners:** + - `Commit` decides direct-hit loot, and `Detonate` decides blast loot, each from the shot's dice. + - `ItemManager` mints lots. + - `EquippedCargoBay` places units. + - `FieldKindData` owns what a field yields. + - `FieldKinds.Assign` owns which kind a belt is. +- **Inputs:** frozen damage, damage type and penetration; the chunk's composition and remaining hit points at the + deciding moment. +- **Outputs:** `ShotOutcome.Loot`, units in cargo, ledger lots. +- **Derived state:** composition (never stored); the source-to-lot index. +- **Forbidden writers:** + - any loot draw outside `Commit` and `Detonate`'s chunk pass; + - any mined `SimpleCommodity` with `Lot == 0`; + - any shared random stream; + - per-ore tables on `SimpleCommodityData`; + - any extraction call that bypasses `FireControl`. +- **Shared paths:** the player's trigger and the test agent (identical calls); save GC; Continue, where the index is + rebuilt. +- **Deletion line:** `BodyData.Resources` is retired, and `Mining.cs` deleted, before the roll is added. + +**Verification** (Yggdrasil, `--filter FullyQualifiedName~MiningCut5`, then the full suite): + +| Test | Rule it pins | +|---|---| +| `RightDamageTypeRaisesTheOresShare` | The Q4 table, as a fixture on a kind record. | +| `PenetrationFlattensTowardRareOre`, `ZeroPenetrationReadsTheRawComposition` | Q4's depth term. | +| `AChunkYieldsItsAuthoredTotalWhateverBreaksIt` | Invariant 7, summed over direct hits and blasts from different weapons. | +| `DirectHitLootIsDecidedAtCommit` | Loot frozen in `ShotOutcome`, performed at arrival. | +| `BlastLootIsDecidedAtDetonationFromTheShotsDice` | The amended invariant 5. The same galaxy and shot give the same ore. | +| `ABlastPaysTheShooter` | Q10 = A. | +| `ASourcelessBlastWearsButYieldsNothing` | The null loot sink. | +| `MinedUnitsCarryAnExtractedLotNamingZoneAndField` | Provenance, including `Extracted.Body`. | +| `UnitsFromOneSourceStack`, `UnitsFromTwoSourcesDoNot` | Q6b A, and the negative for `Entity.cs:1856`. | +| `ASplitStackKeepsItsLot` | The negative for `Entity.cs:1934`. | +| `AMinedStackSurvivesSaveGc` | The negative for `SavedGame.cs:113`. It fails on the old roots. | +| `AFullHoldLosesTheOverflow` | Q6a A. | +| `CompositionIsStableAcrossContinue` | Derived, never stored. | +| `TwoKindsRollDifferentTables` | Per field kind (Q2, Q11). | +| `ATestAgentMinesThroughThePlayersPath` | Q9 = C. After N seconds: ore in the hold, every unit's lot `Extracted` from that belt, and every unit traceable to a `ShotOutcome` or a detonation. The negative: grep for any call to `Zone.Wear` or `ItemManager.ExtractedLot` outside `FireControl.cs`. | + +Also: +- Stryker over the formula, the blast pass and the mint. +- Census: the new `FieldKindData` slots read as `defaulted_missing_slot` until Cut 7. +- Operator: mine one lit rock with a Thermal gun and one with a Kinetic gun, and read the cargo and its brand rows. + +**Subtraction estimate:** about 21 removed (`Resources` 2, `Mining.cs` 19). About 190 added: the roll, the blast +loot, the mint, the kind entries and the test agent. The test agent replaces Cut 6's ~90 lines. + +### Cut 6. Deleted (Q9 = C) + +AI mining is deferred. Its same-path proof is the test agent in Cut 5. The Codex Cut 6 (`:800-822` @`283ce7dc`), +its locomotion Cut 4 dependency and its inheritance of F12-5 all leave this campaign. + +The campaign that builds AI mining starts from: +- `Zone.CreateAgent` (`Zone.cs:126-133` @`283ce7dc`); +- the `Minion` transitions (`Minion.cs:10-21` @`b66ba524`); +- the one writer `Entity.SetTarget`; +- `Combat.cs:121`'s `Activate`. + +### Cut 7. Content (operator review sheet, one catalog commit) + +This is a content cut, applied through an `AetherDb mining-content [apply]` command. It is a dry run by default, +and the precedent is `targeting-catalog`. The operator reviews a sheet before `apply`. The command honours variants +Q1a: no plain `Upsert` at a variant key. + +**1. Ores.** The 14 legacy ores missing from live, restored with their legacy mass, specific heat, conductivity and +category (values are in `legacy-0414`, per the Codex map's `:830-832`): +- Minerals: Crystal, Silicon, Carbon, Nitrogen; +- Metals: Iron, Titanium, Copper, Lead; +- Compounds: Ethanol, Hydrocarbons, Acid, Oxygen, Carbon Dioxide; +- Organics: Plant Matter. + +Simple commodities only (Q11). + +**2. Field kinds.** One `FieldKindData` "Asteroid" record: +- `CrossSection` per cell: recommend about 5. That is dull rock, below Longinus's 7.6 and Djinni's 15 per cell at + their maximum reflector values (section 0). +- `GenerationWeight` 1. +- `Entries` over the live and restored ores, each with an `Abundance` and `Affinity` over **all six damage types**, + now that Q8 = B makes every type reachable. + +Further kinds ("rich", "ice", debris) are the operator's to add. Each is a variant of "Asteroid" overriding entries +by id, once variants C4 lands. + +**3. Drill Bit (Q1 A).** +- A `ConstantWeaponData` beam at Kinetic, with high penetration and range 5-20. +- DPS, efficiency and penetration are from legacy `MiningToolData` (DPS 1-2.5, efficiency 2-5, penetration 1-5, + range 5-20). +- No live weapon uses `ConstantWeaponData` (census), so the Drill Bit is also the first live constant weapon. The + operator's check covers the beam's presentation. + +**4. Corrosive and Ionizing weapons (Q8 = B, against v1's recommendation).** + +*The balance fact this rests on (read at `b66ba524`):* **damage type does nothing in combat today.** +- `WeaponData.DamageType` (`Weapon.cs:22-23`) is frozen into the shot (`FireControl.cs:1477`) and passed to + `Shield.CanTakeHit` and `TakeHit` (`Shield.cs:166`, `:186`). Both ignore their `type` argument. +- Armour absorption (`ArmorAbsorb`, `ItemAbsorb`) and `Detonate` take no type. +- The only other reader is `AbsorbEvent`, which is presentation (`CapabilityEvents.cs:28`). +- Mining's affinity (Cut 5) becomes **the first simulation reader of damage type.** +- Therefore a new-type weapon's combat power is fixed entirely by its non-type stats. Its identity shows only in + mining and presentation. + +*Anchors (census, merged tree):* +- Kinetic 9, Electric 3, Optical 3, Thermal 3. +- Sustained single-target DPS (`Damage × Count / Cooldown`, min to max quality) for the auto class: + - Autocannon 6; + - ClearPath 20-180; + - Spectra 40-340; + - ColdFire 16-160; + - CShot RainbowLite 33-600. +- Burst weapons (DeathCluster, FastBlast+-) are magazine-limited and are not comparable by DPS. + +*Records proposed.* One per type. Each is a minimal override of an existing weapon, so its combat envelope equals +its base's by construction, and balance risk is zero on day one. + +| New record | Type | Base (class, hardpoint) | Overrides | Why this base | +|---|---|---|---|---| +| (name: operator's) | Corrosive | ClearPath (`AutoWeaponData`, Ballistic) | `Name`, `DamageType` | Short-range stream: a sprayer. Range 250-600, DPS 20-180, cheap (price 50,000, mass 50). | +| (name: operator's) | Ionizing | Spectra (`AutoWeaponData`, Energy) | `Name`, `DamageType` | Beam-like optical: a particle beam. Range 800-1750, DPS 40-340. | + +Optional second step, flagged for review and not default: give the Ionizing record `Penetration` 0.25 (Autocannon's +value) with damage × 0.8 to pay for it. That makes it the rare-ore tool through Q4's depth term. It changes combat, +because penetration drives 12.3's lanes, so it needs the operator's eye and F12-2's retune. + +*Presentation:* the effect prefab and icon stay the base's until art exists. The Corrosive and Ionizing presentation +look is the operator's call, and so are the names. + +*Variants:* both records are ideal variants: a base plus two overrides, one of them nested in +`Behaviors[weapon].DamageType`. A nested override needs element ids (C2), and Aetheria adopting them needs C4. +- If Cut 7 runs before C4: author them as plain records, and list them in C4's family re-author pass. +- If it runs after: author them as variants. + +Either way, nobody copies a record by hand twice. + +**5. Tunables:** values for `AsteroidYield`, `ChunkCompositionVariance` and `MiningDepthPerPenetration`. + +**Verification:** +- `census` and `dangling` before and after. +- Cut 5's loot fixture runs against the live catalog. It asserts that every ore in every kind is reachable, meaning + some live weapon's damage type has an affinity above 1 for it. +- `EveryDamageTypeHasALiveWeapon` pins Q8 = B. +- Operator: review the sheet; mine with the new Corrosive and Ionizing weapons; feel the Drill Bit. ## A.4 Subtraction ledger (estimate, whole campaign from here) | Cut | Removed | Added | Formats and targets | |---|---|---|---| | 1-2 (landed) | 250 | 181 src, 681 test | union 26 retired; `ZonePack` key 6 | -| 3 | ~0 (optionally ~15, the scanner `Update`) | ~70, plus ~90 mechanical site edits | `Target` retyped | -| 4 | ~5 (integrator extraction) | ~80 | `PendingShot` and `ShotOutcome` target retyped | -| 5 | 2 | ~170 | a field-kind document type; `Extracted` key 2; `Lot` moves to `ItemInstance` key 11; `BodyData` key 4 retired | -| 6 | ~15 | ~90 | none | -| 7 | 0 | content | catalog records | +| 3 | ~0 (optionally ~15, the scanner `Update`) | ~140, plus ~95 mechanical site edits | `Target` retyped; `FieldKindData` document type; `AsteroidBeltData.Kind` (nullable) | +| 4 | ~5 (integrator extraction) | ~115 | `PendingShot` and `ShotOutcome` target retyped; `Detonate` gains a chunk pass and a loot sink | +| 5 | ~21 (`Resources`, `Mining.cs`) | ~190 | `FieldKindData.Entries`; `Extracted` key 2; `Lot` moves to `ItemInstance` key 11; `BodyData` key 4 retired | +| 6 | deleted | 0 | none | +| 7 | 0 | content | 14 ores, 1 field kind, the Drill Bit, 2 weapons | No new package, assembly or executable target. The net is additive from Cut 3 on. Each addition buys a named -capability: targetable chunks, the loot roll, provenance, and AI mining. +capability: detectable, targetable chunks, the loot roll, blast mining, and provenance. Deleting Cut 6 removed +about 90 lines of AI code from the plan. -## A.5 Operator questions (mining), one fork each +## A.5 Operator questions (mining) -Q1-Q3 are ruled. Q4-Q9 are restated with context. Q10-Q13 are new, raised by 12.4 and by the Q2 ruling. +### Ruled, 2026-09-30 (recorded, not reopened) -**Q4. The loot weight formula.** (Blocks Cut 5.) +| Q | Ruling | Where it lands | +|---|---|---| +| Q4 | A, the formula as written | Cut 5 | +| Q5 | A, per hit | Cut 5 | +| Q6a | A, straight into cargo; overflow lost | Cut 5 | +| Q6b | A, one lot per (zone, field, commodity) source | Cut 5 | +| Q7 | A′, a per-kind entry list (commodity, abundance, affinity), derived, never stored per chunk | Cuts 3 and 5 | +| Q8 | **B**, author Corrosive and Ionizing weapons (against v1's recommendation) | Cut 7 §4 | +| Q9 | **C**, AI mining deferred; the path is proven by a test agent (against v1's recommendation) | Cut 5; Cut 6 deleted | +| Q10 | A, a blast covers every chunk it overlaps; the shooter gets the loot | Cuts 4 and 5 | +| Q11 | field kinds are authored catalog records; tables yield simple commodities only | Cuts 3, 5 and 7 | +| Q12 | A, launchers cannot mine | Cuts 3 and 4 | +| Q13 | A, reach is the longest active weapon range. Detection correction: chunks are detected by the existing reflectance rule, not deferred to EW | Cut 3 | -*Context.* You asked for loot weighted by "some arcane combination of the damage type, the chunk's composition, -and the stats of the weapon you hit it with". The map's proposal for one hit by weapon *w* (damage type *d*, -damage *D*, penetration *P*) on chunk *c*: -- **Units** = `min(D, remaining HP) / HP(size) × Yield(size)`: the floor, plus one Bernoulli draw on the remainder. - A chunk yields the same total however it is broken; damage only gets you there faster. -- **Ore weight** = `Affinity_ore(d) × Comp_c(ore)^(1/Depth)`, with `Depth = 1 + P × MiningDepthPerPenetration`. - - Affinity is "the right tool for the ore". - - Penetration flattens the composition toward rare ores, but never removes the common one. This is the 2020 - `pow(x, 1/penetration)` rule, rewritten so penetration 0 means the raw composition. -- Worked table (`:257-264` @`283ce7dc`): the right damage type roughly triples an ore's share, and penetration - multiplies a rare ore's share further. -- Penetration is being retuned (fire-control F12-2). `MiningDepthPerPenetration` is the one knob that absorbs it. +Earlier rulings: Q1 A, Q2 A (one slot, two kinds; field-kind generalization), Q3 A. + +### Open, two forks raised by the Q13 correction + +**Q14. How does a ship's knowledge of a rock build up over time?** (Blocks Cut 3's `ChunkInfo`.) + +*Context.* Your correction puts chunks on the same detection rule as ships: +- visibility = cross-section × light (`Reflector.cs:46`); +- each sensor adds info in proportion to visibility, sensitivity and angle over distance; +- info decays. + +For ships this is a per-tick integrator. Every sensor visits every entity every tick and keeps a running value +per pair (`Sensor.cs:157-189`). Knowledge rises over a few seconds, then holds or fades. That per-tick loop is +exactly what cannot run over rocks: the probe zone has 809, and the loop costs about 184 ns per body per tick per +sensor, which is the kind of per-rock cost you asked to slim down. + +The gain arithmetic itself is shared in every option below; Cut 3 extracts it into one function. The question is +only how it is integrated over time for a rock. *Options.* -- **A:** the formula as written. -- **B:** A, plus `DamageSpread` as a yield multiplier ("breadth"). It changes no weights. -- **C:** A without the penetration term. +- **A. Settled value, on demand.** A rock's info is the value the per-tick rule would settle at, for this observer, + now. It is computed when something asks (targeting, the fire gate), stored nowhere, and costs nothing per tick. + - What you lose: the rise time. A lit rock is known at its settled level the moment it is in range. A rock never + tries to hide, so that delay carries little play. + - Pings: a ping's boost is `visibility × …` (`Sensor.cs:170-175`), so a dark rock gains nothing from a ping in + any option. + - A parity test pins that a ship with the same reflector, held still, settles at exactly A's value. +- **B. Per-tick accumulation within reach.** Each sensor-bearing ship integrates rocks within its longest weapon + range every tick. + - It has the ships' exact dynamics, rise time included. + - Cost per tick grows with ships × rocks in reach, and it keeps state per ship per rock, which needs pruning. +- **C. Lazy integration.** Store (value, last-time) per ship per rock, and integrate the elapsed time when queried. + - It is close to B's dynamics with no per-tick cost. + - The state grows with every rock a ship ever looked at, and it is approximate when light changes between + queries. -*Recommend A.* Every term is one sentence, and every weapon stat in it reads as a benefit (your reciprocal-formula -rule). B can be added later without changing a weight. +*Recommend A.* It is the same rule with no new state and no per-tick cost, and it matches the campaign's founding +invariant: chunk quantities are pure functions of time. The one behaviour it drops, the rise time, belongs to things +that move and hide, and rocks do neither. -**Q5. Is the loot rolled per hit, or once when the chunk breaks?** (Blocks Cut 5.) - -*Context.* Per-hit rolling keeps only hit points as state. Rolling at break needs either the breaking weapon -alone to decide everything, or per-ore accumulation per chunk. - -*Options.* -- **A:** per hit, units proportional to damage. -- **B:** once at break, weighted by the weapon that broke it. -- **C:** once at break, weights accumulated per hit. - -*Recommend A.* Your sentence ties the weight to "the weapon you hit it with". Under B, a chip shot from the wrong -gun at the end decides the whole rock. - -**Q6. Where does the ore go, and how big is a lot?** (Blocks Cut 5.) Two sub-forks, one decision each. - -*Q6a. Destination.* -- **A:** straight into the shooter's cargo at arrival. A full hold loses the overflow. -- **B:** a floating pickup. -- *Recommend A.* Pickups are Unity-only (`ItemPickup`, and collection in `ShieldManager`). An AI can never collect - them, which breaks the same-inputs ruling. - -*Q6b. Lot granularity.* -- **A:** one lot per (zone, field, commodity) source, reused until garbage collection. -- **B:** one lot per hit. -- *Recommend A.* Stacks merge only within one lot, so B makes every hit its own cargo stack and grows the ledger - per shot. A is also the shape backward generation will synthesize. - -Lot quality is 1. Nothing reads a `SimpleCommodity`'s quality today (`ItemManager.cs:117,226` @`b66ba524` read -crafted lots only). - -**Q11 (new; answer it before Q7). What is a field kind, and what does each kind yield?** - -*Context.* Your Q2 ruling generalized mining to "chunks of a field", with "various kinds of debris fields" beside -asteroid belts, and said a debris field "rolls salvage through the same weighted roll". Today the only field in -the code is `AsteroidBeltData`, and there is no kind concept. The loot entries the map designed are ores -(`SimpleCommodityData`). Salvage could mean commodities, or it could mean gear. - -The question is what the kind is for: -- Is a kind a content record an author makes more of: "rich belt", "ice belt", "wreck field of faction X"? -- Is it a small fixed set of simulation categories: rock, debris? -- Can a kind's table name items other than simple commodities, such as a damaged `GearData` from a wreck? - -I am not recommending here, because the answer defines the concept. What depends on it: -- where composition lives (Q7); -- whether mined units are always `SimpleCommodity`, which Cut 5's lot and stacking rules assume; -- whether debris salvage enters provenance as `Extracted` or as a different origin. - -**Q7 (restated). Where does composition live?** (Blocks Cut 5; follows Q11.) +**Q16. How does a belt get its field kind, including belts in runs that started before this campaign?** (Blocks +Cut 3.) *Context.* -- `BodyData.Resources` exists on every body. It has had no writer since 2020, its only reader died in Cut 2, and - 0 of the 8 belts in the probe save have data in it. -- The map recommended per-ore `Abundance` and `Affinity` fields on `SimpleCommodityData`, with composition - derived. That was written before your ruling that tables are per field kind. +- Your Q11 ruling makes field kinds authored records. A chunk's reflectivity (Cut 3) and its yield table (Cut 5) + both come from its belt's kind. +- Today belts are generated with no kind (`AsteroidBeltData`, `ZoneData.cs:101-105` @`283ce7dc`), and a saved run + keeps its belts for the whole run. +- Only one kind will exist at first ("Asteroid"), but you will add more. *Options.* -- **A′ (new):** composition is derived from a per-kind catalog record: a list of entries, each a commodity, - abundance and affinity. A field names its kind. Nothing is stored per chunk. -- **A:** the original. Per-ore fields on `SimpleCommodityData` give one global table for every field kind. -- **B:** `ZoneGenerator` writes each belt's `Resources` at generation, and chunk jitter is derived. Runs started - before Cut 5 stay empty until New Game. +- **A. Derived, never stored.** A belt's kind is a pure function of the belt's identity and the catalog's kinds + (weighted pick). Nothing is saved. Adding or reweighting a kind silently re-kinds belts the player has already + mined. +- **B. Stored at generation, assigned once for old belts.** New belts get a kind when generated. A belt loaded + without one is assigned by the same function the first time it loads, and saved. Adding a kind later never + changes an existing belt. +- **C. Stored at generation only.** Belts from older runs stay kind-less: dark, unminable and unseen, until New + Game. -*Recommend A′,* if Q11 says kinds are content. -- It is the only option where a debris field and a belt can differ, which is what Q2 ruled. -- It is also the shape document variants pay off on. "Rich belt" is a variant of "common belt" that overrides one - entry's abundance by element id. That works only if the entries are a list of objects, not a dictionary: the - variants ruling Q7 replaces dictionaries whole. - -**Q8. The catalog has no Corrosive or Ionizing weapon, now or in its history.** (Blocks Cut 7.) - -*Context.* -- Live weapons use four damage types (census, section 0), and the enum has six. -- Both legacy msgpacks use the same four. -- An ore whose affinity peaks on a missing type cannot be reached. - -*Options.* -- **A:** author affinities over the four types that exist, and record the gap. -- **B:** author new Corrosive and Ionizing weapons in Cut 7. - -*Recommend A.* New weapon types are a content decision, not a mining one. - -**Q9. Which AI ships mine?** (Blocks Cut 6.) - -*Context.* Nothing assigns a `Mining` task. Every NPC gets `PatrolOrbitsTask` (`Zone.cs:129` @`283ce7dc`). - -*Options.* -- **A:** in a zone with fields, an NPC whose loadout has a weapon with nonzero affinity for some ore there gets a - `Mining` task, at an authored share. -- **B:** only dedicated miner hulls or factions mine. That needs the faction-territory "extraction" role. -- **C:** defer AI mining, and prove the path with a test agent only. - -*Recommend A* for this campaign. B can later replace A's eligibility rule without touching the path. - -**Q10 (new). What does a blast do to chunks, and who gets the ore?** (Blocks Cut 4's proximity branch and -Cut 5's blast loot.) - -*Context.* -- 12.4 made every blast an area: `Detonate` applies damage in proportion to the overlap of the blast disc with - each covered cell, over every entity, live at detonation (`FireControl.cs:1074-1150` @`b66ba524`). -- It takes no shooter, so it cannot route loot. -- Chunks are not entities, so today a blast ignores them entirely. -- The mining map assumed only the targeted chunk counts. -- One mechanism fact matters: **a chunk's pose is a pure function of time.** `Commit` can compute exactly where - every chunk will be at arrival, so blast loot can still be decided at commit, as the target's invariant 5 - demands. The only thing that can change between commit and arrival is a chunk breaking, and `Wear` already - refuses a hit on a broken chunk. - -*Options.* -- **A:** a blast covers every chunk its disc overlaps. Each chunk takes its area share. `Commit` computes the - covered chunks at the arrival pose and rolls their loot from the shot's generator into the shooter's cargo. - `Detonate` gains a chunk pass through `ChunksNear`. -- **B:** a blast covers only the targeted chunk, with its area share. No other chunk is touched. -- **C:** blasts do not mine. Only direct hits wear chunks. - -*Recommend A.* -- It is 12.4's own rule, "a blast has no direction", applied to one more kind of body, so there is one area rule. -- B is a special case that makes a proximity burst next to a rock you did not target do nothing. -- The cost of A is one `ChunksNear` query per blast. The map measured about 55 µs for the largest probe belt. - -**Q12 (new, from the map's own open line `:217-220`). Can launchers mine?** - -*Context.* -- Lock and guided launchers need a lock. `LockWeapon.cs:86` @`b66ba524` locks only a hostile target, and a rock is - never hostile. -- So GT 3K, LRMM72, SRMM72, pswarm and scorched void policy cannot fire at a chunk. -- The map said "Rule it if that is wrong", and nobody has. - -*Options.* -- **A:** launchers cannot mine. -- **B:** a chunk can be locked, and the lock rule gains a chunk branch. - -*Recommend A.* It keeps the lock path as it is, and most launchers are blast weapons, which reach chunks through -Q10 = A anyway when a proximity round is fired at a nearby ship. It is your call if you want missiles as mining -tools. - -**Q13 (new). Until the EW campaign, how far away can a chunk be targeted?** - -*Context.* -- Your Q2 ruling deferred chunk detection. A chunk is visible unconditionally, and targeting it is an on-demand - query. -- The reticle pick is local. -- "Next target" and the AI's pick need a radius, or they walk every rock in the zone (809 in the probe zone). -- The map had tied that radius to the Resource Scanner, which your ruling parked. - -*Options.* -- **A:** the longest range among the ship's active weapons. -- **B:** a `GameplaySettings` constant. -- **C:** reticle only; cycling skips chunks. - -*Recommend A.* It needs no new tunable. Weapon range already gates fire, so a rock you can cycle to is a rock you -can shoot. The EW campaign replaces it. +*Recommend B.* One assignment function serves both paths (generation and first load), so there is no second +rule. A belt keeps the identity the player learned. A is the only option where a catalog edit rewrites the world +under a running save. ## A.6 Dependencies (mining) - **Fire-control-12 on master:** hard, for every cut from M0 on. +- **Both CultLib roots in every build:** CultCache `45c2f40` and CultMath `6d5e209` (shared precondition). The + `VerifyCultLibRevision` guard does not fire on the Linux verify host. That is a separate defect, handed to Self. - **The no-target fuse ruling:** the same lines as Cut 4 (`FireControl.cs:389`). Land it first or fold it in. -- **F12-2, the penetration retune:** Q4's `MiningDepthPerPenetration` absorbs it. There is no ordering constraint. -- **F12-5, AI and proximity blasts:** Cut 6 inherits it. -- **Locomotion Cut 4:** Cut 6. +- **F12-2, the penetration retune:** Q4's `MiningDepthPerPenetration` absorbs it. It gates Cut 7 §4's optional + Ionizing penetration step. +- **F12-6, mines:** when mines ship, they are sourceless blasts. They wear chunks and yield nothing (Cut 4's null + sink). - **Settings-globals:** Cut 5's tunables join `GameplaySettings` and `PlanetSettings` wherever those live. None may be CultMath-typed while settings are Unity-serialized. +- **No longer dependencies (Cut 6 deleted):** locomotion Cut 4, and F12-5 (AI and proximity blasts). - **Document variants:** - **C2 (element ids, in flight on `hands/variants-c2a`):** - - "Ids everywhere" (variants Q3) requires an element-id member on every object-list element type. For - mining, that is the Q7 = A′ yield-entry type (new) and `ChunkWearPack` (`ZoneData.cs:51` @`283ce7dc`, - a list element in `ZonePack`). - - If C4 lands first, Cut 5 adds ids to its own new types. If Cut 5 lands first, C4's sweep lists them. - - **Slot coordination:** Cut 5 claims key 11 on `ItemInstance`, the `Lot` move. C4 must not put - `ItemInstance`'s element id at 11. Whichever lands second checks. - - **C3 and C4 (Studio, then Aetheria adoption at `caching-unity-v1.5.0`):** Cut 7 content authored as variants - (field-kind families; a Drill Bit as a variant of an existing beam, if you name a base) waits for C4. - Content authored as plain records does not. The `AetherDb` content command honours variants Q1a either way. + - "Ids everywhere" (variants Q3) requires an element-id member on every object-list element type. For mining, + that is `FieldYield` (new, Cut 5), the affinity pair type inside it (new), and `ChunkWearPack` + (`ZoneData.cs:51` @`283ce7dc`, a list element in `ZonePack`). + - If C4 lands first, Cuts 3 and 5 add ids to their own new types. If they land first, C4's sweep lists them. + - **Slot coordination:** Cut 5 claims key 11 on `ItemInstance` (the `Lot` move), and Cut 3 claims the next + free key on `AsteroidBeltData`. C4 must not put an element id on either. Whichever lands second checks. + - **C3 and C4 (Studio, then Aetheria adoption at `caching-unity-v1.5.0`):** + - Field-kind families ("rich belt" as a variant of "Asteroid") and the two new weapons (variants of ClearPath + and Spectra, with a nested `DamageType` override) are the natural variant content. + - If Cut 7 lands before C4, it authors them as plain records and adds them to C4's re-author list. - Aetheria pins CultLib `45c2f40` and `caching-unity-v1.4.0` today (`Directory.Build.props`). No mining cut needs a CultLib bump. - **For the C4 owner, not mining:** four catalog records hold a null `Behaviors` element: Refinery, Deep Ore Extractor, Assembly Line and Shipyard (section 0, present on `b66ba524`). C4's `MintElementIds` pass over `Aetheria.cc` has to decide what a null list element is: refuse it, drop it, or skip it. The variants cut map - does not say. These are the station factory gear the mining map leaves alone (`:430-433` @`283ce7dc`). - ---- - + does not say.