Gravity: one belt extent rule, read by Zone, the generator's clearance tree and the station tests

AsteroidBelt.Extent(data, rail) is now static over the belt's data and the only spelling: the clearance tree's
'Distance + BeltHalfWidth' and the instance Extent are gone. The swarm host filter loses its dead '!p.Empty'
(no bodyless node has a parent). Findings extent-second-spelling and dead-empty-guard (trojan-swarms-residuals).

The clearance tree now reads the outermost rock, not the rail plus half-width, so 29 of the 1920 zones of seeds
1-30 place a station or turret differently. TrojanSwarmTests.BaseDigest is repinned for seeds 3-6, which hold such
zones; seeds 1 and 2 are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
soul 2026-10-21 20:28:35 +02:00
commit 73d1cee9de
4 changed files with 22 additions and 16 deletions

View file

@ -221,7 +221,7 @@ public class Zone
foreach (var belt in AsteroidBelts)
{
var rail = Planets[belt.Key].Orbit.Key;
beltExtent[rail] = belt.Value.Extent(Orbits[rail].Data.Distance);
beltExtent[rail] = AsteroidBelt.Extent(belt.Value.Data, Orbits[rail].Data.Distance);
}
var wellNodes = new List<WellNode>();
foreach (var orbit in Orbits)
@ -888,8 +888,10 @@ public class AsteroidBelt
}
// What the belt's rail adds to its parent's L_F (WellTree): an annulus reaches its outermost rock, whose period reads the
// field there; a swarm riding its rail reaches only the rail, whose period every rock shares.
public float Extent(float railDistance) => Data.RidesRail ? railDistance : Math.Max(Radius, railDistance);
// field there; a swarm riding its rail reaches only the rail, whose period every rock shares. The one belt extent rule:
// Zone, ZoneGenerator's clearance tree and the station tests all read it.
public static float Extent(AsteroidBeltData data, float railDistance) =>
data.RidesRail ? railDistance : Math.Max(data.Asteroids.Max(a => a.Distance), railDistance);
// Cut 1 (docs/mining-cut.md): the formula of Zone.cs:269-271 verbatim, `time` in double as `_time` was used.
// The only place a chunk pose is computed; no per-tick copy survives it. Cut 2: size is no longer read from

View file

@ -118,6 +118,7 @@ public static class ZoneGenerator
}
pack.Orbits = planets.Select(planet => cache.RefOf(orbitMap[planet])).ToList();
var beltOf = new Dictionary<GeneratorPlanet, AsteroidBeltData>();
pack.Planets = planets.Where(p=>!p.Empty).Select(planet =>
{
BodyType bodyType = planet.Belt ? BodyType.Asteroid :
@ -149,6 +150,7 @@ public static class ZoneGenerator
planetData.Orbit = cache.RefOf(orbitMap[planet]);
if (planetData is AsteroidBeltData beltData)
{
beltOf[planet] = beltData;
beltData.Asteroids =
Enumerable.Range(0, (int) zoneSettings.AsteroidCount.Evaluate(beltData.Mass * orbitMap[planet].Distance))
.Select(_ => new Asteroid
@ -219,7 +221,7 @@ public static class ZoneGenerator
// rocks round both L4 and L5. Drawn from a stream of its own, so no other draw in the zone moves; not a GeneratorPlanet,
// so it takes no slot and no station candidacy sees it.
var swarmRandom = new Random(seed ^ TrojanStream);
foreach (var host in planets.Where(p => p.Parent != null && !p.Parent.Empty && !p.Empty && !p.Belt && p.Distance > 0
foreach (var host in planets.Where(p => p.Parent != null && !p.Parent.Empty && !p.Belt && p.Distance > 0
&& p.Mass > zoneSettings.PlanetMass && p.Mass <= zoneSettings.SunMass))
{
if (swarmRandom.NextFloat() >= zoneSettings.TrojanProbability) continue;
@ -239,10 +241,11 @@ public static class ZoneGenerator
}
// The generator's own well tree, for clearance only (the Zone builds its own): what a rail must stay out of is the body's
// support, or its Roche margin if more.
// support, or its Roche margin if more. A belt's extent is the rule Zone reads (AsteroidBelt.Extent). Swarm rails are
// left out: a swarm's extent is its own rail, a copy of its host's, so it raises no L_F and no support.
var wellNodes = planets.Select(p => new WellNode(cache.RefOf(orbitMap[p]).Key, p.Parent == null ? default : cache.RefOf(orbitMap[p.Parent]).Key,
p.Distance, p.Phase, p.Empty || p.Belt ? 0f : p.Mass,
p.Belt ? p.Distance + p.BeltHalfWidth : p.Distance, p.FixedPosition)).ToList();
p.Belt ? AsteroidBelt.Extent(beltOf[p], p.Distance) : p.Distance, p.FixedPosition)).ToList();
var wells = WellTree.Build(wellNodes, planetSettings, pack.Radius);
float Extent(GeneratorPlanet p)
{