Aetheria gravity map: grid-lowering mapped (B27-B30: GPU readers, precision, one-quad lowering, parity verify)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-10-15 06:08:27 +02:00
commit f5caef9fd0

View file

@ -55,6 +55,19 @@ All read at c6519383 in the detached worktree `C:\aeth-wt\ag2-docs` on 2026-10-1
A median planet at 610 u from a median sun: [29.8, 145]. Today's generated children already inside their parent's window: 20% (planetoids' children), 35%, 68%, 94% (suns'); children-within-reach moves the rest. Roche 5·m^0.25 would open 98-100% of windows, 10·m^0.25 only 83-99%. Per class (c, core, Roche, peak a = c/core, a at the Roche limit): planetoid 88.5, 7.0, 17.0, 12.7, 5.2; planet 138.5, 12.2, 29.8, 11.4, 4.7; gas giant 211.3, 20.7, 50.5, 10.2, 4.2; sun 304.9, 32.7, 79.8, 9.3, 3.8 u/s². A ship whose thrust is below c/r cannot climb out at r, whatever the core: a packed Djinni (about 4 u/s²) is held inside 76 u of a median sun, 35 u of a median planet. K = 10 draws each body's own well from Z = 4182 to its centre 61, 88, 123, 163 deep (today's brushes 54, 80, 116, 160); the steepest drawn slope, at a body's surface, is 0.93-1.27. Summed cone at K = 10: deepest -198, -342, -478, -588, -675 for zone radii 1000 to 10000, mid-zone -69 to -237; mid-zone pull 1.79 to 0.60 u/s².
- **B26. Grid-lowering is not a sibling of potential-owner** (read at 3f5cef68). It reads what potential-owner adds (`Planet.WellStrength`, `CoreRadius`, `HeightExaggeration`, `GravityField`'s law) and deletes what potential-owner deliberately keeps (`Planet.GravityWellRadius/Depth` in Zone.cs, BodyData keys 6-7 in ZoneData.cs, `GravityRadius`/`GravityDepth` in Settings.cs and in the LFS blob Settings.asset, which two branches cannot merge textually). So it is stacked: its branch is cut from potential-owner's head once that report lands. Facts for its map: the height target `Fog Surface Displacement.renderTexture` is 2048² R32_SFloat (GraphicsFormat 49), so φ/K's range (to about -700) keeps full precision; brushes blend `One One`, so wells drawn one quad per body add exactly as the potential superposes, and the four cameras that render layer 9 (Surface Displacement, Patch Displacement, Map Gravity, Slime Gravity Renderer, B3) would all receive the lowered potential with no change to their wiring if the Gravity Well material's shader becomes the one-well law (`PowerBrush.shader:62` today) and each quad covers its camera's view rather than a support radius (log tails have none). That is a candidate, not a decision: the alternative is one pass over a body buffer, as FlowField is lowered (`VolumeSampling.LowerFlow`).
- **B27. The drawn height's cameras and readers at dd06f39d** (scan `p2.py`, `p4.py` in the Imagination scratchpad `imagination-ag2-grid-lowering/`, 2026-10-15). Only five ARPG.unity cameras render layer 9 or 10, so a renderer on layer 9 reaches exactly these:
| camera (fileID) | layers | target, size, format | window | texel | readers |
|---|---|---|---|---|---|
| Surface Displacement (1537342003, the `GridCamera` and a `FogCamera`) | 9, 10 (`m_Bits: 1536`, ARPG.unity:23476) | Fog Surface Displacement, 2048², R32_SFloat | follows the view, orthographic size = ViewDistance (4096 default) | 4 u | `_NebulaSurfaceHeight`: Volumetric.cginc `gravGradient` (slopeFlow, nebula surface normal), `density` (nebula floor), Stardust.compute:134 `gravHeight`, Asteroid.shader:31, and the drawn grid itself (`Grid 1.mat`, Tessellation Shader: `_DisplacementMap` = this texture, `_DisplacementStrength -1`, vertex y += -texel) |
| Patch Displacement (507065483, a FogCamera) | 9, 10, 12 | Fog Patch Displacement, 256², R16_SFloat | ViewDistance | 32 u | `_NebulaPatchHeight` (nebula patch density only); half precision is 1 u per step at the deepest h (B28), presentation only |
| Slime Gravity Renderer (1066823975) | 9 | Slime Gravity, 1024², R32_SFloat | zone, orthographic size = Pack.Radius | 2R/1024 (8 u at R 4182) | Slime.compute `Heightmap` (agents' height and normal) |
| Map Gravity (326516967, 488792362) | 9 | MinimapGravity 128², R32_SFloat; MapView swaps in a half-screen RFloat | minimap distance (250-2000) | 4-31 u | minimap materials, normalised by `_DepthRange` = the deepest body centre's -GetHeight (ZoneRenderer.cs:571-617, already from h, not from brush depths) |
Every texture holds depth = -h (brushes write positive `_Depth`; Stardust and the grid negate it). Layer 10 now holds only the three Sector Waves noise quads (SimplexBrush); gravity-subtract removed the giants' Gravity Waves. Layer 9 holds the Gravity Well child of Planet, Gas Giant and Sun prefabs (scaled by `GravityWellRadius`, `_Depth` = `GravityWellDepth`, ZoneRenderer.cs:494-498) and Wormhole 1's Gravity Well, Gravity Waves and Gravity Pit. The main scene camera culls neither layer.
- **B28. Bodies, depth and precision at the branch's curves** (`p1.py`, `p3.py` over `docs/research/2026-10-13-orbit-probe/data/bodies.csv`; c = 45.08·m^0.2, core 3·m^0.25, K = 10, reference at Pack.Radius, random phases). Bodies per zone (1500 zones): p10 8, p50 17, p90 28, p99 57, max 80. Over 200 zones (4000 random points plus 20 per body inside three core radii): float32 against float64 of h = Σ(φ_i(r_i) - φ_i(Z))/K differs by at most 9e-4 u (p50 1.9e-4); deepest h p50 -476, minimum -1865 (a sun's centre in a large zone; B25's -675 is the median zone's cone). R32's step at 1865 is 1.2e-4 u, so the height target loses nothing to precision; resolution limits it instead (a planetoid's 7 u core is under two Surface Displacement texels). The texture's central difference over two 4 u texels points within 1.0° (median zone) and 2.9° (worst zone) of GetForce at the 99.9th percentile, away from 16 u of any body's centre.
- **B29. Dead gravity brushes and leftovers.** `Assets/Content/Prefabs/Stations/PlanetOutpost.prefab` (a Gravity Well child, lines 84-164, on PlanetObject.GravityWell) is referenced by no GUID in the repo and its name is in no script or in `GameData/Aetheria.cc`, so it is never instantiated; `Assets/Prefabs/RTS/Planet.prefab` (a Gravity Well child bound to an RTS script's `GravityMesh`, not on layer 9) is likewise unreferenced. Materials no live object uses once the wormhole children go: Wormhole Gravity 1, Wormhole Gravity 2 (already unused), Wormhole Gravity, Wormhole Waves, and Gravity Waves (unused since gravity-subtract); `WormholeBrush.shader` and `RadialWaveBrush.shader` have no other material. `PowerBrush.shader` stays: thirteen other materials use it. `ZoneRenderer.WormholeInstances`' `gravity` member is the whole wormhole instance, not its brush. Main Menu.unity has its own Surface Displacement (9, 10) with a Boundary bowl on layer 9 and no zone.
- **B30. How the renderer lowers the law: one pass over a body buffer, not a brush per body** (decided for grid-lowering from B27-B28). Candidates: (a) the Gravity Well quad per body with the one-well law, each covering every camera's view, blended One One; (b) a CPU-baked texture; (c) one quad on layer 9 whose fragment sums every well from a buffer the renderer fills each frame from `Zone.PlanetInstances`. (b) cannot follow moving bodies at 2048² and four windows. (a) and (c) evaluate the same N wells per texel (Surface Displacement: 4.2M texels x N, N = 17 median, 80 max), but (a) also reads and writes the R32 target once per body: 34 MB per body per frame on Surface Displacement alone, 0.57 GB at the median zone and 2.7 GB at the largest, where (c) writes it once. (c) sums in the sim's order (each well minus its reference, then divided by K) from the sim's own per-body reference, so the parity error is B28's float32 floor plus the GPU's log; and the four cameras need no rewiring, because they already cull layer 9. It follows the flow's precedent: the law lives in ServerShared, a shader include lowers it, and a batchmode verify renders it and compares with the C# (`FlowFieldParityVerify`). Body count sets the cost, so the buffer is a StructuredBuffer (no cap to truncate at), which needs shader target 4.5; the project already runs compute (Stardust, Slime).
## Model page
@ -64,19 +77,19 @@ One row per kind this campaign touches. "Today" is c6519383; "target" is the sha
|---|---|---|---|
| **Zone potential** φ(p, t) | One per `Zone` instance, unnamed (a method, not a record). Not persisted: derived from the pack's bodies, orbits and PlanetSettings at zone time. Today it does not exist as such; GetHeight is a shaped heightfield (mass-neutral wells, probe Reading B). | Built when the zone is constructed; must be valid before the first Update (`zone-valid-before-tick`). Varies with body positions over zone time. No ripple term in flatspace (`flatspace-no-bowl-no-ripples`). Reference: each body's term is zero at distance Pack.Radius from it, a per-body constant, so heights never shift as bodies move (B22). Summed tails draw a zone-wide cone (B21; question superposed-wells-funnel). | Owner (cut potential-owner): the one-well law in `GravityField` (ServerShared, pure static beside FlowField, so shaders can lower it), summed over PlanetInstances by `Zone.GetForce` and `Zone.GetHeight`. Forbidden writers: `GravityAcceleration`'s slope-squared law and its gate, `GetNormal` as a force source, the zone bowl term, `RadialWaves`, any brush or shader that composes gravity on its own. |
| **Force** a = -∇φ | Derived, never stored. | Per query. Analytic gradient (sum of per-well closed forms), not finite differences of the height. | Owner: `Zone.GetForce` over `GravityField.Acceleration`. Every CPU force reader (B5) calls it: ships, floating bodies, clouds, mines, and Zone.Flow for its downhill. `GetNormal` becomes derived from it and K, for ship tilt only. Forbidden: `Ship.Update`'s private normal-to-force path; `GravityAcceleration`, `GravityReferenceRate`, `GravityStrength`. |
| **Body well parameters** (gravitational parameter c, softening radius R) | Derived per body from `BodyData.Mass` through PlanetSettings curves (today: `GravityDepth`, `GravityRadius`, `BodyRadius` × per-body multipliers). Names: per-body record key (`CultRecordRef<BodyData>`), injective. The JSON names of GasGiantData's wave fields collide with BodyData's (B7). | Computed once at Planet construction (`CalculateProperties`); never revised in play. Today's `GravityDepthExponent`, `GravityDepthMultiplier`, `GravityRadiusMultiplier` have no writer; the exponent dies under the target (no PowerPulse well); wave fields die in flatspace. | Owner of the curves: PlanetSettings `WellStrength` (c = 45.08·m^0.2, B19), `CoreRadius` (3·m^0.25) and `RocheLimit` (7.32·m^0.25, the closest a child orbits; B25, core-inside-roche-curve) in `Settings.asset`. Owner of the per-body value: `Planet.WellStrength`, `CoreRadius` and `RocheLimit`, set at construction (core inside Roche is checked there), read later by orbits-from-field and children-within-reach; never authored per body. Demoted until grid-lowering: `GravityRadius`, `GravityDepth`, `Planet.GravityWellRadius/Depth` and the two BodyData multipliers feed the brushes only. Forbidden writers: the brush material's `_Power 16` and `_Cutoff`, ZoneRenderer copying depth into materials, any shader constant. Cell not filled: whether per-body multipliers stay (no writer today; a pillar or authored body may want one). |
| **Body well parameters** (gravitational parameter c, softening radius R) | Derived per body from `BodyData.Mass` through PlanetSettings curves (today: `GravityDepth`, `GravityRadius`, `BodyRadius` × per-body multipliers). Names: per-body record key (`CultRecordRef<BodyData>`), injective. The JSON names of GasGiantData's wave fields collide with BodyData's (B7). | Computed once at Planet construction (`CalculateProperties`); never revised in play. Today's `GravityDepthExponent`, `GravityDepthMultiplier`, `GravityRadiusMultiplier` have no writer; the exponent dies under the target (no PowerPulse well); wave fields die in flatspace. | Owner of the curves: PlanetSettings `WellStrength` (c = 45.08·m^0.2, B19), `CoreRadius` (3·m^0.25) and `RocheLimit` (7.32·m^0.25, the closest a child orbits; B25, core-inside-roche-curve) in `Settings.asset`. Owner of the per-body value: `Planet.WellStrength`, `CoreRadius` and `RocheLimit`, set at construction (core inside Roche is checked there), read later by orbits-from-field and children-within-reach; never authored per body. `GravityRadius`, `GravityDepth`, `Planet.GravityWellRadius/Depth` and the two BodyData multipliers (keys 6-7) fed the brushes only and are deleted by cut grid-lowering; keys 6-7 join the retired keys. Forbidden writers: the brush material's `_Power 16` and `_Cutoff`, ZoneRenderer copying depth into materials, any shader constant. Cell not filled: whether per-body multipliers stay (no writer today; a pillar or authored body may want one). |
| **Pillar** (Ink zones) | The zone's central body in a sun's role (`ink-pillars-are-suns`). Cell not filled: no record names a pillar today (no `Pillar` or `Adrasteia` anywhere under Assets/Scripts at c6519383); whether it becomes a `BodyData` kind is the Ink-zone work's decision. | Built with the zone like any body. | Owner: same law as a sun (`pillars-are-suns`); its strength comes from the same curve or an authored c. Pseudospace shenanigans are not in scope. |
| **Orbit rail** (`OrbitData`: Distance, Phase, Parent, FixedPosition; runtime `Orbit`: Period, Position, PreviousPosition, Velocity) | `OrbitData` is a catalog/pack record keyed by `CultRecordKey`, parent by `CultRecordRef<OrbitData>`; root has no parent. Period is derived, not stored. | OrbitData created by ZoneGenerator only (the run-time writers `AddOrbit` and `CreateOrbit` are dead, B17, and go in cut-orbits-placed-at-build); Period computed at `Orbit` construction; Position written by `GetOrbitPosition` each Update and float2.zero before the first (follow-up). Asteroids and belt targets recompute periods from the same curve (B6). | Today: `OrbitPeriod` curve owns period and ignores the field. Target (open: rails-vs-field, recommended field-owns-rails): period derived from the parent's c, T = 2πd/sqrt(c_parent), the same derivation for the four readers in B6. Forbidden writers then: `OrbitPeriod` as an independent authority, any per-reader period formula. Position placement moves to construction (`zone-valid-before-tick`). |
| **Orbit placement** (child distances) | Generated `OrbitData.Distance` per body, authored ZoneGenerationSettings ranges. | Written once at generation; persisted in the zone pack. | Owner: ZoneGenerator. Open: moons-beyond-tidal-reach (whether the generator must keep children within their parent's tidal reach, which makes the generator read the strength curve). |
| **Zone bowl** | PlanetSettings `ZoneDepth`, `ZoneDepthExponent`, `ZoneBoundaryFog`; scene `Boundary` brush. | Live today (B2). Target: gone from flatspace gravity. | Ruled (`grid-shows-how-you-move`, `flatspace-no-bowl-no-ripples`). Forbidden: the GetHeight term, ZoneRenderer's driving of the Boundary brush on a gravity layer, FlowFieldTests' use of the bowl as a slope source (tests need a well instead). Cell not filled: whether the zone needs a non-gravitational edge (nothing asked for one). |
| **Ripples** (gas giant and sun waves) | PlanetSettings `WaveDepth`, `WaveRadius`, `WaveFrequency`, `WaveSpeed`; GasGiantData wave multipliers; Gravity Waves brush objects. | Live today (B2). Target: gone from flatspace. | Ruled. Forbidden: RadialWaves in the potential, the Gravity Waves objects on displacement layers. Pseudospace pulses (follow-up `pseudospace-flavours-campaign`) are a different kind, under the same grid invariant. |
| **Exaggeration factor** K | `PlanetSettings.HeightExaggeration` (cut potential-owner), 10 to start (B25); h = φ/K. | Authored; constant in play. | Ruled one global value (exaggeration-scope-ruled). Owner: PlanetSettings. Forbidden: per-class or per-material depth scales, ZoneRenderer's `_StartDepth/_DepthRange` as an independent mapping of depth (it may normalise the minimap, but from h, not from brush depths). |
| **Drawn grid height** (`_NebulaSurfaceHeight` and the other displacement targets) | Render textures named in B3; global shader names. | Rendered every frame from brush quads. | Today: the brushes own it, independently of the sim (B3), which is the defect this campaign kills. Target: lowered from the sim's potential (h = φ/K), whether by an analytic shader function fed body parameters (as `FlowField` is lowered) or by brushes whose shapes are generated from the same law; the one owner is the ServerShared potential and the shader is a lowering with a parity check (as `FlowFieldParityVerify` does for flow). Forbidden: material constants (`_Power`, `_Cutoff`, `_Depth`), smoothstep rim fades, the wormhole and station gravity wells, and any brush on a gravity layer with no sim term. Open: grid-presentation-noise (what becomes of the Sector Waves noise and the liquid-grid detail). |
| **Drawn grid height** (`_NebulaSurfaceHeight` and the other displacement targets) | Render textures named in B3; global shader names. | Rendered every frame from brush quads. | Today: the brushes own it, independently of the sim (B3). Target (cut grid-lowering, B30): one Gravity Field quad on layer 9 whose shader (`Gravity.cginc`) sums the sim's wells from a buffer `GravityLowering` fills each frame from `Zone.PlanetInstances` and `Zone.WellReference`; the owner stays the ServerShared potential, and `GravityFieldParityVerify` renders the quad and compares every texel with -GetHeight. Every camera that draws a gravity texture culls layer 9 only (Surface Displacement drops layer 10), so the drawn grid, slopeFlow, stardust, slime and the minimap read gravity only. Forbidden: material constants (`_Power`, `_Cutoff`, `_Depth`), smoothstep rim fades, per-body brush quads, the wormhole and station gravity wells, and any brush on a gravity layer with no sim term. The Sector Waves noise is off the grid from that cut until grid-noise-presentation gives it a non-slope channel (grid-gravity-only-ruled). |
| **Readers that place things at the height** (B5) | Call sites. | Every frame. | Readers only. They read h = φ/K + GridOffset. No change of owner; listed so a cut finds them all. |
| **Flow** (FlowField) | PlanetSettings `Flow`; `Zone.Flow`; shader `globalFlow`/`slopeFlow`. | Zone time. | Owner: FlowField in ServerShared (`sim-knows-the-flow`). Its slope term must read the potential's gradient, not GetNormal of a separate heightfield. Open: flow-moves-ships (B11). |
| **Ship motion under gravity** | `Ship.Update`. | Per tick: drag, then position, then gravity. | Reader of the force. Hull Drag owns damping (B8, B9). Forbidden: any gravity computation local to Ship. |
Cells not filled, plainly: whether per-body well multipliers survive (no writer today, no consumer asked); what record names an Ink pillar; whether the PlanetOutpost prefab's drawn well is ever instantiated; whether the zone needs a non-gravitational edge once the bowl is gone; real generated loadout masses (B8 is a bound, not a measurement).
Cells not filled, plainly: whether a per-body multiplier on c or the core is ever wanted (none exists; the brush multipliers go in grid-lowering); what record names an Ink pillar; whether the zone needs a non-gravitational edge once the bowl is gone; real generated loadout masses (B8 is a bound, not a measurement).
## Rationale
@ -90,15 +103,16 @@ Cells not filled, plainly: whether per-body well multipliers survive (no writer
- Why the reference is per body at the zone radius, not a zone-wide constant: a constant per body never changes as bodies move, so the height of a fixed point changes only because wells moved, never because the reference did; and the edge of the zone sits near zero whatever the body count (B22).
- Why the core is the body's radius and the Roche limit 2.44 body radii: both are mass curves with the body radius's exponent, so core < Roche holds at every mass by construction rather than by a check over the generator's range; 2.44 is the classical fluid Roche coefficient at equal densities, and it opens a moon window for 93-100% of bodies (B25). A smaller multiplier opens more windows but lets moons skim the surface.
- Why the calibration is on the sun's own field: field-strength-ruled names bodies at 12-18 u/s, which is v = sqrt(c) of the parent alone; the summed field ships feel is 2-6 times stronger (B21), which is the fork the question puts to the operator.
- Why grid-lowering takes the Sector Waves noise off the grid now rather than leaving it until grid-noise-presentation: grid-gravity-only-ruled already decides that displacement carries gravity only, and the operator's flight on this branch (funnel-kept-until-flown) is to judge how the field moves her, which slopes that move nothing would blur. Keeping the noise drawn while the samplers read gravity only would need a second height texture (a camera or a compute target) that the next cut deletes. The noise stays in the scene on layer 10, drawn only into the nebula patch height, for grid-noise-presentation to place.
- Why one field quad and not the per-body Gravity Well quads with the new law: B30. The per-body quads also cost a GameObject, a material instance and a scale per body, and a quad per body has to cover every camera's view because log tails have no support radius.
- 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.
## Cut sequence
Mapped and admitted (2026-10-14, base 11b2d7ad): `aetheria-gravity:cut_spec:cut-gravity-subtract.r2` and its sibling `aetheria-gravity:cut_spec:cut-orbits-placed-at-build.r1`, both merged at 5dd1f6b7. Mapped and admitted (2026-10-14, base 5dd1f6b7): `aetheria-gravity:cut_spec:cut-potential-owner.r3` (rebased on 3f5cef68 on 2026-10-15, after rulings `funnel-kept-until-flown` and `core-inside-roche-curve`), one Hands pass; it carries follow-ups `deep-orbit-placement-test` and `gravity-subtract-test-residuals`. Between it and grid-lowering the drawn grid lags (B23). The operator will fly the funnel before ruling on it again (`funnel-kept-until-flown`), so potential-owner and grid-lowering must both be on one playable branch before that flight; grid-lowering is stacked on potential-owner, not a sibling (B26), and its map is the next Imagination pass once potential-owner's report lands. Until grid-noise-presentation also lands, that flight still shows the Sector Waves noise as slopes. Not yet mapped:
Mapped and admitted (2026-10-14, base 11b2d7ad): `aetheria-gravity:cut_spec:cut-gravity-subtract.r2` and its sibling `aetheria-gravity:cut_spec:cut-orbits-placed-at-build.r1`, both merged at 5dd1f6b7. Mapped and admitted (2026-10-14, base 5dd1f6b7): `aetheria-gravity:cut_spec:cut-potential-owner.r3` (rebased on 3f5cef68 on 2026-10-15, after rulings `funnel-kept-until-flown` and `core-inside-roche-curve`), one Hands pass; it carries follow-ups `deep-orbit-placement-test` and `gravity-subtract-test-residuals`. Between it and grid-lowering the drawn grid lags (B23). The operator will fly the funnel before ruling on it again (`funnel-kept-until-flown`), so potential-owner and grid-lowering must both be on one playable branch before that flight; grid-lowering is stacked on potential-owner, not a sibling (B26), and its map is the next Imagination pass once potential-owner's report lands. Mapped and admitted (2026-10-15, base dd06f39d, stacked on potential-owner's head): `aetheria-gravity:cut_spec:cut-grid-lowering.r1`, one Hands pass, Starfire-verified; on its branch the drawn grid is the field (B30) and the Sector Waves noise is off the grid until grid-noise-presentation. Not yet mapped:
- **potential-owner** (mapped, above): one ServerShared potential (c from an authored compressed curve G·m^p, authored core-radius curve, uniform-disc core, log tail, superposition, analytic gradient, no gate, global K in PlanetSettings, reference zero at the root's zone radius); every CPU force reader (Ship, floating bodies, clouds, mines) and `Zone.Flow`'s slope term read it; GetHeight = φ/K; deletes `GravityAcceleration`, `GravityReferenceRate`, `GetNormal` as a force source, the PowerPulse well and `GravityDepthExponent`; field strength per field-strength-ruled. Waits on both admitted cuts. May split into the potential and its readers if one Hands pass cannot hold both.
- **orbits-from-field**: Orbit.Period = 2πd/sqrt(c_parent) for the orbit constructor, asteroid poses (Zone.cs:822) and BeltTargets.cs:63; `OrbitPeriod` deleted. Waits on potential-owner.
- **children-within-reach**: ZoneGenerator places children inside a fraction of the parent's tidal radius and the core radius inside the innermost child (softening-radius-ruled); tested over generated zones. It reads `Planet.RocheLimit` (lower bound) and half the tidal radius (upper bound); the windows per class are in B25, and a body with no window (4-7% of small bodies) gets no children. Waits on orbits-from-field.
- **grid-lowering** (stacked on potential-owner, B26): the renderer draws φ/K from the sim's law (analytic or generated brushes) with a parity check; deletes the Gravity Well brush constants and the wormhole gravity brushes; gravity samplers (slopeFlow, stardust, slime, minimap) read gravity only; closes `aetheria-fauna:follow_up:flow-slope-gradient-parity`. Starfire-verified. Waits on potential-owner.
- **grid-noise-presentation**: the Sector Waves noise and fine-grid detail move to non-slope presentation (grid-gravity-only-ruled). Waits on grid-lowering.
- **grid-noise-presentation**: the Sector Waves noise (after grid-lowering on layer 10, drawn only into the nebula patch height) and fine-grid detail move to non-slope presentation (grid-gravity-only-ruled); it also owns the Main Menu's own Surface Displacement brushes (B29, follow-up `menu-grid-brushes`). Waits on grid-lowering.
- **top-speed-field**: top speed becomes a hull field; no drag below it, drag on the excess above it; the VelocityLimit clamp (Behaviors/VelocityLimit.cs) and always-on hull drag (Ship.cs:127-129) go; AI pilots brake actively; open: drag shape above the cap, consumables as modifiers, whether loot and wreckage come to rest (aetheria-gravity:ruling:no-drag-below-top-speed, target invariant orbits-do-not-decay). Waits on potential-owner and orbits-from-field.