ZoneGenerator.Place: a bodyless node is one body of its family's mass wherever a mass is read, and ChildrenWithinReachTests widens to seeds 1-30

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-10-18 02:01:09 +02:00
commit 408b1c45bd
3 changed files with 40 additions and 13 deletions

View file

@ -586,7 +586,7 @@ public static class ZoneGenerator
/// outermost overflows the window, and the rest are stretched geometrically so the outermost sits at the window's edge. A
/// non-root's window ends before its own fade (Gamma x Reach x its distance); a root's, in a zone of several, ends under
/// rootReach / Beta so its support never meets another root's (root-window-floor), and is 0.75 x the area radius at most.
/// A rosette's ring is one orbit; once its members have settled (their own children folded in), it folds into its node,
/// A rosette's ring is one orbit, and a member may itself be bodyless, which counts as one body of its family's mass; once its members have settled (their own children folded in), it folds into its node,
/// which becomes a body, if it cannot keep its heaviest member outside its Roche margin. Ported from docs/research/gravity-well-law/replace.py.
/// </summary>
public static void Place(GeneratorPlanet root, PlanetSettings planetSettings, float radius, float rootReach)
@ -596,6 +596,8 @@ public static class ZoneGenerator
float Strength(float mass) => WellTree.Curves(planetSettings, mass).Strength;
float WellMass(GeneratorPlanet p) => p.Empty || p.Belt ? 0f : p.Mass;
float Family(GeneratorPlanet p) => (p.Empty ? 0f : p.Mass) + p.Children.Sum(c => Family(c));
// A bodyless node pulls from afar as one body of its family's mass, which it becomes if its ring cannot stand (binaries-orbited-from-afar)
float OneBody(GeneratorPlanet p) => p.Empty ? Family(p) : WellMass(p);
void Fold(GeneratorPlanet node, GeneratorPlanet into)
{
into.Mass += Family(node);
@ -607,8 +609,7 @@ public static class ZoneGenerator
upper *= 1 - 1e-6f;
float AtLeast(GeneratorPlanet child, float edge)
{
// A bodyless node stands in for one body of its family's mass, which it becomes if its ring cannot stand
var m = child.Empty ? Family(child) : WellMass(child);
var m = OneBody(child);
var mInto = WellMass(into);
return m <= 0 || mInto <= 0 ? edge : Math.Max(edge, 2 * CuspMargin * Roche(m) * Strength(mInto) / Strength(m));
}
@ -663,9 +664,9 @@ public static class ZoneGenerator
return;
}
var ring = node.Children.Where(c => c.Ring && WellMass(c) > 0).ToList();
var ring = node.Children.Where(c => c.Ring && OneBody(c) > 0).ToList();
if (ring.Count == 0) { foreach (var child in node.Children.ToList()) Settle(child); return; }
var heavy = ring.OrderByDescending(c => c.Mass).First();
var heavy = ring.OrderByDescending(OneBody).First();
var ringDistance = Math.Min(.5f * cap, share * reach / (GravityField.Beta * GravityField.Beta * (1 + GravityField.RingReach)));
PlaceChildren(node.Children.Where(c => !ring.Contains(c)).ToList(),
() => (1 + GravityField.RingReach) * ringDistance * GravityField.Beta, upper, heavy);
@ -676,7 +677,7 @@ public static class ZoneGenerator
Settle(member);
}
// A member's mass is final only once it has settled (its own overflowing children fold into it).
if (ringDistance < RocheMargin * Roche(ring.Max(c => c.Mass)))
if (ringDistance < RocheMargin * Roche(ring.Max(OneBody)))
{
// The ring cannot keep its heaviest member outside its Roche margin inside its node's reach: the node is one body.
node.Empty = false;