60 KiB
Mining: refreshed cut map (v2, operator rulings of 2026-09-30)
Superseded in part (master
df7c44f2). Chunk reach and picking no longer go throughZone.ChunksNearor anActionGameManagercandidate list.Zone.Targets(ServerShared/Targeting/) answers range and best-first queries,Entityowns the reticle, next, previous and nearest rules, and belt tasks,SettleBelts,BeltUpdatesandMineAsteroidare gone. Read the as-built section "Mining index: as built" indocs/aetheria-release-map.md(brancheureka/aetheria-release-target) before any anchor below that namesChunksNear,VisibleChunksInReach's old body,ActionGameManagertarget code or belt threading.
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; operator confirmed Q14 A and Q16 B the same day)
- 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
-
Branch tips read, 2026-09-29, after
git fetch.origin/master9b85211forigin/codex/fire-control-12b66ba524origin/codex/mining283ce7dcorigin/codex/moddable-ships375d6bd4- Nothing in
F:\Projects\Aetheria's working tree was read or switched. Every read isgit showorgit grepagainst a SHA.
-
The post-fire-control Body is a synthetic merge.
git merge-tree --write-tree origin/master origin/codex/fire-control-12is clean, tree91576243. I wrapped it in an unreferenced local commit,8a2db490(no ref, no push to origin), so it can be built. The fire-control checklist (docs/merge-to-master-checklist.md:7,107onb66ba524) predicts the same clean merge. -
Anchors. Each
file:linenames a real SHA, never the synthetic one:b66ba524for files only fire control touched;283ce7dcfor files the mining branch touched;375d6bd4for files the ships branch touched.
The three file sets are disjoint. I checked with
git diff --name-onlyover each branch's own range, and none of the three pairs shares a file. So each anchored line is byte-identical in the merged tree. -
Tests ran on Yggdrasil, against synthetic merges (section 0). CultLib
45c2f40and CultMath6d5e209are the pins inDirectory.Build.props. The two LFS files the tests read (GameData/Aetheria.cc,Assets/Resources/Settings.asset) were staged beside the job, because the verify mirror carries no LFS. -
Catalog facts come from a probe that opens the merged tree's
GameData/Aetheria.ccthroughAetheriaStores.Open(section 0).
0. Probe results
Test suite on the synthetic merges, Yggdrasil, dotnet test tests/Aetheria.Shared.Tests, CultLib 45c2f40,
CultMath 6d5e209, the dotnet image:
| Tree | Commit (local, unreferenced) | Result |
|---|---|---|
| master + fire-control-12 + mining | 396d19e5 |
379 passed, 0 failed |
| master + fire-control-12 + ships | 0036204f |
364 passed, 0 failed |
| master + fire-control-12 + mining + ships | 474c35c3 |
381 passed, 0 failed |
Fire control closed 12.4 at 362 tests. 379 = 362 + 17 mining tests, 364 = 362 + 2 ship tests, and 381 is both together. Nothing on either branch breaks a fire-control test, and nothing in fire control breaks a branch test. This proves the headless simulation only. No Unity compile ran.
Catalog probe (scratchpad/probe/Program.cs: it opens GameData/Aetheria.cc through AetheriaStores.Open
in the merged tree; the catalog oid 2aa79c0a is the same in all three merges and at b66ba524):
-
124 items: 32
GearData, 18WeaponItemData, 13SimpleCommodityData, 5HullData, 51 compound commodities, 4 cargo bays, 1 docking bay. -
Weapon damage types: Kinetic 9, Electric 3, Optical 3, Thermal 3. There is none of Corrosive or Ionizing, though the
DamageTypeenum has all six. The mining map said Kinetic 10. -
Penetration > 0: only Autocannon, LRMM72 and SRMM72, each at 0.25.
-
DamageSpread > 0: GT 3K 3.25, plight 3.3, pswarm 1.5, scorched void policy 1.5, and SRMM72 0.5. The map listed four and missed SRMM72.
-
No weapon has
BlastRadiusorFuse. Blasts are dormant in shipped content until F12-2 authors them. -
Simple commodities: the 13 the map named. Every one is priced 0 except Ammo, at 1000. The map's "every live simple commodity has Price 0" is off by one.
-
No live item carries a scanner or mining behaviour, on fire control or on mining merged.
-
Pre-existing, on
b66ba524as well as on the merges: fourGearDatarecords (Refinery, Deep Ore Extractor, Assembly Line, Shipyard) hold a null element inBehaviors(index 0 of 1). It does not come from mining's union-26 retirement: the same four are null on fire control alone. This matters to variants C4 (A.6). -
Shipped hull mount ids are not unique:
Hull Hardpoints Distinct TransformsEmpty TransformsDjinni 22 20 3 Longinus 13 12 2 LonginusX 10 9 2 Turret 7 3 5 Zenith 5 1 5 All five hulls use
Prefab. This matters to ship migration (docs/moddable-ships-cut.mdS5).
Shared precondition for both campaigns
Both campaigns wait on one event: codex/fire-control-12 merging into master with --no-ff, which Self does
after the operator signs off (docs/merge-to-master-checklist.md:105-113 on b66ba524). Until then, neither
campaign has a base to merge onto except the fire-control branch itself. After it:
git switch codex/mining && git merge --no-ff master # in a worktree, never the operator's tree
git switch codex/moddable-ships && git merge --no-ff master
Merge, do not rebase. Both maps and their Soul receipts cite landed SHAs: mining cites 11 of its 13 commits, 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@b66ba524pins two revisions of the same repository:CultLibRevision45c2f40(CultCache) andCultMathRevision6d5e209(CultMath, tagcultmath-unity-v0.2.4).CultMathRootdefaults toCultLibRootwhen it is not passed.erfanderfinvexist in CultMath at6d5e209(packages/cultmath/src/CultMath/math.cs:528). They do not exist at45c2f40:git grepfinds neither there.FireControl.cscalls them at:1323and: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
A.1 Status header (verified against git)
Branch codex/mining, tip 283ce7dc: 13 commits on 8bd25f6f. 8bd25f6f is an ancestor of fire control,
which has since moved 76 commits to b66ba524.
What truly landed:
| Cut | Commits | What |
|---|---|---|
| map | e729e6d6, 670831c8, 007771fe, e4d38f16, a9538b20, 283ce7dc |
target, cut map, Q1-Q3 rulings, status |
| 1 | 22a54ccb; fix d9c887c1 |
belts evaluated on demand; Task.Run belt threading deleted |
| 2 | 3b9f0f1d, c924bae0; fixes daa64560, 34994732, 93bfdebd |
MiningTool, MineAsteroid and the survey Execute deleted; ChunkId, Zone wear store, ZonePack key 6 |
Production delta under Assets/Scripts (git diff --numstat 8bd25f6f 283ce7dc): +181 / −250. Tests add
+681 (MiningCut1Tests.cs 240, MiningCut2Tests.cs 441).
Test counts. The map says 274 tests after Cut 2 (:493), counted on its own 8bd25f6f base. On the merged
tree the suite is 379, of which 17 are mining's. Every one passes (section 0).
Corrections to docs/mining-cut.md at 283ce7dc:
:5"Nothing here has landed" is false. Cuts 1 and 2 landed at the SHAs above, and the map's own status block (:487-500) says so. Replace it with a status line naming both cuts and the base.:9anchors at8bd25f6fare stale for every fire-control file. Atb66ba524,FireControl.csis 1,614 lines.Splashis gone.Applyis a switch over three functions (ApplyDirectHit:680,ApplyBlastHit:740,Detonate:1074).Silhouetteis at:1278andPendingShotat:1464. Every Cut 3 and 4 anchor in the map (:396-412,:669-721) is re-anchored in A.3 below.: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.md12.4 status onb66ba524).- Chunk detection: both the Codex map and v1 of this map got the ruling wrong.
- The Codex map's Cut 3 (
:667-668,:693) specifiesFireControl.ChunkDetectionRangefrom the scanner'sRange, anUnaidedChunkRangefallback, and a testAScannerExtendsChunkDetection. 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.
ResourceScannerstays parked.
- The Codex map's Cut 3 (
- Q2's blast consequence (
:221-223) was written againstSplash. Cut 12.4 replaced it withDetonate(Zone, float2 worldPlanar, float radius, float damage, DamageType)(FireControl.cs:1074@b66ba524).Detonatetakes no shooter.- It walks
zone.Entitiesonly (:1084). - So a proximity burst beside a rock does nothing to the rock today, and nothing could route its loot.
- This was new question Q10, ruled A on 2026-09-30: a blast covers every chunk it overlaps (Cut 4).
:135, and theSimpleCommodityDatacomment it copies, are wrong about the retired keys. Both claim keys 6, 7 and 8 are retired. They are live:ItemDatadeclares them for every item,SpecificHeatKey(6),ConductivityKey(7)andPriceKey(8)(ItemData.cs:291-298@b66ba524), andSimpleCommodityDatainherits them. Only key 11 is retired onSimpleCommodityData. Keys 12 and 13 are still free. The comment atItemData.cs:304should say "key 11".- 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-oreAbundanceonSimpleCommodityData, which has one table for every field kind. The two are unreconciled. Q7 is restated below. Resolved 2026-09-30: Q11 (kinds are authored records yielding simple commodities) and Q7 A′. - The
Targetblast-radius count (:193-196, "about 60 reads in 12 files") has grown.- At
b66ba524,git grepfinds 28 target reads inActionGameManager.csand 22 inFireControl.cs, where the map counted 20 and 11. - It finds 11 in
Entity.cs, plus sites in the Unity weapon managers (GuidedProjectileManager,LightningGunManager,ConstantParticleWeaponManager,GuidedProjectile) that the map does not list.
- At
- The catalog changed after the mining probe.
4f218e85anddd437580(locomotion Cut 1) landed Longinus, Djinni and five thrusters inAetheria.ccafter8bd25f6f.- Catalog oid at
8bd25f6f:1862ddc4. Atb66ba524, and in all three merged trees:2aa79c0a. - The census in section 0 replaces the map's weapon and commodity counts.
- Kinetic is 9 weapons, not 10 (
:313). - Ammo is priced 1000 (
:117,:421). - SRMM72 also authors
DamageSpread(:270). - The Q8 conclusion stands: no Corrosive or Ionizing weapon exists.
- Still owed from Cut 1 (
:508-510): a Unity compile of theZoneRenderer/AsteroidBeltUIchange, an isolated Stryker rerun, and the operator's fly-through. Nothing afterd9c887c1records any of them. - Still owed from Cut 2 (
:493): Soul has not verified the two-commit fix (34994732,93bfdebd). The map folds that check into Cut 3's Soul pass. Keep it named, so that it is not lost when Cut 3 is rewritten.
A.2 Merge path onto the fire-control-12 lineage
| Probe | Result |
|---|---|
git merge-tree --write-tree origin/codex/fire-control-12 origin/codex/mining |
clean, tree b7feb776 |
git merge-tree --write-tree 8a2db490 origin/codex/mining (master+fc12) |
clean, tree 7280568c |
| mining merged, then ships merged on top | clean, tree a7410174 |
files touched by both mining (8bd25f6f..283ce7dc) and fire control (8bd25f6f..b66ba524) |
none |
The merged tree passes the full headless suite: 379/379, and 381/381 with ships (section 0).
No textual conflicts, and no shared file. The semantic seam is not textual: mining Cut 3 onward edits the code fire control rewrote after the mining map was written. That is handled by re-anchoring, not by merging.
A.3 Ordered cuts
The Codex numbering is kept so the map's history still reads, and Soul fix slots are added. Cut order:
| Cut | Nature | Blocked by |
|---|---|---|
| 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, detected by reflected light; the field-kind record appears | M0 (Q14 A, Q16 B ruled) |
| 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 |
| 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 is ruled.
M0. Placement and map correction
- Repo/branch: Aetheria,
codex/mining, in a worktree. Do not use the operator's tree. - Deletes first: none in code.
- Keeps/moves/adds:
git merge --no-ff masteronce fire-control-12 is on master.- In
docs/mining-cut.md: apply corrections 1-11 of A.1. - Replace Cut 3 and Cut 4 with the versions below.
- Strike
UnaidedChunkRangefrom 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-412table as history, anchored at8bd25f6f.
- Verification:
- Yggdrasil, the same job that section 0 ran:
ygg-verify.sh <aetheria clone> <merge sha> dotnet '<clone CultLib 45c2f40 to /CultLib and 6d5e209 to /CultMath; restore the two LFS files; dotnet test tests/Aetheria.Shared.Tests -p:CultLibRoot=/CultLib -p:CultMathRoot=/CultMath>'. 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. TheGIT_CONFIG_*safe-directory setting thatygg-verify.shexports 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 job mounts the Yggdrasil mirrors read-only with
- The verify mirror has no LFS (the push from
F:\Projects\Aetheriafails in the LFS pre-push hook on anssh:remote). Push from a--shared --no-checkoutclone, and mount the two LFS blobs. This is recorded so the next Hands does not rediscover it. - Unity batchmode compile of the merged tree (Self, in a worktree, never with the operator's editor open on the main tree). This is the Cut 1 renderer compile the map still owes.
- Operator: fly through a belt (the Cut 1 check the map still owes). Rocks orbit smoothly, and the minimap and the map screen track them.
- Yggdrasil, the same job that section 0 ran:
- Subtraction: docs only.
M-Soul. Fix slot for the parallel Soul pass
- Repo/branch:
codex/mining, after M0. - Content: whatever the parallel Soul pass finds in Cuts 1-2, including the unverified
34994732and93bfdebd. This map does not predict it. Cut 3 does not start until this slot is closed or empty, because Cut 3 builds onZone.Wear,ChunkExistsandChunkRadius.
Cut 3. A chunk is a target, and is detected by reflected light
- Repo/branch: Aetheria,
codex/mining, after M-Soul. - Written for: Q13 = A with the detection correction, Q14 = A, Q16 = B (all ruled).
How detection works today (read at b66ba524)
-
Emitters write into
Entity.VisibilitySources(Entity.cs:63, aDictionary<object, float>). 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:44andRadiator.cs:121.
Sources decay each tick (
Entity.cs:1083-1087,VisibilityDecay0.5). -
Entity.Visibility(Entity.cs:136) is the sum of the sources. -
Each
Sensorbehaviour 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, aReactiveDictionary). - 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;TargetInfoDecay0.5 in shippedSettings.asset).
- It adds a gain into
-
Crossing
TargetDetectionInfoThreshold(0.1) adds the target to or removes it fromVisibleEntities, via theObserveReplacesubscriber atEntity.cs:237-253. -
Fire control reads both.
PFiregates onVisibleEntities.Contains(FireControl.cs:173) and pricesPSensorfrom the info value (:179). The same info also gates the armour and gear reveal thresholds (0.5 and 0.8) throughIsRevealed. -
Light is
Zone.GetLight(float2)(Zone.cs:369@283ce7dc). It sumsPowerPulse(d / LightRadius, 8)over every sun within itsLightRadius. 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 isReflector.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, whoseObserveReplacesubscriber buildsVisibleEntities,VisibleEnemiesandVisibleFriendlies. 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, noUnaidedChunkRange, 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
ResourceScannerbehaviour still runs a per-tickUpdateover three stats no code reads (ResourceScanner.cs:35-58@283ce7dc). Delete theUpdateand the properties, and keepResourceScannerDataauthored.
Adds
-
Sensor.Gain(...), a static function extracted verbatim fromSensor.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.Executecalls 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.
AsteroidBeltDatagains a nullableCultRecordRef<FieldKindData> Kind, at the next free key afterAsteroidsKey(9)(ZoneData.cs:101-105@283ce7dc).ZoneGeneratorwrites it at generation, through one functionFieldKinds.Assign(belt key, catalog): aStableHashpick weighted byGenerationWeight.- 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 activeSensorbehaviours throughSensor.Gain, integrated per Q14. Visible meansChunkInfo > TargetDetectionInfoThreshold. -
TargetRef: a readonly struct holdingEntity EntityandChunkId? Chunk, with value equality andIsNone. It lives besideChunkId(Zone.cs:527@283ce7dc). -
Zone.ChunksNear(float2 position, float range, List<ChunkId> 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 parentOrbit.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 writesTarget.Valuedirectly (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: callSensor.Gain. Behaviour must stay identical; the whole existing suite is the check.Entity.cs:46:TargetbecomesReactiveProperty<TargetRef>, written only throughSetTarget.Entity.cs:201,:227: entity removal clears the slot only when it holds that entity.Entity.cs:229:TargetedByfires for entities only.Entity.cs:233:TargetedByCountcounts entity targets only.Entity.cs:298-299: today it clears a target missing fromEntityInfoGathered, which would clear every chunk target on the next pass. For a chunk, it clears whenChunkInfo ≤ 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:TrySelectTargetItemis entity-only.Entity.cs:1078:TargetRangereads the chunk position for a chunk.ActionGameManager.cs:365-377(reticle pick): pick the chunk under the reticle fromChunksNearat 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:.Entityreads, or the chunk position for the indicator.ActionGameManager.cs:1285:UpdateFireControlDebugtakesTargetRef.Weapon.cs:101StanceAllowsFire: true for a chunk.Weapon.cs:110ArcAllowsFire: 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:.Entityand null checks.
Authority map
- Owners:
Entity.Targetis the one slot, andSetTargetis its one writer.Sensor.Gainis the one detection gain rule.FieldKindDataowns a chunk's reflectivity.FieldKinds.Assignowns 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:
ChunkVisibilityandChunkInfoare pure functions of time and are never stored.TargetItemis entity-only.TargetRange.
- Forbidden writers:
- any second target field;
- any direct
Target.Value =outsideSetTarget; - any chunk in
Zone.EntitiesorEntityInfoGathered; - 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<Entity> Targetremains; noTarget.Value =outsideSetTarget; no gain arithmetic outsideSensor.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<Entity> Target\b"andgit grep -n "Target.Value ="outsideEntity.SetTargetare empty. - Stryker over
Sensor.Gain,Zone.ChunkVisibility,Entity.ChunkInfo,ChunksNearandTargetRef. - Census before and after:
AsteroidBeltDatais a run-store type and is absent from the catalog, andFieldKindDatashows one record once Cut 7's content lands. Until then, Cut 3 ships a test fixture kind and a one-record catalog write throughAetherDb, 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.Gainextraction nets about 0;FieldKindDataplus assignment, about 40;ChunkVisibilityandChunkInfo, 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 sametarget != nullbranches 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 fromSilhouette:1302-1308(thePhisum over merged intervals, andspan). The hull path and the disc path call one function. No secondPhisummation 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). CircleCircleOverlapis the closed-form lens area, about 12 lines, besideCircleSquareOverlap(: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.
Detonategains 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.
Detonatestill reads no host (12.4's rule).
Per-file changes (FireControl.cs @b66ba524):
PFire:161-181:- For a chunk, the visibility gate (
:173) isChunkInfo > TargetDetectionInfoThreshold, andinfo(:179) isChunkInfo(Cut 3). This replaces v1's "no gate, info 1". PSensorprices a chunk exactly as it prices a ship at the same info.- A
LockWeaponis gated out (:175, Q12 = A). The arc stays (:176).
- For a chunk, the visibility gate (
Forecast:206,HitProbability:228,Inspect:243-283: the disc silhouette.Inspect'sVisibleandInfocome from the chunk path.PredictedIntercept:133,TravelDirection:307: position and velocity through the ref.Bearing:316is not called for a chunk, because a disc has no facing.DeviationProbability:336-341: the live chunk position isZone.ChunkPoseatnow.Fire:355-418: freezes the ref.Step:439:targetGoneincludes!ChunkExists.CommitProbability:506-520: for a chunk, the disc (noGetData(Hull)at:516).Commit:550-600: for a chunk hit, noLane(:563-578) and no shield block (:585-592).Cell,BearingandLateralstay zero inMakeOutcome(: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).Detonateat the chunk's centre at arrival. The pose is a pure function of time, so the point is exact. - Proximity:
Detonateas today, now with the chunk pass. - Contact or delayed on a ship:
ApplyBlastHitas today, and itsDetonatenow also covers nearby chunks.
- Null fuse on a chunk:
- 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.Detonatestays the one area owner. - New inputs: chunk pose, velocity, radius and info, through
ZoneandEntity.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 atCommit. - Forbidden writers:
- any chunk hit test outside
Commit, exceptDetonate's area pass; - any second area-share formula;
- any second
Phiintegrator; Detonatereading a host.
- any chunk hit test outside
- 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 throughDetonate. - Deletion line: none.
Verification (Yggdrasil, --filter FullyQualifiedName~MiningCut4, then the full suite):
| 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. |
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.
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.csand its.meta(@283ce7dc: 19 lines, no reader, not persisted perAgentTask.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.Mineenum member (AgentTask.cs:27). Removing it would renumber the members after it for no gain.
- Leave the
Adds:
-
FieldKindDatagainsEntries, aList<FieldYield>. EachFieldYieldis:Commodity: aCultRecordRef<SimpleCommodityData>(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), withDepth = 1 + P × MiningDepthPerPenetration; - one draw per unit.
- units =
-
Where each roll happens:
- Direct hit: inside
Commit, after the hit draw, from the shot's own generator (FireControl.cs:540@b66ba524). Frozen intoShotOutcome.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 inChunksNearorder (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
Commitfor 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.
- Direct hit: inside
-
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) gainsBodyat key 2 (CultRecordRef<BodyData>, nil on older records). -
Lotmoves fromCraftedItemInstance(ItemInstance.cs:38) toItemInstance, 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 releases the overflow as floating items throughZone.Release, which the player and NPCs collect (rulingaetheria-release:ruling:mining-overflow-floats, 2026-10-16, superseding Q6a A's 'lost', whose premise was Unity-only pickups; cut linemining-yieldin the release map). -
Tunables:
MiningDepthPerPenetrationonGameplaySettings;AsteroidYield(ExponentialLerp) andChunkCompositionVarianceonPlanetSettings. 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:
Entity.SetTarget(TargetRef(chunk))(Cut 3);weapon.Activate()(the callCombat.cs:121and the player's trigger make);Zone.Updateticks.
There is no
Agent, noAgentTaskand noMinionstate. It proves the path an AI will later drive, without building the AI.
Per-file changes (b66ba524):
FireControl.Commit,ApplyDirectHitandDetonate: the roll and the deposit, as above.Entity.cs:1856: the stack merge matchesData.Keyonly today; it must also matchLot.Entity.cs:1934: the split constructs aSimpleCommoditywithoutLot; it must copyLot.SavedGame.cs:113: the ledger roots are.OfType<CraftedItemInstance>()today; they become everyItemInstancewithLot != 0.ItemManager.cs:72:GetLot(CraftedItemInstance)takesItemInstance.
Authority map:
- Owners:
Commitdecides direct-hit loot, andDetonatedecides blast loot, each from the shot's dice.ItemManagermints lots.EquippedCargoBayplaces units.FieldKindDataowns what a field yields.FieldKinds.Assignowns 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
CommitandDetonate's chunk pass; - any mined
SimpleCommoditywithLot == 0; - any shared random stream;
- per-ore tables on
SimpleCommodityData; - any extraction call that bypasses
FireControl.
- any loot draw outside
- Shared paths: the player's trigger and the test agent (identical calls); save GC; Continue, where the index is rebuilt.
- Deletion line:
BodyData.Resourcesis retired, andMining.csdeleted, 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. |
AFullHoldReleasesTheOverflow |
Ruling mining-overflow-floats (was Q6a A's AFullHoldLosesTheOverflow). |
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
FieldKindDataslots read asdefaulted_missing_slotuntil 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
Miniontransitions (Minion.cs:10-21@b66ba524); - the one writer
Entity.SetTarget; Combat.cs:121'sActivate.
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:
CrossSectionper 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).GenerationWeight1.Entriesover the live and restored ores, each with anAbundanceandAffinityover 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
ConstantWeaponDatabeam 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 toShield.CanTakeHitandTakeHit(Shield.cs:166,:186). Both ignore theirtypeargument.- Armour absorption (
ArmorAbsorb,ItemAbsorb) andDetonatetake 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:
censusanddanglingbefore 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.
EveryDamageTypeHasALiveWeaponpins 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) |
~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: 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)
Ruled, 2026-09-30 (recorded, not reopened)
| 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 floats (ruling mining-overflow-floats, 2026-10-16, replaces '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 |
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. 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. 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.
Q16. How does a belt get its field kind, including belts in runs that started before this campaign? (Blocks Cut 3.)
Context.
- 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. 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 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
45c2f40and CultMath6d5e209(shared precondition). TheVerifyCultLibRevisionguard 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
MiningDepthPerPenetrationabsorbs 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
GameplaySettingsandPlanetSettingswherever 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
FieldYield(new, Cut 5), the affinity pair type inside it (new), andChunkWearPack(ZoneData.cs:51@283ce7dc, a list element inZonePack). - 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(theLotmove), and Cut 3 claims the next free key onAsteroidBeltData. C4 must not put an element id on either. Whichever lands second checks.
- "Ids everywhere" (variants Q3) requires an element-id member on every object-list element type. For mining,
that is
- 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
DamageTypeoverride) 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.
- Field-kind families ("rich belt" as a variant of "Asteroid") and the two new weapons (variants of ClearPath
and Spectra, with a nested
- Aetheria pins CultLib
45c2f40andcaching-unity-v1.4.0today (Directory.Build.props). No mining cut needs a CultLib bump. - For the C4 owner, not mining: four catalog records hold a null
Behaviorselement: Refinery, Deep Ore Extractor, Assembly Line and Shipyard (section 0, present onb66ba524). C4'sMintElementIdspass overAetheria.cchas to decide what a null list element is: refuse it, drop it, or skip it. The variants cut map does not say.
- C2 (element ids, in flight on