feat: migrate simulation to physically-based unit system
Replace arbitrary game units with real-world measurements throughout the simulation and viewer: planet orbits in AU, sizes in km, galaxy positions in light-years. Add SimulationUnits helpers for conversions, separate WarpSpeed from FtlSpeed for ships, fix FTL transit progress to use galaxy-space distances, overhaul Lagrange point placement with Hill sphere approximation, and update the viewer to scale and format all distances correctly. Ships in FTL transit now render in galaxy view. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -11,7 +11,7 @@ public sealed partial class SimulationEngine
|
||||
var eccentricAnomaly = meanAnomaly
|
||||
+ (eccentricity * MathF.Sin(meanAnomaly))
|
||||
+ (0.5f * eccentricity * eccentricity * MathF.Sin(2f * meanAnomaly));
|
||||
var semiMajorAxis = planet.OrbitRadius;
|
||||
var semiMajorAxis = SimulationUnits.AuToKilometers(planet.OrbitRadius);
|
||||
var semiMinorAxis = semiMajorAxis * MathF.Sqrt(MathF.Max(1f - (eccentricity * eccentricity), 0.05f));
|
||||
var local = new Vector3(
|
||||
semiMajorAxis * (MathF.Cos(eccentricAnomaly) - eccentricity),
|
||||
@@ -58,7 +58,7 @@ public sealed partial class SimulationEngine
|
||||
private static float ComputeResourceNodeOrbitSpeed(ResourceNodeRuntime node)
|
||||
{
|
||||
var baseSpeed = node.SourceKind == "gas-cloud" ? 0.16f : 0.24f;
|
||||
return baseSpeed / MathF.Sqrt(MathF.Max(node.OrbitRadius / 180f, 0.45f));
|
||||
return baseSpeed / MathF.Sqrt(MathF.Max(node.OrbitRadius / 180000f, 0.45f));
|
||||
}
|
||||
|
||||
private static Vector3 ComputeResourceNodeOffset(ResourceNodeRuntime node, float timeSeconds)
|
||||
@@ -73,39 +73,51 @@ public sealed partial class SimulationEngine
|
||||
|
||||
private static IEnumerable<LagrangePointPlacement> EnumeratePlanetLagrangePoints(
|
||||
Vector3 planetPosition,
|
||||
float orbitRadius,
|
||||
float planetSize,
|
||||
int planetIndex)
|
||||
PlanetDefinition planet)
|
||||
{
|
||||
var radial = NormalizeOrFallback(planetPosition, new Vector3(1f, 0f, 0f));
|
||||
var tangential = new Vector3(-radial.Z, 0f, radial.X);
|
||||
var offset = ComputePlanetLocalLagrangeOffset(orbitRadius, planetSize, planetIndex);
|
||||
var orbitRadiusKm = MathF.Sqrt(planetPosition.X * planetPosition.X + planetPosition.Z * planetPosition.Z);
|
||||
var offset = ComputePlanetLocalLagrangeOffset(orbitRadiusKm, planet);
|
||||
var triangularAngle = MathF.PI / 3f;
|
||||
|
||||
yield return new LagrangePointPlacement("L1", Add(planetPosition, Scale(radial, -offset)));
|
||||
yield return new LagrangePointPlacement("L2", Add(planetPosition, Scale(radial, offset)));
|
||||
yield return new LagrangePointPlacement("L3", Add(planetPosition, Scale(radial, -(offset * 1.2f))));
|
||||
yield return new LagrangePointPlacement("L3", Scale(radial, -orbitRadiusKm));
|
||||
yield return new LagrangePointPlacement(
|
||||
"L4",
|
||||
Add(
|
||||
planetPosition,
|
||||
Add(
|
||||
Scale(radial, offset * MathF.Cos(triangularAngle)),
|
||||
Scale(tangential, offset * MathF.Sin(triangularAngle)))));
|
||||
Scale(radial, orbitRadiusKm * MathF.Cos(triangularAngle)),
|
||||
Scale(tangential, orbitRadiusKm * MathF.Sin(triangularAngle))));
|
||||
yield return new LagrangePointPlacement(
|
||||
"L5",
|
||||
Add(
|
||||
planetPosition,
|
||||
Add(
|
||||
Scale(radial, offset * MathF.Cos(triangularAngle)),
|
||||
Scale(tangential, -offset * MathF.Sin(triangularAngle)))));
|
||||
Scale(radial, orbitRadiusKm * MathF.Cos(triangularAngle)),
|
||||
Scale(tangential, -orbitRadiusKm * MathF.Sin(triangularAngle))));
|
||||
}
|
||||
|
||||
private static float ComputePlanetLocalLagrangeOffset(float orbitRadius, float planetSize, int planetIndex)
|
||||
private static float ComputePlanetLocalLagrangeOffset(float orbitRadiusKm, PlanetDefinition planet)
|
||||
{
|
||||
var orbitalScale = MathF.Min(orbitRadius * 0.016f, 96f + (planetIndex * 4f));
|
||||
var sizeScale = (planetSize * 1.9f) + 10f;
|
||||
return MathF.Max(22f + (planetIndex * 2f), MathF.Max(orbitalScale, sizeScale));
|
||||
var planetMassProxy = EstimatePlanetMassRatio(planet);
|
||||
var hillLikeOffset = orbitRadiusKm * MathF.Cbrt(MathF.Max(planetMassProxy / 3f, 1e-9f));
|
||||
var minimumOffset = MathF.Max(planet.Size * 4f, 25000f);
|
||||
return MathF.Max(minimumOffset, hillLikeOffset);
|
||||
}
|
||||
|
||||
private static float EstimatePlanetMassRatio(PlanetDefinition planet)
|
||||
{
|
||||
var earthRadiusRatio = MathF.Max(planet.Size / 6371f, 0.05f);
|
||||
var densityFactor = planet.PlanetType switch
|
||||
{
|
||||
"gas-giant" => 0.24f,
|
||||
"ice-giant" => 0.18f,
|
||||
"oceanic" => 0.95f,
|
||||
"ice" => 0.7f,
|
||||
_ => 1f,
|
||||
};
|
||||
|
||||
var earthMasses = MathF.Pow(earthRadiusRatio, 3f) * densityFactor;
|
||||
return earthMasses / 332_946f;
|
||||
}
|
||||
|
||||
private static Vector3 NormalizeOrFallback(Vector3 value, Vector3 fallback)
|
||||
@@ -185,7 +197,7 @@ public sealed partial class SimulationEngine
|
||||
var planetPosition = ComputePlanetPosition(planet, worldTimeSeconds);
|
||||
planetNode.Position = planetPosition;
|
||||
|
||||
foreach (var lagrange in EnumeratePlanetLagrangePoints(planetPosition, planet.OrbitRadius, planet.Size, planetIndex))
|
||||
foreach (var lagrange in EnumeratePlanetLagrangePoints(planetPosition, planet))
|
||||
{
|
||||
var lagrangeId = $"node-{system.Definition.Id}-planet-{planetIndex + 1}-{lagrange.Designation.ToLowerInvariant()}";
|
||||
if (spatialNodesById.TryGetValue(lagrangeId, out var lagrangeNode))
|
||||
|
||||
Reference in New Issue
Block a user