Merge eureka/aetheria-gravity-ring-roche-final-heavy (head 390a5958)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
b9e16872c7
2 changed files with 36 additions and 13 deletions
|
|
@ -586,8 +586,8 @@ 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; it folds into its node, which becomes a body, if it cannot keep the heavy member outside
|
||||
/// its Roche margin. Ported from docs/research/gravity-well-law/replace.py.
|
||||
/// A rosette's ring is one orbit; 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)
|
||||
{
|
||||
|
|
@ -647,6 +647,7 @@ public static class ZoneGenerator
|
|||
}
|
||||
}
|
||||
|
||||
var settled = new HashSet<GeneratorPlanet>(); // ring members, settled by their node so the ring is judged on their final masses
|
||||
var chordCap = new Dictionary<GeneratorPlanet, float>(); // a ring member's children stay within .2 of the nearest partner's chord
|
||||
void Settle(GeneratorPlanet node)
|
||||
{
|
||||
|
|
@ -668,19 +669,20 @@ public static class ZoneGenerator
|
|||
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);
|
||||
// The heavy member's mass is final only now (the outer children fold into it).
|
||||
if (ringDistance < RocheMargin * Roche(heavy.Mass))
|
||||
{
|
||||
// The pair cannot keep its heavy member outside its Roche margin inside its node's reach: the node is one body.
|
||||
node.Empty = false;
|
||||
node.Mass = 0;
|
||||
foreach (var child in node.Children.ToList()) Fold(child, node);
|
||||
return;
|
||||
}
|
||||
foreach (var member in ring)
|
||||
{
|
||||
member.Distance = ringDistance;
|
||||
chordCap[member] = .2f * 2 * ringDistance * sin(PI / ring.Count);
|
||||
Settle(member);
|
||||
settled.Add(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)))
|
||||
{
|
||||
// The ring cannot keep its heaviest member outside its Roche margin inside its node's reach: the node is one body.
|
||||
node.Empty = false;
|
||||
node.Mass = 0;
|
||||
foreach (var child in node.Children.ToList()) Fold(child, node);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -689,7 +691,7 @@ public static class ZoneGenerator
|
|||
while (queue.Count > 0)
|
||||
{
|
||||
var node = queue.Dequeue();
|
||||
Settle(node);
|
||||
if (!settled.Contains(node)) Settle(node);
|
||||
foreach (var child in node.Children) queue.Enqueue(child);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,9 @@ public sealed class ChildrenWithinReachTests : IClassFixture<ChildrenWithinReach
|
|||
public readonly List<(uint Seed, Zone Zone)> All = new List<(uint, Zone)>();
|
||||
public ZoneGenerationSettings Settings;
|
||||
|
||||
public Zones() : this(1, 6, null) { }
|
||||
// Seeds 7, 11 and 12 held rings inside their heavy member's Roche margin (ring-roche-final-heavy), which seeds 1-6 do not
|
||||
// show. Seed 10 holds a bodyless non-ring node whose massive children are never placed (distance 0); widen past 9 once that is fixed.
|
||||
public Zones() : this(1, 9, null) { }
|
||||
|
||||
// Not public: xunit builds the fixture through its one public constructor; a test that wants a tuned sample builds its own.
|
||||
internal Zones(uint first, uint last, Action<ZoneGenerationSettings> tune)
|
||||
|
|
@ -458,6 +460,25 @@ public sealed class ChildrenWithinReachTests : IClassFixture<ChildrenWithinReach
|
|||
Assert.True(Math.Abs(member.Distance - .375f * radius) <= 1e-4f * radius, $"a ring member sits at {member.Distance}, not {.375f * radius}");
|
||||
}
|
||||
|
||||
// A ring is judged on its members' settled masses: a member's own children fold into it when it settles
|
||||
// (ring-roche-fixed-in-generator-cut).
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
public void ARingIsJudgedOnItsMembersSettledMasses(bool heavyGrows)
|
||||
{
|
||||
var settings = AuthoredPlanetSettings();
|
||||
const float mass = 1000f;
|
||||
var ringDistance = RocheMargin * settings.RocheLimit.Evaluate(mass) * 1.02f;
|
||||
var root = new GeneratorPlanet { Empty = true };
|
||||
var heavy = Body(root, mass, 0, true);
|
||||
var light = Body(root, .9f * mass, .5f, true);
|
||||
Body(heavyGrows ? heavy : light, .5f * mass);
|
||||
ZoneGenerator.Place(root, settings, ringDistance / .375f, float.PositiveInfinity);
|
||||
Assert.True(!root.Empty && root.Children.Count == 0 && Math.Abs(root.Mass - 2.4f * mass) <= 1e-3f * mass,
|
||||
$"the ring stood at {ringDistance} with a member grown to {Math.Max(heavy.Mass, light.Mass)}");
|
||||
}
|
||||
|
||||
// A ring member's children end within a fifth of the chord to the member's nearest partner, where the chord, not the member's
|
||||
// fade, is what holds them.
|
||||
[Fact]
|
||||
|
|
|
|||
Loading…
Reference in a new issue