From 827f11c6df5435eec4a9ee36cd86c7d852eaf3aa Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sun, 18 Oct 2026 01:56:43 +0200 Subject: [PATCH] aetheria-gravity map: B59 bodyless binary members left at distance 0; why Settle recurses (spec bodyless-node-places-children.r1) Co-Authored-By: Claude Opus 5.5 --- docs/aetheria-gravity-map.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/aetheria-gravity-map.md b/docs/aetheria-gravity-map.md index 647963181..caf7cc957 100644 --- a/docs/aetheria-gravity-map.md +++ b/docs/aetheria-gravity-map.md @@ -96,6 +96,7 @@ All read at c6519383 in the detached worktree `C:\aeth-wt\ag2-docs` on 2026-10-1 - **B56. A belt is an interval in its slot** (imagination-ag4-cwr4 scratchpad, 2026-10-17: Soul's belt and isolation probes, extended with percentiles, run on Yggdrasil at 1f83d074 with three placements patched in, jobs jbase/jA/jB/jC, production entrance zones seeds 1-130 plus every zone of seeds 7-12). Spread over AsteroidBeltWidth(D), 170 entrance belts: r3's symmetric half-width gives 145 at zero, median 0, p90 0.04, mean 0.012. Filling the slot (inner edge max(lower, previous sibling's outer edge x q), outer edge min(window, next sibling / q), belt centred, width min(W, slot)) gives 0 at zero, p10 0.02, median 0.11, p90 0.29, mean 0.13, 1 belt over 0.5; clamping the ruled point into the slot instead of centring gives the same distribution. Body counts, isolation (0 of 1456 pairs, worst 0.833; 0 orbit-instants in another root's support or reach) and the full suite (2103) are unchanged: belts are massless, so moving one inside its slot moves no well. Letting a belt claim its authored width in the chain instead keeps 21 of 170 belts (median 0.65) and folds bodies (wells 1793 to 1727), against c4-proceed-and-fly's counts, and fails LockOnSituation. So under the band law every production belt is slot-bound: AsteroidBeltWidth never binds and is a ceiling only, and the neighbour terms bind once the belt is an interval (the symmetric form's neighbour term was redundant, Soul's s1). Turret order: the unmutated StationPlacement harness gives the same stations, turrets and orbit sums in two processes and twice in one; the 0.8 tail mutant survived 3 four-class runs and 2 single-class runs (jm0p8), so jM's one CAUGHT is not reproduced, and there is no shared state to fix: the tail rule needs a forced fixture. - **B57. Belt rocks and the floater layer** (imagination-ag4-floater, 2026-10-17, uncommitted xunit probe over `ChildrenWithinReachTests.Zones`, all 384 zones of seeds 1-6 at 1d4aa626). 240 zones hold a belt, 450 belts, 42693 rocks; rocks per belt p10/50/90/max 31/75/186/499, at most 6 belts per zone. |field height| under a rock p10/50/90/99 41/165/323/434 (max 1027); field spread across one belt p50 41, p99 111. Rocks are drawn at field + AsteroidVerticalOffset (-10 in Settings.asset; the asteroid shader adds the Surface Displacement texel, which is -GetHeight), but the chunk reticle (ActionGameManager.ChunkWorldPosition) sits at absolute y -10, so it misses the drawn rock by the field depth. Rock radius pose.w p10/50/90/max 3.03/3.73/5.41/6.00; the LowPoly Small meshes measure 1.06-1.46 from their centre (FBX vertices; Unity file-scale reading unverified), so a rock centred on the floater layer (0, ships +3) reaches the ship plane at the median. - **B58. A ring is checked before its members settle** (imagination-ag5-ring, 2026-10-17: scratchpad `imagination-ag5-ring/`, jobs job2 and job3 via ygg-verify at f948992d and 26c44020, `ZoneGenerator.Place` instrumented by `instr.patch`, the candidate fix `fix.patch`, constructed fixture `ProbeRing.cs`; `ChildrenWithinReachTests.Zones`, all 384 zones of seeds 1-6). Source: `Settle`'s ring branch judges `ringDistance >= 1.5 x Roche(heavy.Mass)` (ZoneGenerator.cs:672) right after the circumbinary children fold into heavy, but a member's own children fold into it only when the queue reaches that member (ZoneGenerator.cs:687-694), after the check, so its mass is not final when it is read. Probe at f948992d: of 63 standing production rings, one (7 members, radius 98.302) ends 0.07% inside its heaviest member's margin (98.367), inside the test's 1e-3 tolerance; the constructed rings (two members, the lighter given a child of half the heavy's mass, radius 1.02 x margin) all stand, with the lighter member grown past the heavy (1000 -> 1400 for 900 + 500). At 26c44020 (place names): 4 production rings violate; the failing seed-1 ring (9 members, radius 126.029) passed its check at heavy mass 14591.9 (margin 120.68), then heavy's own children folded in to 19148.25 (margin 129.16); in all 4 the heaviest at the end is the member that was checked, so the seed-1 shape is the heavy growing, not a member overtaking it. Settling the members inside their node's Settle and checking `ring.Max(Mass)` folds exactly the violating rings (master: 1 flip in 384 zones, a ring folding into one body, so topology moves there; constructed: all 3 fold, root mass 2400 for mA 1000), and the full suite passes with it: f948992d 2215/0, 26c44020 2224/0, so no seeded test repins on either. Other order inferences, by source read: a body child's cusp bound (`AtLeast`, ZoneGenerator.cs:607-614) reads `WellMass(child)` before the child's own folds, and circumbinary children read `WellMass(heavy)` before heavy settles: the same shape, growing as mass^0.05 (the test's own `CuspTolerance` comment), absorbed by its 1e-2 tolerance. The Roche lower bound is re-read on every fold iteration (`lower()`), so it is final; belt slots read that final `lo` and siblings placed in the same pass; the circumbinary distance, chord caps and subzone isolation (`rootReach` from `WellTree.SubzoneReach`, centres only) are mass-free. The GPU parity fixture is hand-built (GravityFieldParityVerify.cs:152-190) and never calls the generator. +- **B59. A bodyless ring member is left off its ring** (imagination-ag5-zero-dist scratchpad, 2026-10-18; xunit probe `ImagProbe` with `ProbeLog` instrumentation of `ZoneGenerator.Settle`, Yggdrasil jobs j3/j4 at 390a5958, every zone of seeds 1-30, 1920 zones). Shape: a planet expanded into a binary (`ExpandRosette(2)`) has a member expanded again in a later satellite pass, so the member is `Empty` with its own ring. Settle's ring is `c.Ring && WellMass(c) > 0`, and WellMass of an Empty node is 0, so a bodyless member is not on its node's ring. 85 nodes in 1920 zones have a bodyless ring member: 81 have one (the other member is a body), and 4 have two. With one, `ring` is the body alone and the bodyless member goes to `PlaceChildren` as a circumbinary child. With two, `ring.Count == 0` returns, both members keep distance 0, and the queue settles each one with reach `Reach x 0`: their inner rings get distance 0 too and fold, which leaves two bodies stacked at their node's centre. These are exactly the finding's zones: seed 10 zone 608, 13/792, 21/1285 and 30/1856, each with 2 bodies at distance 0 under a bodyless parent. *Candidate* (a bodyless node is one body of its family's mass, `OneBody`, for ring membership, the heavy member and the margin, as `AtLeast` already reads it; and a recursive Settle in place of the queue and the settled set). The order change alone gives a dump of all 1920 zones identical to head. The candidate has 0 zero-distance zones, changes 0 of the 384 zones of seeds 1-6, and changes exactly the 4 zones above in 1-30 (orbits 18 to 16, 32 to 30, 32 to 30 and 13 to 11; one body fewer each, because the outer ring of two folded binaries cannot keep its margin and becomes one body). The 81 one-bodyless rings fold under both generators: all had the same dump. Under the candidate, no node in seeds 1-30 has a bodyless ring member together with a non-ring child, and none has massive children but no massive ring member. Under the constants, a ring's circumbinary children always fold: the lower bound `(1 + RingReach) Beta ringDistance` equals the window's top for roots and non-roots alike (B51). ChildrenWithinReachTests takes 5 s at seeds 1-9 and 6 s at 1-30 (test time; 25 s and 64 s wall with build). At 1-30 the head generator fails RingsKeep at seed 10 ("ring of radius 0"), and the candidate passes 18 of 18. The full suite under the candidate at seeds 1-30 fails 2 of 2327. ANarrowAuthoredBeltKeepsItsWidth hit a load timeout (51 s) and passes alone. StationPlacementTests.AStationNeverSitsOnABinarysBodylessNode reads "0 bodyless nodes and 188 stations": its forced fixture (every satellite a binary, 4 passes) found its bodyless nodes only in the defect's shapes. Once every binary is judged, they all fold. Under base the same fixture holds 1202 bodyless nodes. Under the candidate, forcing rosettes (RosetteProbability 1) gives 338 bodyless nodes but only 2 stations, while production settings with parentless bodyless nodes counted (root rosettes) give 41 bodyless nodes and 191 stations, and the test passes (job j11 at b9e16872). ## Model page @@ -139,6 +140,7 @@ Cells not filled, plainly: whether a per-body multiplier on c or the core is eve - Why nesting the light roots under the heaviest is rejected (B52): roots are stationary, so a light sun fixed on the heavy sun's slope sits in a field nothing balances; a ship parked by it drifts toward the heavy sun while the sun's planets keep their rails, which is the coasting-orbit mismatch `tame-tails-parent-only-rails` ruled out. Patched conics (spheres of influence, as KSP flies them) are the prior art for the alternative: each region owned by one body. Isolated subzones are that reading for stationary roots; superposed-inside keeps today's superposition of fixed masses. Which one the operator flies is question `multi-root-zone-shape`. - Why a belt's extent is the well tree's input and not the placement's alone: "inside L_F a node pulls as its body alone" is the tree's guarantee about every rail round the node, and a belt is a family of rails out to its outermost asteroid (B53). The tree reads that extent; children-within-reach decides where belts go and how wide, and must keep their inner edge outside the Roche margin. - Why the series threshold is a GravityField constant emitted into the template: B54 sets it at 0.4 by measurement, and a literal `1` in both C# and HLSL is the second spelling `gravity-law-one-spelling` already names. +- Why Settle recurses instead of draining a queue (B59): a node's Settle reads only itself, its parent's chord cap and its own subtree, so the visit order is free, and the order change alone leaves all 1920 zones of seeds 1-30 unchanged. Recursion puts each child's Settle right after its parent places it, so every node settles once by construction. The settled set and the queue's guard then have nothing left to guard; Soul's surviving mutant on that guard (follow-up `queue-settle-guard-pin`) disappears along with it. Why a bodyless member belongs to its node's ring: under `binaries-orbited-from-afar`, nothing orbits a binary except its members (round the node) and children far enough away to see one body. A member that is itself a binary is still a member, and from afar it pulls as one body of its family's mass, the reading `AtLeast` already used. One helper now carries that reading everywhere a mass is read. - Why the ring's members settle inside their node's Settle rather than the check reading a predicted mass (B58): a member's final mass is whatever its own placement folds, which only its Settle computes; predicting it would be that placement spelled a second time. Settling the members first reads the one owner, and a member's Settle depends only on the Distance and chord cap its node has just written, so nothing else in the breadth-first order moves. - A constraint for pseudospace, not solved here: a push with curl (B11) has no height, so under `grid-shows-how-you-move` it either never moves ships, or is drawn by another channel, or is restricted to its gradient part.