102 KiB
Aetheria gravity: map
Target: aetheria-gravity:target:r4 (rationale in docs/aetheria-gravity-target.md). Rulings in force: aetheria-gravity:ruling:grid-shows-how-you-move; from other campaigns, aetheria-release:ruling:gravity-honest-wells-agreed, aetheria-fauna:ruling:ink-pillars-are-suns, aetheria-fauna:ruling:sim-knows-the-flow, aetheria-release:ruling:liquid-grid-from-sim-brushes. Prior art: docs/research/2026-10-14-gravity-prior-art.md. Orbit measurements: docs/research/2026-10-13-orbit-probe/report.md.
Body facts
All read at c6519383 in the detached worktree C:\aeth-wt\ag2-docs on 2026-10-14 unless marked. Scripts live in the Imagination scratchpad (imagination-ag2-gravity-0b/).
-
B1. Sim gravity today.
Zone.GetHeight(Zone.cs:631-658) sums the zone bowl-PowerPulse(|p|/(2·Pack.Radius), ZoneDepthExponent)·ZoneDepth, one PowerPulse well perPlanetInstancesentry (radiusGravityWellRadius, depthGravityWellDepth, exponentBodyData.GravityDepthExponent), and for everyGasGiant(suns included) aRadialWavesripple in zone time.GetNormal(:725-734) differences GetHeight at ±0.1;GetForce(:692-696) andShip.Update(Ship.cs:133-135) feed the normal's xz toGravityAcceleration(:684-689),S·R·(1/(1-|n|²)-1)gated off at|n|² <= .001. Wells readbody.Orbit.Position, which is float2.zero until the firstZone.Update(follow-upzone-orbits-unplaced-before-first-tick). -
B2. The bowl and the ripples are live in code. The operator's ruling says the zone bowl is disabled and stays so, ripples likewise. At
c6519383neither is disabled:Settings.asset(the only asset carrying PlanetSettings) hasZoneDepth: 64,ZoneDepthExponent: 2,WaveDepth 0.5·m^0.3; GetHeight applies both unconditionally; the bowl is felt only in zones with radius under about 3117 (the force gate, probe F3) and the ripples are felt around every gas giant and sun (8 to 32% of peak force, probe wave table). The drawn grid shows both: the sceneBoundaryobject (PowerBrush, layer Displacement, active) is driven each frame byZoneRenderer(ZoneRenderer.cs:621-622) with ZoneDepth plus ZoneBoundaryFog (0), and theGravity Wavesobjects inGas Giant.prefabandSun.prefab(layer FineDisplacement, active) carry the ripple. Soflatspace-no-bowl-no-ripplesis delivered by deletion in a cut, not already true. -
B3. The drawn grid is a second height owner. It is not sampled from
Zone.GetHeight. CameraSurface Displacement(ARPG.unity, culling layers 9 Displacement and 10 FineDisplacement) renders additive brush quads intoAssets/Resources/Fog Surface Displacement.renderTexture, whichVolumeSampling.cs:63publishes as the global_NebulaSurfaceHeight. Brushes on those layers:object (where) layer material and shader sim counterpart Gravity Well (Planet, Gas Giant, Sun prefabs) 9 Gravity Well, PowerBrush, _Power 16fixed in the material,_Depthset fromGravityWellDepth, quad scaled byGravityWellRadius(ZoneRenderer.cs:500-503)the well in GetHeight, but the exponent is the material's 16, not BodyData.GravityDepthExponent, and the shader's envelope addssaturate(abs(x))-.001,min(_Cutoff, …)and asmoothstep(1,.95)rim fade the sim lacksGravity Waves (Gas Giant, Sun) 10 Gravity Waves, RadialWaveBrush, _Power 8,_Cutoff .5the ripple in GetHeight Boundary (ARPG scene) 9 Sector Boundary, PowerBrush, _Powerand_Depthdriven by ZoneRendererthe zone bowl Waves Small / Medium / Large (ARPG scene) 10 Sector Waves *, SimplexBrush, depths -16.75, 7.58, 2 none: drawn, never felt Gravity Well, Gravity Pit, Gravity Waves (Wormhole 1 prefab) 9 Wormhole Gravity 1 ( _Depth 54,_Power 4), Wormhole Gravity (_Depth 2000,_Power 64,_Cutoff .15), Wormhole Wavesnone: the sim has no wormhole well Gravity Well (PlanetOutpost station prefab) 9 Gravity Well none found; whether this prefab is instantiated in play was not checked Other cameras render the same layers into their own textures:
Patch Displacement(layers 9, 10, 12),Map Gravity(9, the minimap;MapView.cs),Slime Gravity Renderer(9;Slime.csbinds it asHeightmap). -
B4. Readers of the drawn height (GPU).
Volumetric.cginc:121-130(gradient of_NebulaSurfaceHeight, the shader's slopeFlow and surface normal;:174surface displacement),Asteroid.shader:31(displacement),Stardust.compute:134(gravHeight) (property),Slime.compute(Heightmap), the minimap gravity materials (ZoneRenderer.MapGravityMaterials,_StartDepth/_DepthRangeat :623-628), and the editor verifierFlowFieldParityVerify.cs:162,261. Follow-upaetheria-fauna:follow_up:flow-slope-gradient-parityalready names the slopeFlow mismatch. -
B5. Readers of the sim height and force (CPU). Force:
Ship.Update(Ship.cs:133-135, through GetNormal and GravityAcceleration directly, not GetForce),Zonefloating bodies, clouds and mines (Zone.cs:411, 437, 451 via GetForce). Gradient for flow:Zone.Flow(:700-704). Height for placement:Entity.cs:1849(Position.y of non-ship entities),Agents/States/Combat.cs:114andBehaviors/TurretController.cs:73(predicted target height),Gameplay/ItemPickup.cs:44,Gameplay/MineInstance.cs:59,ZoneRenderer.cs:545(belt bounds) and:581(planet body height, minimap depth range). Ship tilt:Ship.Updatebuilds Rotation from the same normal. Tests:FloatingBodyTests.cs:187-209, 254(pin the gate and the legacy law),LootTests.cs:299-308,MineTests.cs:623-628,FlowFieldTests.cs(zone with bowl as the slope source),FireControlTests.cs:380. Editor smoke:FloatingItemsDrawnSmoke.cs:159. No AI reader predicts gravity. -
B6. Rails.
Orbit.Period = OrbitPeriod.Evaluate(Distance)(Zone.cs:910), withOrbitPeriod= 1·d^1 + 0 in Settings.asset: period is a function of distance only, never of the parent. Other readers of the same curve:Zone.cs:599(an orbit added at run time, phase back-solved),Zone.cs:822(each asteroid of a belt),Targeting/BeltTargets.cs:63(belt target periods).GetOrbitPosition(:465-500) is the closed-form rail; the root orbit has no parent and sits atFixedPosition. Two commented-out period writers remain inZoneGenerator.cs:589, 650. -
B7. Body well parameters.
Planet.CalculateProperties(Zone.cs:755-760):BodyRadius,GravityWellRadius,GravityWellDepthfrom PlanetSettings curves times per-body multipliers;GasGiantadds wave depth, radius, speed. The generator never writes any multiplier orGravityDepthExponent(no writer in ServerShared).GasGiantData's wave fields reuse the JSON namesgravRadiusMul,depthMulanddepthExp(twice:WaveDepthExponentandWaveSpeedMultiplier) thatBodyDataalready uses for its gravity fields; MessagePack keys differ (14-17 against 6-8), so the binary store is unambiguous and the JSON names are not.WaveDepthExponentis read by nothing (the sim hard-codes 8). -
B8. Ship scale, measured headless from
GameData/Aetheria.cc(CultCache Python runtime,hulls.py,mass.py). Hull Drag: Longinus 0.1/s, Smoke Test Hull 0.1, Djinni 0.2, stations and turret 0. Drag is linear:Velocity = normalize(v)·v·exp(-Drag·dt)(Ship.cs:128-129;CultMath.decay). Thrust ranges: Small Drive 60-90k, Medium 100-300k, Large 200-300k, Rugged 75-250k, Burn and High-Thrust 250k-1M, Racing 200k-1.5M.Entity.Massstarts at hull mass and changes as items are added and removed (Entity.cs:1251, 1407). Items weigh about 40-50 per schematic cell (median over gear, weapons, cargo bays); the Longinus schematic has 66 cells, the Djinni 166. So a Longinus with one Small Drive accelerates at 24-36 u/s² empty and about 11-16 u/s² fully packed (2500 + ~3000); a Djinni 6-9 empty, about 3.4-5 packed. "Fully packed" is an estimate from cell count times median density, not a generated loadout (LoadoutGenerator was not run). Agent TopSpeed defaults to 100 (VelocityLimit). -
B9. What drag means for orbits. Holding a circular orbit at speed v costs a tangential thrust of Drag·v against drag, plus whatever radial thrust the field mismatch needs. At v = 6.3 (today's rail speed) that is 0.63 u/s² for a Longinus (2-6% of its range), 1.26 for a Djinni (14-37%); at v = 17.5 it is 1.75 (5-16%) and 3.5 (39-100%). No free orbit exists for any hull with Drag > 0; with honest wells a ship in orbit needs only the drag term, where today it also fights a radial mismatch of 17-30% of thrust (probe F2).
-
B10. Tide under superposed 1/r tails (computed,
hill.py, over the probe's bodies). 2D tidal radius of a parent at R from its own parent: d_H = R·sqrt(c_p/(2c_g)). Fraction of the 17,864 children of non-root parents that orbit inside 0.5·d_H: 11% if c ∝ mass, 35% for m^0.5, 59% for m^0.25, 66% for m^0.175; children of planets 1%, 10%, 30%, 38%. Speeds: if a median sun's field is 0.5 u/s² at 610 u, v_circ = 17.5 u/s around it and 12.4 around a median planet (m^0.175). -
B11. The flow has curl.
FlowField.Slope(FlowField.cs:53-59) adds a swirl term, the downhill direction rotated 90 degrees, andGlobalis a noise field: neither is a gradient, so no height can show them as slopes. No ship readsZone.Flow; it drives stardust and nebula (shader) and, by ruling, fauna. -
B12. A ruling in force contradicts the target.
aetheria-release:ruling:one-gravity-law(Defaulted, 2026-10-07) fixes the slope-squared law and "the feel ships have today". The target replaces both. Superseded by Self on 2026-10-14. -
B13. Removing a persisted member is compatible drift. CultCache resolves a persisted schema against the local one slot by slot (
CultLib/src/GameCult.Caching/CultCache.cs,CompareSchemaShapes, read 2026-10-14): a persisted slot the local type no longer has is listed inIgnoredExtraSlotswith a warning, not an error. So deletingGasGiantDatakeys 14-17 (the wave fields) loads old run caches with acontent_hash_driftwarning. The shipped catalogGameData/Aetheria.ccholds noaetheria.gasgiantdatadocuments (B8's type listing); generated zone packs in run caches do. -
B14. The zone bowl is exactly one well at the origin. Bowl:
-PowerPulse(r/(2·Pack.Radius), ZoneDepthExponent)·ZoneDepth. Well:-PowerPulse(r/GravityWellRadius, GravityDepthExponent)·GravityWellDepth, zero at and beyond half the radius either way (PowerPulse doubles then clamps). APlanetDataon a parentless orbit atFixedPositionzero, withGravityRadiusconstant 2·Pack.Radius,GravityDepthconstant ZoneDepth andGravityDepthExponent= ZoneDepthExponent, gives the same height and force at every point. The five test fixtures that dig a bowl to get a slope (FloatingBodyTests.cs:233, LootTests.cs:289, MineTests.cs:121, FlowFieldTests.cs:49-51, FireControlTests.cs:380-381) can therefore keep every expected number. Such a fixture must also setBodyRadius,GravityRadius,GravityDepthandOrbitPeriod(null curves throw inPlanet.CalculatePropertiesand theOrbitconstructor). -
B15. Where the drawn bowl and ripples live in Unity YAML (scan
yamlgo.py).Gas Giant.prefab: GameObjectGravity Waveslines 223-240, Transform 241-255, MeshFilter 256-263, MeshRenderer 264-305, parent's child entry line 34,GasGiantObject.GravityWavesreference line 56.Sun.prefab: GameObject 60-77, Transform 78-92, MeshFilter 93-100, MeshRenderer 101-142, child entry 34, reference 57.ARPG.unity: GameObjectBoundary22642-22659, Transform 22660-22674, MeshRenderer 22675-22723, MeshFilter 22724-22731, child entry 16867 (parent transform 1037490739),ZoneRenderer.SectorBoundaryBrushreference 16325. Neither prefab is a variant (no PrefabInstance documents). -
B16. Unity-side code compiles only on Starfire. The Yggdrasil verify job builds and tests
Aetheria.Shared.Tests(ServerShared);ZoneRenderer.cs,GasGiantObject.cs,GameSettings.cs, scenes and prefabs are compiled and loaded only by the Unity editor.Assets/Scripts/Editor/FloatingItemsDrawnSmoke.csopensAssets/Scenes/ARPG.unity(line 238) in batchmode and exits nonzero on failure, so it is the cheapest check that the scene still loads and the renderer compiles. -
B17. Dead orbit writers.
Zone.AddOrbit(Zone.cs:269-272) andZone.CreateOrbit(:594-611, the run-time orbit that back-solves its phase fromOrbitPeriod) have no caller under Assets/Scripts or tests. Every live orbit comes fromZonePack.Orbitsin the constructor (:157-160).GameSettings.MinimapZoneGravityRange(GameSettings.cs:18, Settings.asset line 23) feeds only the bowl's minimap start depth (ZoneRenderer.cs:623). -
B18. Settings.asset is an LFS object. At
11b2d7adits git blob is a 3-line LFS pointer; the smudged worktree file is the YAML read in B2.tools/context-pack.pyreads base blobs, so a spec cannot line-anchor it (cut-gravity-subtract r1 was superseded by r2 for this); edits to it are named by key, and verify jobs copy the edited file in after LFS staging. -
B19. The calibrated strength (computed,
probe1.pyin the Imagination scratchpadimagination-ag2-potential-owner/, over the orbit probe's 31,640 bodies at 5dd1f6b7's generator). Median sun mass 14152 (n 4187); median distance of a planet from its sun 609.8 (n 6112). field-strength-ruled gives c_sun = 0.5 x 609.8 = 304.9 u²/s² (v = 17.46 u/s). With c = G·m^p and p = 0.2 (the middle of the ruled 0.175-0.25): G = 45.08; c = 88.5 (median planetoid, 29.2), 138.5 (planet, 273.9), 211.3 (gas giant, 2260), 304.9 (sun); circular speeds 9.4, 11.8, 14.5, 17.5 u/s. p = 0.175 gives G 57.25, p = 0.25 gives G 27.96. A zone's bodies sum to c ≈ 2760 (median; p90 4210) and the root holds 18% of it (p10 13%). -
B20. The core radius at the first default (superseded by B25 under core-inside-roche-curve; kept because it is why the core shrank). softening-radius-ruled defaulted the core to today's slope-peak radius: the peak slope of PowerPulse(x, 16) lies at x = 1/sqrt(31) of the half-support, so R = (500·m^0.25 / 2)/sqrt(31) = 44.9·m^0.25 (planetoid 104, planet 183, gas giant 310, sun 490). Peak acceleration c/R: 0.85, 0.76, 0.68, 0.62 u/s², so with p < 0.25 a small body's core is the steeper one. Innermost child (belt inner edges included) against 44.9·m^0.25 (
probe2.py): inside the default core for 78% of suns' families, 67% of planets', 83% of gas giants', 73% of planetoids'; 9-21% of parents have a child inside 3·m^0.25 (the body radius). The bound "the core sits inside the innermost child" therefore cannot hold for today's generator under any useful core curve; it is enforced by children-within-reach, which places children. That cut also meets a window problem: a median planet at 610 u from a median sun has a tidal radius d_H = 610·sqrt(138.5/(2·304.9)) = 291, so half of it (145) is inside the planet's default core (183) and no moon can satisfy both rules until one curve moves. -
B21. Summed tails make a funnel (
probe3.py,probe4.py,probe6.py; random phases, 300 zones per radius). At p = 0.2 a ship at mid-zone feels 1.63 u/s² (zone radius 1000), 1.39 (2125), 1.02 (4182), 0.76 (6846), 0.59 (10000), about 90% of it toward the middle; at the zone edge 1.15 to 0.41. Beside a sun's planet the total field is 2.9x the sun's own 0.5 (median; p90 6.0). Alternatives probed at p = 0.175 (mid-zone inward pull 1.10): subtracting a uniform disc of the zone's total c over the zone radius, 0.69; capping each tail with c·L/r² beyond L = 100·m^0.25, 0.55 (and 300·m^0.25, 0.87); p = 0.5, 0.80; p = 1, 1.36 (the heaviest suns dominate). Questionaetheria-gravity:question:superposed-wells-funnelcarries the fork. -
B22. Heights, reference and K (
probe3.py). φ_i(r) = c·r²/(2R²) inside R, c·(ln(r/R) + 1/2) beyond; h = Σ(φ_i(r_i) - φ_i(Z))/K with Z = Pack.Radius, a constant per body, so heights never shift as bodies move and are finite everywhere (the core removes the log's singularity; nothing grows faster than ln r). At K = 5 a body's own well from Z = 4182 to its centre is 74 (planetoid), 101, 131, 161 (sun) deep, the depths today's brushes draw (54, 80, 116, 160); the steepest drawn slope of a lone well is c/(R·K) = 0.13-0.17. The summed field draws the zone as a cone: deepest point -264, -503, -745, -944, -1106 for zone radii 1000 to 10000, mid-zone -136 to -473, edge 0. K is one authored number; only heights and ship tilt read it in this cut. These numbers use the first core (44.9·m^0.25); B25 gives them for the shrunk core and K = 10. -
B23. The drawn grid lags one cut. The potential cut changes
GetHeight; the Gravity Well brushes still draw PowerPulse wells fromGravityWellRadius/Depthuntil grid-lowering. So between the two cuts every placement reader (B5) puts ships, loot, mines and planet bodies at φ/K, up to hundreds of units below the drawn grid in the middle of a zone. Brush depths cannot copy φ/K: a quad per body cannot draw the summed tails. -
B24. Readers at
5dd1f6b7.GravityStrength: Settings.cs:24, Settings.asset (2), Ship.cs:135, Zone.cs:667 and five test fixtures.GetNormal: Ship.cs:133 (force and tilt) and Zone.Flow (Zone.cs:674);FlowField.Slopenormalises its input, so any vector along the downhill serves.GravityAcceleration: Ship, Zone.GetForce and FloatingBodyTests.GravityDepthExponent: ZoneData.cs:97, Zone.cs:626, WellFixture, GravitySubtractTests. Forty-eight test files build aPlanetSettings; only the ones with bodies evaluate its curves, so new curves default tonew ExponentialCurve()(c = 0). Unity-side code reads none of the deleted names (no Editor or Zone Display reader), so the Starfire smoke is a load check for Settings.asset only. -
B25. Roche limit, shrunk core and the moon window (
probe7.py,probe3.py; rulingcore-inside-roche-curve, G as B19 underfunnel-kept-until-flown). Choice:RocheLimit= 7.32·m^0.25, which is the classical fluid Roche limit 2.44 x the body radius (BodyRadius = 3·m^0.25, equal densities);CoreRadius= 3·m^0.25, the body's own radius, so the well is a uniform body (force linear inside, peak at the surface) and core/Roche = 0.41 at every mass. Window for a body's children, [its Roche limit, half its tidal radius d_H = D·sqrt(c/(2c_parent))] (the root's upper bound is the zone radius):body class n window exists hi/lo p10, p50 window of the median body planetoid 10878 93% 1.12, 2.74 [19, 52] planet 8121 94% 1.21, 3.81 [27, 105] gas giant 3979 96% 1.46, 6.39 [41, 264] sun 4187 100% 5.37, 14.2 [71, 1000] 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².
-
B31. Core at the Roche limit, K = 4.098 (ruling
core-at-roche-and-exaggeration-up, after the operator flew the stacked branch on 2026-10-15; landed in grid-lowering h4,8f491fd3). Each well's core radius is its Roche limit (7.32·m^0.25); there is no separate core curve. K = 10 / (7.32/3) = 4.0983607, so the peak drawn slope c/(R·K) at the Roche radius equals the earlier c/(3·m^0.25 · 10), and every slope outside the cores draws 2.44x steeper than at K = 10 (pinned by ThePeakDrawnSlopeStaysAndTheFunnelDrawsSteeper; TheCoreRadiusIsTheRocheCurve locates the core). This supersedes the core and K numbers in B22, B25 and B28; depths scale by about 2.44 (median cone about -1160 deep), still within R32 precision. Children orbit outside the Roche limit, so they orbit outside the core. GravityFieldParityVerify on8f491fd3: PASS, max error 0.0106 u, max gradient angle 0.03°. -
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/Depthin Zone.cs, BodyData keys 6-7 in ZoneData.cs,GravityRadius/GravityDepthin 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 targetFog Surface Displacement.renderTextureis 2048² R32_SFloat (GraphicsFormat 49), so φ/K's range (to about -700) keeps full precision; brushes blendOne 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:62today) 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(scanp2.py,p4.pyin the Imagination scratchpadimagination-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 GridCameraand aFogCamera)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.cgincgravGradient(slopeFlow, nebula surface normal),density(nebula floor), Stardust.compute:134gravHeight, 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 onlySlime 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 byGravityWellRadius,_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.pyoverdocs/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 inGameData/Aetheria.cc, so it is never instantiated;Assets/Prefabs/RTS/Planet.prefab(a Gravity Well child bound to an RTS script'sGravityMesh, 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.shaderandRadialWaveBrush.shaderhave no other material.PowerBrush.shaderstays: thirteen other materials use it.ZoneRenderer.WormholeInstances'gravitymember 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). -
B32. The ship is drawn ahead in xz but not in y (source read at
8f491fd3, 2026-10-15, after the operator's flight: "the new jitter is showing up when moving up a steep slope, not necessarily on those texel boundaries"). The sim steps at a fixed 60 steps per real second (ActionGameManager.Clock = new SimClock(1f / 60f), ActionGameManager.cs:251;Clock.Advanceruns whole steps, :1378).Entity.UpdatesetsPosition.y = Zone.GetHeight(Position.xz) + hullData.GridOffsetonce per step (Entity.cs:1893). Presenters drawLeadsim seconds past the last step (SimClock.Lead, 0 <= Lead < Step);DrawAhead.Position(Entity, lead)(DrawAhead.cs:17-20) addsVelocity * leadto x and z and leaves y at the last step's height, andEntityInstancewrites that to its transform every frame (EntityInstance.cs:405-406). The follow camera'sFollowis that transform and itsLookAtisLookAtPoint, placed from the same transform (ActionGameManager.cs:1071-1072, EntityInstance.cs:396; CinemachineBrain update method 2, LateUpdate, ARPG.unity:30782). So on a slope the drawn ship and both camera targets move smoothly across and in steps up: each step's y jump is slope x speed x Step, and a frame that runs 0, 1 or 2 steps shows 0, 1 or 2 jumps. At 100 u/s on a drawn slope of 0.5 that is 0.83 u per step against a 70 u camera distance; at K = 4.098 every slope outside the cores is 2.44x what it was at K = 10 (B31), so the steps are 2.44x taller.SimClockTests.DrawAheadIsTheStateAtZeroLeadpins the defect (Assert.Equal(player.Position.y, ahead.y), SimClockTests.cs:267), vacuously true in its flat duel arena. Everything else on screen (grid, nebula, stardust, planets) is drawn at the last step's state, so it is the camera that steps, which is why the whole view jitters; why planets look worst is not established by source read (candidates: the LookAt composer turns the camera with each step and planets are the large sharp-edged objects far below; or their own heights). Not reproduced in Unity. -
B33. The nebula patch draws gravity a second time, in half floats at 32 u. Patch Displacement (ARPG.unity camera 507065483,
m_Bits: 5632= layers 9, 10, 12, line 8040) renders the Gravity Field quad (layer 9) intoFog Patch Displacement.renderTexture, 256²m_ColorFormat: 45(R16_SFloat), over the same 2·ViewDistance window as Surface Displacement, so 32 u texels. Its one reader iscloudDensity(Volumetric.cginc:95-104):patchDensity = saturate((-abs(pos.y + patchDisp - _NebulaFloorOffset) + patch) / _NebulaPatchBlend) * _NebulaPatchDensity, withPatchBlend: 25in the default environment (Settings.asset:378). A half float's step is 0.5 u from depth 512, 1 u from 1024, 2 u from 2048 and 4 u from 4096; at K = 4.098 the median cone is about -1160 and the worst about -4500 (B31), so the patch surface is terraced in 1-4 u steps, 4-16% of the 25 u blend, where at K = 10 (median -476, B28) the steps were 0.25-0.5 u. The patch's caller already holds the full-precision gravity height:densitysamples_NebulaSurfaceHeight(R32, 4 u) at the same uv and passes it in assurfaceDisp(Volumetric.cginc:174, 224). No other code reads_NebulaPatchHeight(Stardust.cs:31holds the texture and never binds it). Shader precision model is platform default (ProjectSettings.asset:263), sohalf4(uv, ...)is full float on desktop and not a cause. Not confirmed in Unity that the operator's bands are these terraces rather than the 32 u bilinear facets of the same texture; the cut below removes both. -
B34. The fog cameras are already texel-snapped.
FogRenderers(parent of Surface Displacement, Patch Displacement and Patch) carriesCopyTransformwithPixelSnapCamera= the Patch camera (512²) andPixelSnapIncrement 4, so it follows Main Camera in xz snapped toorthographicSize * 4 / 512= 32 u (CopyTransform.cs, ARPG.unity FogRenderers), a whole number of texels of every fog target (8 of Surface Displacement, 1 of Patch Displacement, 2 of Fog Patch). Execution order ZoneRenderer 50, CopyTransform 51, VolumeSampling 52 (their .cs.meta), so_GridTransformis set after the snap in the same frame. The texel-boundary jitter the operator saw is older and separate: "I have felt that grid texel jitter before, that only showed up for me when I brought back the liquid grid shader (CatmullGrid object in ARPG scene), couldn't figure out how to get that to match, even when reading the same offsets the marcher uses and applying the translation in-shader." No CatmullGrid object exists in ARPG.unity at8f491fd3; it is follow-upcatmullgrid-texel-jitter. -
B35. Orbit speeds and periods under the field (
o1.py,o2.pyin the Imagination scratchpadimagination-ag2-orbits-from-field/, 2026-10-15, overdocs/research/2026-10-13-orbit-probe/data/bodies.csv; ZoneGenerator is unchanged sincec8d40eaf, 2026-10-07, so the distribution holds at8f491fd3; c = 45.08·m^0.2, Roche limit and core 7.32·m^0.25). Rule probed: a child's rail obeys its parent's own well, a = |GravityField.Acceleration(c_p, R_p, d)|, v = sqrt(a·d), T = 2π·sqrt(d/a); a bodyless parent by the ring law of B37. Circular speed by parent class, p10/p50/p90 (n): sun 16.5/20.2/24.5 u/s (16613, root and non-root suns), gas giant 13.6/14.6/15.5 (6226), planet 10.9/12.2/13.3 (3417), planetoid 10.1/10.5/10.6 (300); bodyless parents: rosette suns 11.2/42.7/68.7 (1338: outer planets of a root rosette enclose several suns), gas giant 8.7/11.7/32.3 (844), planet 6.8/7.7/9.6 (1248), planetoid 6.1/6.8/7.3 (154). Today every rail runs at 2π = 6.28 u/s (T = d). Periods: p10/p50/p90 47/151/621 s against 100/389/2560 s today, a median 2.65x shorter. Station orbits (Lagrange candidates from the middle of each zone's distance order, a proxy forselectedStationOrbits, ZoneGenerator.cs:241): 13.9/16.7/23.5 u/s, max 48.5. Children inside their parent's Roche limit, hence inside its core: 234 of 30,140 (0.8%; planetoids 214, planets 9, belts 11); their period from the core law is 2πR/sqrt(c), finite and independent of d (a solid-body rail), so the orbit stays well defined; children-within-reach moves them out. With no rule for bodyless parents, 3584 children (12%) would have no field and stand still. -
B36. Readers of the period and of orbit velocity at
8f491fd3. Period writers:Orbit's constructor (Zone.cs:849-853,settings.OrbitPeriod.Evaluate(data.Distance)),AsteroidBelt.Poseper asteroid per call (Zone.cs:765),BeltTargets(BeltTargets.cs:63, which must divide by the same float Pose does). Readers ofOrbit.Period:GetOrbitPosition(Zone.cs:484-486, which moves an orbit only when Period > .01) and three tests (ZonePlacedAtBuildTests.cs:113, 137, 143; RunStartTests.cs:325).Zone.CreateOrbitis gone (B17's dead writer was deleted). Orbits are constructed (Zone.cs:159-162) and placed (:167-168) before any body exists (:170-195), andBeltTargetsis built inside the body loop (:181), so a period that reads a parent's well needs the bodies first: construct orbits, build bodies, derive periods, then place and build belt targets. Orbit velocity is a difference of placed positions (Zone.cs:283-287);OrbitalEntity.Updatecopies it to stations and turrets (OrbitalEntity.cs:30-31) and a docked ship copies its parent's (Entity.cs:1891). Docking checks distance only, 25 u (ActionGameManager.cs:896-897,DockingDistance), never relative speed; undocking keeps the station's velocity. AIMoveToOrbitStatechases the orbit's position at TopSpeed (100) with no lead (MoveTo.cs:18-38); turrets lead targets bytarget.Velocity(TurretController.cs:68). So faster rails (B35) break no mechanism: a pilot holds within 25 u of a station moving 14-24 u/s (48 at most), where today it moves 6.3. No Unity-side code readsOrbitPeriodorPeriod(git grep). Tests that setOrbitPeriod: GravitySubtractTests.cs:49, MiningCut1Tests.cs:40, MiningCut2Tests.cs:43, MiningCut3Tests.cs:54, MiningIndexTests.cs:461, PotentialOwnerTests.cs:49, 86, 141, WellFixture.cs:13, ZonePlacedAtBuildTests.cs:45; MiningCut1Tests.cs:100-116 re-implements the curve. Fixtures whose parents carry no well (they move today only because the curve ignores the parent): ZonePlacedAtBuildTests (bodyless root; a bodyless moon under the moon's moon), MiningCut1Tests (a parentless root orbit at Distance 4000 that circles its FixedPosition, a shape the generator never makes: generated roots sit at distance 0), and MiningCut3Tests and MiningIndexTests (belts round a SunData of mass 0 under a PlanetSettings with no WellStrength, so c = 0). c = 4π² gives T = d exactly outside the core, today's game periods. The Lagrange station orbit adds PI/3 to Phase (ZoneGenerator.cs:264), but Phase is in turns (OrbitData.Evaluate, ZoneData.cs:234-237), so a station sits 0.047 turns (17°) from its planet, not at L4 or L5 (follow-uplagrange-phase-in-turns). -
B37. Bodyless parents and the ring law. ZoneGenerator's
ExpandRosette(ZoneGenerator.cs:551-592) leaves the parent node empty and puts n children on one circle at phases i/n; binaries are rosettes of two (:484-487), roots can be rosettes of suns (the probe's 321 empty root suns), and a laterExpandSolaron an empty node adds outer children beyond the ring. In 2D a point of strength c_j at distance s on the same circle pulls a ring member inward by exactly c_j/(2d) (radial part of c_j/s with s² = 2d²(1-cos θ)), so a ring member's rail is v² = ½·Σ_{j≠i} c_j; and by 2D Gauss's law, which holds for log potentials, a ring exerts c_ring/d on anything outside it and nothing on anything inside it. So the field gives every child of an empty node a definite pull: the strength of its siblings strictly inside its distance, each with its whole family (a sibling's moons orbit with it), plus half the strength of siblings on its own circle (within 0.1 u, the generator's own rosette test, ZoneGenerator.cs:227) when the child itself carries strength. The last clause keeps a bodyless companion (a station or turret orbit at a planet's distance) on its planet's period: neither the planet nor its companion counts the other. Probed: no child of a generated zone is left without a field under this rule (B35), and nested binaries (an empty binary member that holds its own binary) get the ring term through their family's strength. -
B38. Parent-only rails against the summed field (
o1.py). By 2D Gauss's law (B37) the phase-averaged pull at a child's distance from a body parent is the parent plus every sibling inside that distance, each with its family; siblings outside pull nothing on average. The circular speed of that enclosed field over the parent-only rail speed (B35), p10/p50/p90/p99, by the child's class: suns (children of a root or rosette) 1.44/1.98/2.44/2.75, gas giants 1.00/1.58/2.14/2.55, planets 1.00/1.21/1.65/2.05, planetoids 1.00/1.00/1.37/1.61, belts 1.00/1.14/1.49/1.88. Enclosed speeds: suns' children 24.4/46.0/60.3 u/s, planets 11.0/21.8/32.3. So under the funnel the operator kept (funnel-kept-until-flown), a ship coasting free at a body's orbital radius, away from the body, needs 1.2-2x the body's rail speed; patched conics' premise, that the parent dominates, is weak where c ∝ m^0.2 (the root holds 18% of a zone's total c, B19). The enclosed rule would also break field-strength-ruled's 12-18 u/s for planets round a median sun. Questionaetheria-gravity:question:rails-enclosed-or-parentcarries the fork; the two options differ by one term in one function. -
B39. Planets are drawn at the step, the camera at the drawn time (source read and Yggdrasil probe at
09ec04a8, 2026-10-15, after the operator's flight: "if I look at the planet while doing that, it jumps back and forth every frame").ZoneRenderer.Updateplaces each planet atZone.GetOrbitPosition(ZoneRenderer.cs:582-585), the cachedOrbit.Positionthe lastZone.Updatewrote (Zone.cs:507-532), with noLead; ships, and through them the follow camera, are drawnLeadpast that step (EntityInstance.cs:405, B32). The sim runs before every presenter in the frame (ActionGameManager executionOrder -10, ZoneRenderer 50), so the gap is one of clocks, not of order. Probe (DrawnPoseProbe.cs, Imagination scratchpadimagination-ag3-planet-jitter/, via ygg-verify): a ship kept 60 u beside a planet moving 20 u/s, sim at 60 steps/s; planet-minus-ship along the motion spans 0.306 u at 144 Hz with 239 reversals in 286 frames (75 Hz 0.27, 120 Hz 0.17 reversing every frame, 165 Hz 0.30, 240 Hz 0.25, 60 Hz 0). Amplitude is the planet's speed x Step, so orbits-from-field (B35) made it 2.65x larger. Evaluating the orbit atExactTime + Leadgives 0.0002 u and no reversals. Also drawn at step time: belts and the belt UI (EvaluateBeltat_time, ZoneRenderer.cs:546-549, 719), the drawn wells (GravityLowering.cs:53), loot and mines (Body.Position, ItemPickup.cs:43-44, MineInstance.cs:58-59), the chunk reticle (ActionGameManager.cs:1184) and the flow clock (VolumeSampling.cs:112); the ship's drawn ride height reads wells at step positions (source-read consequence, unprobed). Stations, clouds and rounds are already drawn ahead.LookAtPointand group aims read the previous frame's drawn transform (EntityInstance.cs:396 before 405-406). Fixed bycut-drawn-pose-at-drawn-time.r1. -
B40. The conserving compact law (
docs/research/gravity-well-law.mdsection 1,b2f6122d; Imagination 2026-10-15). Building block: a band term U(eps, a, b) with pull r/(r^2+eps^2)B(r^2), B = 1 inside a, fading to 0 by b with S(t) = 35t^4-84t^5+70t^6-20t^7, 0 beyond; closed-form potential (one log plus a polynomial), so the drawn grid is exactly the potential. Each orbit-tree node F contributes c(m_F)U(eps_m, L_F, B_F) + c(M_F)[U(eps_M, max(B_F, Lambda_F/2), Lambda_F) - U(eps_M, L_F, B_F)], with m_F the body's mass, M_F the family's, c and eps the existing WellStrength and Roche curves, L_F the outermost child's distance, B_F the family's support, Lambda_F = max(2d_Hill, 1.5B_F) its reach; the root has no reach; a bodyless node's ring group stands in for its body. Inside L_F the node pulls exactly as its body; beyond B_F exactly as one body of mass M_F; beyond Lambda_F not at all. Checks: F = dphi/dr to 1.6e-9; derivative jumps of order 0-4 are zero at 50 digits at band edges (C4), C-infinity elsewhere; Plummer core (no C1 seam); 0 containment violations in 70,890 term/ancestor pairs; float32 height error about 2e-3 u. The family's centre is its body, not its centre of mass (exact merge-in-slot, nothing a test sees is lost). Plummer's peak pull is 0.5c/eps against the harmonic core's 1.0*c/R, so K halves to 2.049 to keep core-at-roche-and-exaggeration-up's peak drawn slope; slopes outside the cores draw 2x steeper while the tamed funnel pulls about 2.6x less. -
B41. Probe under the conserving compact law (
gravity-well-law/probe3-output.txt, 6 draws, same data as B35/B38). Reach 2*d_Hill (w3): ratio p50 Sun/GasGiant/Planet/Planetoid/Belt 1.09/1.00/0.97/0.97/0.98 (p90 1.15-1.32) against today 2.00/1.63/1.24/1.10/1.16; far field exactly 1.00x c(M_zone) at 2Z and 10Z (today 5.54x); deepest point 1.01x one body of the zone's mass (today 3.43x, p90 4.83); split invariance outside the family for 2382/2427 families inside the ruled generator window (4573/6976 under today's generator, which places most moons outside it, B25); mid-zone pull 0.60 at Z 1000; planets round a median sun 17.2 u/s, 99% in 12-18; suns' children keep 23.6 u/s rails. 234 children get under half the log pull: the same 234 inside their parent's Roche limit. -
B42. Evaluation cost of the tree walk. 30 terms per zone; a sim sample evaluates 4 terms p50 (p99 15) after 10 reach tests; a shader texel 3 (p99 14); today every sample evaluates all 17 wells p50 (p99 58). Skipping is exact because nothing outside a reach pulls; camera-window culling is optional.
-
B43. Consequences the well-law cut carries or names. The ring law becomes partners' own terms at their chord (a binary member feels c(M_partner)*2D/(4D^2+eps^2)); a body orbiting a binary from outside feels c(M_pair), not the sum of strengths; strengthless companions on a ring keep the mean-pull rule until children-within-reach deletes it; children-within-reach reads the law's reach and support instead of respelling the tidal radius, and places circumbinary children beyond 5x the ring radius. Knobs (smoothstep order, band 1.5, fade from half the reach, ring reach 4D) were chosen once, not swept.
-
B44. Hull contact and layers (orbital-layering, merged
949fa005; verdicts cut-orbital-layering.s1/s2). Contacts.Step paired every active unparented Entity by planar disc, so stations (r 14.4) and turrets (r 8.9) blocked ships; it now pairs ships only (ship bounce kept, ruling ship-contact-stays-keep). Vertical placement had four owners (per-hull GridOffset, prefab GridOffset, planet +2R, AsteroidVerticalOffset); one settings owner now sets ships 3, orbitals 15, floaters 0 (key 26 retired); planets keep their size lift; asteroids move to the floater layer in follow-up asteroid-floater-layer-cut. -
B45. Station and turret placement (turret-placement, merged
949fa005; verdicts cut-turret-placement.s1/s2). PlaceTurrets' t/2 integer division put every turret at distance 0 from its station; CreateLagrangeOrbit added pi/3 to a Phase in turns (stations 17 deg from their planet). Now stations sit at 1/6 or 5/6 turn and turrets flank by cumulative reach (min gap 8.74 u over 1024 zones). Orbit-choice residuals in follow-up station-orbit-choice-residuals. -
B46. Stardust culling (stardust-bounds, merged
fdecde02; verdicts cut-stardust-bounds.s1/s2). The bounded Graphics.DrawProcedural was culled against a hand-set 2048-high box, and no overload opts out; the draw is now one CommandBuffer on TargetCamera at CameraEvent.BeforeForwardAlpha. Both target cameras are deferred, where AfterForwardOpaque never runs (it drew 0 pixels); BeforeImageEffects and AfterEverything also draw. -
B47. The ambient's edge (ambient-follows-populated-area, merged
6865af81; verdicts .s1/.s2; ruling dark-beyond-populated-area). The only tint in the outer zone was the Ambient Fog Light quad (PowerBrush power 2) sized to exactly Pack.Radius, so the tint map was zero beyond it while the field kept rising into a rim wall: the circular light edge. Sun brushes (power 16 over LightRadius/2) are under 1% by half their radius. Now Zone.PopulatedRadius (max chain reach of bodies and belts + Roche) sets a Gaussian fade, edge 0.12 of centre, quad sized to the B10G11R11 format floor 2^-15 (last step <= 1.5 floors). The dead second marcher (VolumeRender shaders, Fog Marching materials) was deleted (dead-fog-marcher, merged949fa005). -
B48. Law and generator together (
docs/research/gravity-well-law-r3.md, merged with its minimap renders; question tail-taming-law-r3). Recommended C4: the band law with reach 1/2 D, plus generator rules (child at least max(1.5 x parent Roche, 3 eps_c c_p/c_c), at most D_parent/6, sibling spacing D_(i+1) >= D_i/(1 - lambda/2), unfit children folded into the parent's body) keeping 20,768 of 31,640 nodes; rail honesty 0.90-1.10, planetoid cusps lost 4/4894 (today 805/10127), far field exactly c(M_zone). The closed-form band potential loses float32 precision for tau > 1; the law needs a series branch there. Blob share stays >= 0.49 under every candidate because the minimap spaces 21 contours linearly over the deepest body (IsolinesUI.shader:178): a presentation fix for grid-noise-presentation. Dead or derived generator variables: PlanetSafetyRadius, the neighbour-based ChildDistanceMaximum, distMul as spacing owner, the rosette child windows. -
B49. Stardust against the nebula march (probe 2026-10-16 by imagination-ag4-stardust-order at
6b6e7c7c, Unity 6000.3.24f1 batchmode D3D11, ARPG.unity Main Camera, deferred HDR; every camera MonoBehaviour disabled, an [ImageEffectOpaque, ExecuteAlways] snapshot added that copies its source and _CameraDepthTexture; an Unlit/Color quad 50 u ahead drawn by a CommandBuffer at one event per render; log in the session scratchpad imagination-ag4-stardust-order/probe2.log). The nebula is VolumeCloudRenderer, an [ImageEffectOpaque] OnRenderImage: it marches to _CameraDepthTexture and composites mcol*(1-a)+cloud (CloudShader.shader:161, :361). Seen by the opaque effect (so fogged): BeforeLighting, AfterLighting, BeforeImageEffectsOpaque. Drawn but after it (so on top of the fog): AfterImageEffectsOpaque, BeforeForwardAlpha, which is where1973b6d2put the dust. Not run with no forward-opaque object or skybox: BeforeForwardOpaque, AfterForwardOpaque, BeforeSkybox, AfterSkybox. BeforeImageEffectsOpaque ran with every image effect disabled. Only BeforeGBuffer and AfterGBuffer depth writes reach _CameraDepthTexture (a deferred camera copies it from the G-buffer depth), and a single-target colour draw there lands in albedo and is unlit. So the dust has never been in the depth the march reads: before the cut (a queue-2000 renderer drawn forward after lighting) the march ran past it and laid the whole ray's fog over it, as it will again at BeforeImageEffectsOpaque. Dust in front of a cloud is fogged as if behind it; per-particle depth occlusion would need a G-buffer pass writing emission to the lighting target. -
B50. Pockets: drawn extent and nesting, flown against ruled (
pockets.py, imagination-ag4-children-reach scratchpad, 2026-10-16, over the B35 bodies.csv, 188 of 1500 zones; it reuses r3'sprobe4.pylaw andreplace.pyplacement, so it is a numpy port, not a Unity render). For each body with a body parent, along the line to the parent (the pocket's tight side): r_v is where its outermost closed contour at the minimap spacing (deepest body / 20, IsolinesUI.shader:178) crosses, r_s the saddle (its basin's edge), both in body radii 3 m^0.25. Flown at6865af81(C0: harmonic core, today's generator): planets r_v p50 2.0 (2.5 for planets with moons), 44% with no closed contour of their own; 4% of planets' children inside the drawn extent and 24% inside the basin (gas giants 22%/55%, suns with a body parent 26%/55%). Well-law alone (C1): planets r_v 5.5, no contour 23%; children inside 11%/33% (gas giants 39%/66%). Well-law plus children-within-reach's rules (C4): planets with moons r_v p50 6.5 (p10 2.0, p90 15.1); children inside the drawn extent 51% and inside the basin 91% (gas giants 65%/90%, suns 83%/91%); 32% of planets, mostly moonless, still draw no closed contour. Reading against the operator's flight (follow-up wells-read-as-pockets): "barely larger than themselves" is measured at C0 (about 2 body radii), and most of a planet's moons then sat outside its basin. Both rules move it; what remains (half of moons inside the basin but outside the outermost drawn contour, moonless planets with no contour) is contour spacing, grid-noise-presentation's. Her screenshot (session image 4.png) is the in-world view with orbit rings, not the minimap, so this is a proxy for what she saw. -
B51. Binary members shear, and C4 merges most binaries (
shear.py,stats.py, same scratchpad and data). At4fb5cb94a binary member's period is its partner's own terms at the chord (WellTree.PullAround), so unequal members on one ring get unequal periods: of 1163 generated binaries 99% have unequal periods (ratio p10/50/90 1.016/1.091/1.236); started opposite, they meet after p50 665 s (p10 171 s), 47% within 10 minutes of play. The log-law equilibrium of a pair is two radii, r_i proportional to c_partner (rosette-relative-equilibria.md section 1), which WellTree's one-radius ring group (RingOf, RingWindow) cannot express. Under C4 (replace.py, all 1500 zones) 905 of 1155 non-root binaries cannot keep their members outside each other's Roche limit inside their node's reach and become one body; 9026 massive nodes and 1658 of 3134 belts fold into their parents; non-root binaries keep no circumbinary child (the bound 7.5 D_ring equals the window's top, D_node/6), and root rosettes keep none (7.5 x 0.375 Z exceeds 0.75 Z). -
B52. Multi-root zones: three shapes for the zone's owner (imagination-ag4-well-law-r3 scratchpad, 2026-10-16:
ImagWellDump.csrun on Yggdrasil at4fb5cb94(job jE) dumps every orbit of 40 production entrance zones, seeds 1-40, with masses, t = 0 positions and belt extents;welltree.pyports WellTree's BuildReaches and EmitTerms in float64;shapes.py,overlap.py). 32 of the 40 zones have 2-6 massive parentless roots, one per occupied area (ZoneGenerator 93). Metrics: far = |F| r / c(M_zone) at 10 Pack.Radius, r from the origin; foreign = |force at a child's rail point with its own subtree removed, less its parent body's Plummer pull| / that pull, for children of bodies, split between the heaviest root's subzone and the others; depth = deepest drawn potential over the bodies / one body of M_zone at the origin (Soul's metric); plain = share of 60 random points inside 0.75 R where |F| < 1e-3 c(M)/R. Today (every root its own global term): far p50 2.21, max 4.16; foreign heavy/light p50 0.49/0.28; depth p50 1.10; plain 0. Isolated subzones (one zone node at the roots' barycentre owns the only global term, c(M_zone)[U_global - U(A, 1.5 A)] with A = max over roots of distance to the node + reach; each root's reach max(0.5 x nearest-root distance, 1.5 x support)): far 1.000 (0.997-1.005 from the origin, exact from the barycentre); foreign 0.41/0.17; depth p50 0.82, max 1.42; plain p50 8%, max 28%. Nested under the heaviest root (the other roots become its children): far 0.92-1.00 from the origin, exact from the heavy root; foreign 0.41/0.20; depth 0.97; plain 0; every light root then sits fixed on the heavy root's slope. Superposed inside (each root reaches past the pack and fades where the zone term rises): inside the pack it is today's field, foreign and depth as today, far exact. Under isolated subzones 73 of 130 roots take the 1.5 B floor rather than half the nearest-root distance, and 210 of 490 ordered root pairs have one root's reach entering the other's support: today's generator gives roots families wider than their areas. A sits at p50 1.43, max 2.70 Pack.Radius. Most of the foreign share in both subzone kinds is siblings and children outside their reach (today's generator), which children-within-reach owns. -
B53. Belt extent (same dump,
belts.py, 155 belts under bodies in 40 zones). With L_F read from the belt orbit's Distance (4fb5cb94), the parent's own pull at a belt's outermost asteroid over its body's Plummer pull is at minimum 0.012 (p01 0.938, 8 belts under 0.99); with L_F read from the outermost asteroid it is 1.000 for every belt. The asteroid spread is the generator's (ZoneGenerator 150: Distance + u (v - 1/2) AsteroidBeltWidth(D)): 42 of 155 belts reach inside their parent's Roche limit and 1 below distance 0. -
B54. The series threshold, measured (
threshold.pyon Soul'sband_check.py: float32 port against 40-digit mpmath quadrature, five (a, b) shapes, 15 radii each). Worst error per unit strength with the series from tau 0.4 (40 terms): 1.0e-6 overall; 3.1e-7 at tau 0.05, 6.1e-7 at 0.3, 4.7e-7 at 0.39 (closed), 9.7e-7 at 0.41, 1.0e-6 at 0.43, 6.5e-7 at 0.6, 4.9e-7 at 1, 2.3e-7 at 2.5, 6.0e-8 at 10. From 0.5: 1.04e-6; 0.6: 1.45e-6; 0.7: 2.63e-6; 1 (4fb5cb94): 1.01e-5 (at tau 1). The closed form is float32-clean only to about tau 0.45 (9.7e-7) and worsens from there (1.5e-6 at 0.6, 1.0e-5 at 1). -
B55. Subzone isolation needs the root window from the reach (imagination-ag4-cwr-refresh scratchpad, 2026-10-17:
ImagWellDumpwith belt masses run on Yggdrasil at09a7f189, jobs jH/jI, production entrance zones seeds 1-130, 106 multi-root, 417 roots;place3.pyports replace.py at C4 plus r3's rules;cwr.pyrebuilds each placed zone withwelltree.py's zone+belt tree and counts Soul's metric from findingcut-well-law.s2.subzones-overlap-in-production). Areas are tangent circles (ZoneGenerator 63-91), so the nearest other root sits about r_i + r_j away and the root's reach, Reach x that distance, is roughly the area radius: a family filling 0.75 r cannot end inside it. Today: 705 of 1456 ordered root pairs have reach_i + support_j > d (worst 4.62), 948 of 4940 orbits inside another root's support, 2496 inside its reach, 246 roots at the 1.5 x support floor; 5461 bodies (4907 wells, 554 belts); 10 belts reach below distance 0, 283 inside 1.5 x their parent's Roche limit. C4 with the 0.75 window (r2): 636 pairs, 261 of 2302 orbits, 2880 bodies (2590 wells, 290 belts). floor-window (a root's support under reach / Beta, window min(0.75 r, reach / 2.25)): 0 pairs (worst 0.83 of d), 0 of 1318 orbits, no floor-bound root, 1927 bodies (1762 wells, 165 belts), 24 of 47 root rings in multi-root zones merged. fade-window (rule 2 for roots, support under Gamma x reach): 0 pairs (worst 0.75), 0 of 985, 1601 bodies (1471 wells, 130 belts), 30 rings merged. Belts as intervals at the authored AsteroidBeltWidth fold almost all (543 of 554 under floor-window): the width (50 + 5 D^0.666) is wider than most slots, so r3 places a belt as a point and clamps its half-width to its slot. Two port details: a parent that absorbs folded children grows its Roche limit, so the lower bound is recomputed after each fold (59 belts sat inside it otherwise); a zone dump over 240 seeds in one Fact ran out of memory. Caveats: the dump is post-generator (children the old generator dropped are missing), positions are at t = 0, area radii are reconstructed from the area rule. -
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
1f83d074with 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 at1d4aa626). 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 atf948992dand26c44020,ZoneGenerator.Placeinstrumented byinstr.patch, the candidate fixfix.patch, constructed fixtureProbeRing.cs;ChildrenWithinReachTests.Zones, all 384 zones of seeds 1-6). Source:Settle's ring branch judgesringDistance >= 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 atf948992d: 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). At26c44020(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 checkingring.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:f948992d2215/0,26c440202224/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) readsWellMass(child)before the child's own folds, and circumbinary children readWellMass(heavy)before heavy settles: the same shape, growing as mass^0.05 (the test's ownCuspTolerancecomment), 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 finalloand siblings placed in the same pass; the circumbinary distance, chord caps and subzone isolation (rootReachfromWellTree.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
ImagProbewithProbeLoginstrumentation ofZoneGenerator.Settle, Yggdrasil jobs j3/j4 at390a5958, 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 isEmptywith its own ring. Settle's ring isc.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,ringis the body alone and the bodyless member goes toPlaceChildrenas a circumbinary child. With two,ring.Count == 0returns, both members keep distance 0, and the queue settles each one with reachReach 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, asAtLeastalready 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 ringDistanceequals 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 atb9e16872). -
B60. Belt shapes, measured (imagination-ag6-belt-shapes scratchpad, 2026-10-18: xunit probe
ImagBeltover every zone of seeds 1-12, 768 zones, at70c7a497, Yggdrasil job j4; prior artdocs/research/2026-10-17-belt-shapes-prior-art.md). Source read at70c7a497: a belt is oneAsteroidBeltDatabody on anOrbitDataof its own, and each rock persists (Distance, Phase, Size, RotationSpeed) about that orbit's parent (ZoneData.cs:96-106, 201-215). Zone derives each rock's period from the rock's own distance,PeriodAround(parent, rock.Distance)(Zone.cs:236-242), and gives the well tree the belt's farthest rock as its rail's extent (Zone.cs:216-230).BeltTargetsbands rocks by distance and keys them by turn (BeltTargets.cs:15-21), so it serves any (Distance, Phase) draw. Nothing in Zone, BeltTargets, DrawAhead or ZoneRenderer reads an annulus as such: the shape lives only in the generator's rock draw (ZoneGenerator.cs:149-160) and Place's slot (636-651). Measured: 921 belts in 485 of the 768 zones (414 of them under a root); rocks per belt p50 73 (p10 33, p90 183); width / D p50 0.19. Hosts, the 7301 massive bodies with a parent: period p50 111 s (p10 52, p90 249); d ln T / d ln r at the host p50 0.92 (p10 0.83, p90 0.98). So a co-orbital rock 1% off its host's distance drifts p50 0.29 turns an hour against it (p90 0.56). On own-distance rails, a Trojan swarm (about 0.07 turn long) with any radial width shears into an annulus within the first hour of zone time. Host reach / D is 0.5 for every non-root body (band law). So L4 and L5, a chord D from the host, lie outside the host's own well. The nearest sibling edge sits at least 0.25 D from any host (p50 0.30), so a swarm of radial half-width under 0.25 D never meets a sibling. 548 of the 7301 hosts already carry a station's bodyless Lagrange orbit (CreateLagrangeOrbit, ZoneGenerator.cs:289-299). Station candidacy refuses a host with any sibling within RingWindow of its distance (252-256), so a swarm on the host's rail would bar the host. Lanes: take the 2545 childless light bodies (mass < BeltMassCeiling) with no belt sibling, and a lane of half-width E = max(support, RocheMargin x Roche) round each (E / D p50 0.19). The lane leaves room on both sides inside the neighbours' spacing and the window for 468 of them, and on one side only for 1257. 820 have room on neither side. The outermost child sits at the window, so its outer side is empty by construction. Edges: a non-root family's outermost extent sits at p50 0.325 of its reach (max 0.333, the window at reach / 3). The fade band from Gamma x reach to the reach is 0.5 of the reach wide in every family, because support never exceeds Gamma x reach. A belt extent fed to the tree beyond the window pushes support past Gamma x reach and narrows that band; at an extent of reach / Beta the band is gone (WellTree.cs:224-229). Sole roots fill out to the 0.75 R window. PopulatedRadius / Pack.Radius is p50 0.88 (min 0.41, max 0.90), and any rock beyond it moves PopulatedRadius (Zone.cs:736-744). -
B61. Trojan swarms, measured and built as a candidate (imagination-ag6-trojans scratchpad, 2026-10-18, at
67517f40, Yggdrasil jobs j2, j5-j8; rulingstrojans-first,trojans-ride-host-period-with-stations). Source read at67517f40(no gravity file differs from70c7a497):AsteroidBeltDataholds keys 9 and 10, so key 11 is free (ZoneData.cs:97-107); CultCache fills a slot a saved record lacks with its default (DefaultedMissingSlots, CultLib CultCache.cs:851-865). A rock's pose reads its own absolute Phase and itsPeriods[i](Zone.cs:889-895), and a rail's position reads its Phase and Period the same way (Zone.cs:564-567), so a rock that shares its host's Period keeps its phase offset from the host forever.WellTree.PullAroundadds a ring member's partners' pull only whenselfis that member (WellTree.cs:410-418), so a rail copied beside a ring member would take another period. Station candidacy reads the GeneratorPlanet tree (ZoneGenerator.cs:252-257), and cache keys are fresh Guids, so a seed sweep compares zones by structure, not keys. Measured over seeds 1-12 (768 zones, probeImagTroj): of the massive bodies with a parent, 560 are ring members; 5035 bodies above PlanetMass have a body for a parent (2914 planets, 1613 giants, 508 suns), p50 6 per zone (p90 13), in 719 of the 768 zones; the host rule takes the 4527 planets and giants. The figures below are over all 5035. Host distance p50 326 (planets) and 529 (giants); host period p50 140 s. A rail copy's period equals its host's bit for bit on 5035 of 5035. Radial room to the nearest sibling edge is at least 0.25 D (p50 0.28). With an arc of 0.07 turn and a half-width of 0.06 D, the nearest swarm point clears the host's own extent by at least 0.44 D (p50 0.60). 509 of the hosts already carry a station's Lagrange orbit. In 96 hosts the outer rock would pass PopulatedRadius, by at most 3.3%. Annulus belts hold p50 56 rocks per D^2 of area (p90 426); a swarm covers 0.053 D^2 per side, so at belt density it would hold 3 rocks, which is why TrojanCount sets the count directly (0.04 D + 8 per side, 21 at D 326, 29 at D 529, near belt p90 density). Rocks per zone today: p50 72, p90 325, max 811; MapBeltPerf measures belts up to 2250 rocks. Candidate (cand.diff in the scratchpad, plus 7 lines of Settings.asset): TrojanProbability 0.15 gives 346 swarms in the 384 zones of seeds 1-6, 51.6 rocks per swarm on average, split evenly between L4 and L5. Every rock's drift against its host over an hour of zone time is at most 1.8e-7 turns. 45 orbital entities sit on a swarm host's rail. With swarms off and on, every other orbit's (Distance, Phase, Period, Reach, Support), every other body and belt rock, every entity count and the height at three points per body are identical in all 384 zones; PopulatedRadius grows in 8. The full suite passes 2411 of 2411 in 2.4 min. Hand mutants on the candidate: each rock on its own distance's period fails the hour test; a swarm's extent at its farthest rock fails the comparison; the main random stream fails the comparison; ring members as hosts fail the hour test.
Model page
One row per kind this campaign touches. "Today" is c6519383; "target" is the shape under r2 with the recommended answers, marked where a question is open.
| kind | identity | lifecycle | authority (one owner per decision; forbidden writers) |
|---|---|---|---|
| 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), and RocheLimit (7.32·m^0.25, the closest a child orbits, which is also the well's core radius: B31, core-at-roche-and-exaggeration-up) in Settings.asset. Owner of the per-body value: Planet.WellStrength and RocheLimit, set at construction (a positive, finite Roche limit 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 pack record keyed by CultRecordKey, parent by CultRecordRef<OrbitData>; root has no parent. Period is derived, never stored or authored. |
OrbitData created by ZoneGenerator only (the run-time writers are deleted); placed at build (zone-valid-before-tick), then each Update. Period derived once at build, after the bodies exist (B36). Asteroids get one period each, held by their belt and read by both Pose and BeltTargets. |
Owner (cut orbits-from-field): Zone.PeriodAround(parent, distance, self) = 2π·sqrt(d/a), a from the parent's own well through GravityField.Acceleration (core included), or for a bodyless parent the ring law (B37); no field, no motion. A moon's period reads only its parent: the hierarchical (patched-conics) approximation, with its weak premise stated in B38 and the fork in question rails-enclosed-or-parent. Forbidden writers: PlanetSettings.OrbitPeriod (deleted), any per-reader period formula (Orbit's constructor, AsteroidBelt.Pose, BeltTargets), the grandparent's or the zone's strength. |
| 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). 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 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).
Belt shapes (2026-10-18, imagination-ag6-belt-shapes, read at 70c7a497; B60; ruling belt-shapes-direction)
| kind | identity | lifecycle | authority (one owner per decision; forbidden writers) |
|---|---|---|---|
Belt (AsteroidBeltData, a field of rocks) |
Body record key minted by the cache; its name is the key's first 8 characters. One OrbitData of its own, whose Parent is the body the rocks orbit. Injective. A belt's shape is not a name or an enum that readers branch on. It is the generator's draw of the belt's rocks, plus at most two rules Zone reads: the period rule and the extent rule (rows below). |
Created once by ZoneGenerator.GenerateZone and persisted in the zone pack; rocks never leave their draw. A changed generator changes new zones, and seeded tests repin. Saved packs keep their rocks, and a rule persisted later reads its default (today's annulus) on an old pack: a trailing MessagePack key is compatible drift (B13). Field kind is chosen at generation or first load (FieldKinds.Ensure). | ZoneGenerator owns which belts exist, each belt's host, slot and lane, and the rock draw: Place sets the bounds, GenerateZone draws the rocks. Zone owns the rocks' periods and the extent it gives WellTree. Forbidden writers: the renderer, BeltTargets, DrawAhead and mining, which read poses only. No reader computes its own period, and nothing outside Zone's two rules branches on shape. |
Rock (Asteroid) |
(belt key, index) is the ChunkId. The index order is fixed at generation and never re-sorted, because wear (ChunkWear) is keyed by it. |
Persisted (Distance, Phase, Size, RotationSpeed) about the belt's parent; wear and respawn live on Zone. | The generator draws a rock. Zone owns its wear and its pose (AsteroidBelt.Pose). |
| Period rule | A property of the belt, not of the rock: the persisted flag AsteroidBeltData.RidesRail (MessagePack key 11, JSON ridesRail). False: each rock reads its own distance (PeriodAround(parent, rock.Distance)), an annulus. True: every rock takes its rail's period (Orbits[rail].Period), so a Trojan swarm keeps its shape (ruling trojans-ride-host-period-with-stations; on own-distance rails it shears within an hour, B60). |
Persisted at generation. A belt saved before key 11 reads false, the annulus it was (CultCache defaults a missing slot, B61). | Zone's constructor alone writes AsteroidBelt.Periods, through PeriodAround or the rail's own Period; the flag only says which. Forbidden: a period formula in Pose, BeltTargets, DrawAhead, the renderer or the generator, and a second flag or shape enum read anywhere else. |
| Extent rule | What a belt's rail adds to its parent's L_F: AsteroidBelt.Extent(railDistance), the outermost rock for an annulus, the rail's own Distance for a swarm, whose rocks' periods never read their own distance. So a swarm moves no well (B61). An edge (Oort-like) belt must add nothing beyond the window, or the family's fade band narrows (B60). |
Derived at zone build. | AsteroidBelt.Extent, read by Zone (the only WellNode builder in play) and by the station tests' rebuilt tree. The generator's clearance tree never sees a swarm, which is not a GeneratorPlanet. |
| Belt host | A generator-time relation only. A Trojan host is a planet or gas giant (PlanetMass < mass <= SunMass) whose parent is a body, at a distance > 0. A ring member is never a host: its rail's pull adds its partners' (WellTree.PullAround), so a rail copied beside it would take another period and shear (B61). The swarm's rail copies the host's (Parent, Distance, Phase); that copy is all Zone needs, and nothing persists the host. A lane's host is a body that keeps its slot with room on both sides of the lane (cut laned-belts: half the eligible bodies, never a Trojan host, one shape per parent). | Lives for one GenerateZone call. | ZoneGenerator.GenerateZone's swarm pass. |
| Lagrange station orbit | A bodyless OrbitData on a host's rail, 1/6 turn ahead of or behind it (CreateLagrangeOrbit). On a swarm host it sits inside the swarm, a mining outpost among the rocks (ruling trojans-ride-host-period-with-stations); rocks are on the floater layer below it, so nothing collides. |
Created at generation and persisted. | ZoneGenerator's station placement, unchanged: candidacy reads the GeneratorPlanet tree, where no swarm exists, so no swarm can bar its host. |
| Belt generation settings | ZoneGenerationSettings in Settings.asset (an LFS object): BeltProbability 0.25, BeltMassCeiling 500, AsteroidBeltWidth (a ceiling that never binds, B56), AsteroidCount = sqrt(mass x D) + 11. Trojans: TrojanProbability 0.15 per host, TrojanArc 0.07 turn per swarm, TrojanHalfWidth 0.06 D, TrojanCount = 0.04 D + 8 rocks round each of L4 and L5 (tuning defaults for a flight, B61). The code default of TrojanProbability is 0, so settings built in code draw no swarm. |
Authored. | Settings.asset. Forbidden: a tunable shape's numbers as constants in code. The swarm's random stream salt is a ZoneGenerator constant, not a tunable. |
Cells not filled, plainly: where an edge cloud's rocks sit between a root's window and the zone boundary (edge-material's map, rulings edge-material-root-only and edge-material-in-the-dark). The edge material's place in the potential is not a question: it stays out of the extent, because the law breaks otherwise (B60).
Rationale
- Why the drawn grid is a lowering, not an owner:
grid-shows-how-you-movemakes the slope a promise about motion. Six brush kinds on the gravity layers have no sim term or a different envelope (B3), so today the grid shows motion that does not happen (sector noise, wormholes, rim fades) and hides motion that does (the bowl in small zones is drawn at the same depth everywhere while felt only under the gate). - Why rails follow the field: every shipped game where ships feel gravity derives the rails from the same parameter (KSP) or simulates bodies (Outer Wilds); authored rails coexist only with no felt gravity (Elite, Starsector). See the prior-art doc, section 3.
- Why strength, softening and tide are separate questions: a·r = v² ties felt strength to body speed (field-strength); c(mass) sets both readability and how many moons a ship can follow (well-strength-from-mass, B10); R sets the peak (softening-radius). Any two can be ruled without the third.
- Why the force gate is not in the subtraction cut: deleting the gate changes forces everywhere the gate zeroed them (every core, every rim), so it is behaviour, not subtraction, and the function it lives in (
GravityAcceleration) is deleted whole by the potential cut. Removing it first would rewrite FloatingBodyTests' gate assertions into a no-gate test of a law that dies one cut later. - Why the bowl fixtures become a well and not a new test helper per file: B14 makes the substitution exact, so the subtraction cut changes no expected number in five behaviour tests; the potential cut then replaces the fixture's well law once, in one helper.
- Why orbits are placed at build in a sibling: it touches the zone constructor, Update and the dead run-time orbit writers (B17), none of which the subtraction cut touches, and it removes the stacked-wells-at-origin state before the potential cut needs a valid field at construction.
- Why the potential cut is not split into the potential and its readers: the readers are three lines (Ship's force and tilt, Zone.Flow's downhill); floating bodies, clouds and mines already call GetForce. Deleting GravityAcceleration forces Ship's change in the same commit, so a split would leave a cut that cannot build.
- 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).
- (Superseded by B31: the core is now the Roche limit.) Why the core was 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.
- Why a period is derived from
GravityField.Accelerationrather than written as 2πd/sqrt(c): the formula is the tail law spelled a second time and is wrong inside the core (it would run a child faster than any free orbit there); T = 2π·sqrt(d/a) reads the one law, so the core, and any later change to the law, arrive in the rails with no edit (B35's 234 in-core children get a finite solid-body period). - Why the asteroid periods live on the belt: Pose and BeltTargets must divide by the same float (BeltTargets.cs:62); one array computed at build is one owner, where two calls to the same function are two spellings that only agree by care.
- Why one zone node owns the zone's far field (well-law r3): the conserving property is one global term of the total mass, and a tree with several roots has several. Hierarchical N-body methods put the same fact in structure: Barnes-Hut (1986) and the fast multipole method (Greengard and Rokhlin 1987) keep one root cell holding everything, whose far field is the monopole of the total mass at its centre of mass. So the node sits at the roots' barycentre; it is not an orbit, has no body and no ring group, and gives no rail (roots are stationary, period +inf, so parent-only rails are untouched). A zone with one massive root needs no node: that root keeps its infinite reach and nothing about single-root zones changes.
- 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-railsruled 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 questionmulti-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
1in both C# and HLSL is the second spellinggravity-law-one-spellingalready 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: underbinaries-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 readingAtLeastalready 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-moveit either never moves ships, or is drawn by another channel, or is restricted to its gradient part. - Why a belt's shape is its draw plus two rules, not a Shape enum its readers branch on: every reader (Pose, BeltTargets, DrawAhead, mining, the renderer, the minimap) already serves any (Distance, Phase) set (B60). A shape enum read beyond Zone would make each of those readers a second owner of what a shape means.
- Why Trojans ride their rail rather than their own distance: at d ln T / d ln r of about 0.92, any radial width shears the swarm apart within the first hour (B60). Real Trojans are held by libration about L4 and L5 (prior art, section 2.1), which rails cannot produce. The libration-averaged motion is the host's period, so riding the rail is the rail model of a tadpole orbit. The period still comes from the parent's field, read at the guiding centre's distance (
field-owns-orbits). Libration itself could be added later as a phase wobble. - Why a Trojan swarm is not the host's child: L4 and L5 lie a chord D from the host, outside its reach of 0.5 D (B60). Rocks orbiting the host would have the wrong motion and sit in the wrong well.
- Why a swarm is not a GeneratorPlanet: it is massless and sits on its host's own rail, so in the placement tree it would be a sibling at the host's distance, claim a slot, and fail station candidacy's RingWindow check against its own host. Emitted beside the host's records after placement, it takes no slot and no candidacy sees it, so the ruling's "must not refuse a host for its own swarm" holds by where the swarm is made, with a test that stations land on swarm hosts (B61). Its random stream is its own (the zone seed with a salt), so turning swarms on or off moves no other draw in the zone.
- Why a lane's width is not a Hill radius: this law's reach does not depend on mass (it is 0.5 D), and Hill's m^(1/3) is not a curve of this law (prior art, section 1.3). Nor is it the placement Roche margin: planets sit above the grid and belts below, so the lane prevents no collision and only sells the impression of a swept lane (ruling
lanes-two-sided-visual-margin). Its value is laned-belts' to propose and a flight's to judge. - Why edge material stays out of the extent: otherwise support would pass Gamma x reach and the family's fade band would narrow toward nothing (B60, WellTree.cs:224-229), so Oort-like rocks would change gravity. Their periods read the family's fading pull, which is what the field gives there.
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. 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. Mapped and admitted (2026-10-15, base 8f491fd3, stacked on grid-lowering's head): aetheria-gravity:cut_spec:cut-fog-precision-and-jitter.r1, the slope jitter (B32) and the patch's second gravity lowering (B33), one Hands pass. Mapped and admitted (2026-10-15, base 8f491fd3, a sibling of fog-precision-and-jitter on grid-lowering's head, disjoint files): aetheria-gravity:cut_spec:cut-orbits-from-field.r1, periods from the field (B35-B38), one Hands pass; it raises aetheria-gravity:question:rails-enclosed-or-parent and is written for the recommended parent-only option. Not yet mapped:
- ring-roche-final-heavy (mapped and admitted 2026-10-17, base
f948992d):aetheria-gravity:cut_spec:cut-ring-roche-final-heavy.r1(a ring is judged on its members' settled masses; B58; rulingaetheria-release:ruling:ring-roche-fixed-in-generator-cut). Lands on master before aetheria-release faction-curated-place-names, which then rebases and re-runs its seeded repins. - asteroid-floater-layer (mapped and admitted 2026-10-17, base
1d4aa626):aetheria-gravity:cut_spec:cut-asteroid-floater-layer.r1(DrawAhead.Chunk owns rock and reticle height; AsteroidVerticalOffset and the shader's field read go; B57) and its siblingcut-floater-fixtures-wells.r1(follow-up floater-fixtures-flat-field). Open: questionasteroid-clearance(centre or top on the layer). - drawn-pose-at-drawn-time (mapped and admitted 2026-10-15, base
09ec04a8:aetheria-gravity:cut_spec:cut-drawn-pose-at-drawn-time.r1, one Hands pass):DrawAheadowns every drawn pose at T = ExactTime + Lead (B39); closes item 3 offog-jitter-residuals. - tail-taming (ruling
tame-tails-parent-only-rails, leantail-taming-lean-conserving-compact):aetheria-gravity:cut_spec:cut-well-law.r2(the band law, B40-B43, B48; reach 1/2 D, series branch), runnable before questiontail-taming-law-r3is answered; the generator rules go to children-within-reach once r3 sets the spacing. Soul's s1 falsified it (multi-root far field, belt extent, unpinned fade start and ring edge, series threshold); the fix batch isaetheria-gravity:cut_spec:cut-well-law.r3(B52-B54), with the zone's shape between roots put to the operator as questionmulti-root-zone-shape. children-within-reach r2 was refreshed after it merged:aetheria-gravity:cut_spec:cut-children-within-reach.r3(B55), written to questionroot-window's recommended floor-window. Soul's s1 held isolation but found belts collapsed to a line; the delta on Hands' head1f83d074isaetheria-gravity:cut_spec:cut-children-within-reach.r4(B56): belts fill their slots, five forced pins, the font asset restored. - 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; deletesGravityAcceleration,GravityReferenceRate,GetNormalas a force source, the PowerPulse well andGravityDepthExponent; 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. - rosette-equilibria (follow-up
rosette-generation-r3, rulingsrosettes-artificial-derived-from-fields,rosettes-placed-in-equilibrium,rosettes-rare-and-huge,binaries-orbited-from-afar): pairs and rosettes placed as relative equilibria under the band law, members on their own radii (a pair at r_i proportional to c_partner; twisted crowns for alternating rosettes), one spin for every member, so no ring shears (B51); WellTree's ring group accepts members on two radii; members always suns, rare and huge. Waits on children-within-reach (cut-children-within-reach.r3), which places rings as one radius and merges root rings that cannot fit a multi-root zone's reach (B55); rare, huge rosettes need a sole-root zone or a large area. - 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.
- belt-shapes (follow-up
belt-shapes, rulingbelt-shapes-direction; B60, B61; order by rulingtrojans-first). One cut per shape. trojan-swarms is mapped and admitted (2026-10-18, base67517f40):aetheria-gravity:cut_spec:cut-trojan-swarms.r1.- laned-belts (mapped and admitted 2026-10-21, base
d52b50f9:aetheria-gravity:cut_spec:cut-laned-belts.r1; lane half-width 5 body radii, a flight default): only a body with room on both sides of its lane keeps a laned belt, and the lane's half-width is its own visual margin, not the placement Roche margin (rulinglanes-two-sided-visual-margin; 468 eligible at the Roche width, B60). Folds inbelt-after-belt-edge-pin, in the same Place loop. - edge-material: one sparse Oort-like cloud per root (per subzone root in a multi-root zone), from its family's window out toward the zone boundary, nothing at planets' rims (ruling
edge-material-root-only); kept out of the extent (B60) and skipped by the populated radius, so its rocks sit in the dark fade (rulingedge-material-in-the-dark).
- laned-belts (mapped and admitted 2026-10-21, base