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:
@@ -115,9 +115,9 @@ public sealed partial class ScenarioLoader
|
||||
var compactPositions = new[]
|
||||
{
|
||||
new[] { 0f, 0f, 0f },
|
||||
new[] { 2600f, 24f, -420f },
|
||||
new[] { -2400f, -36f, 560f },
|
||||
new[] { 520f, 42f, 2480f },
|
||||
new[] { 2.6f, 0.02f, -0.42f },
|
||||
new[] { -2.4f, -0.04f, 0.56f },
|
||||
new[] { 0.52f, 0.04f, 2.48f },
|
||||
};
|
||||
|
||||
for (var index = 0; index < systems.Count && index < compactPositions.Length; index += 1)
|
||||
@@ -165,9 +165,9 @@ public sealed partial class ScenarioLoader
|
||||
AsteroidField = new AsteroidFieldDefinition
|
||||
{
|
||||
DecorationCount = template.AsteroidField.DecorationCount + ((generatedIndex % 5) * 10),
|
||||
RadiusOffset = template.AsteroidField.RadiusOffset + ((generatedIndex % 4) * 18f),
|
||||
RadiusVariance = template.AsteroidField.RadiusVariance + ((generatedIndex % 3) * 12f),
|
||||
HeightVariance = template.AsteroidField.HeightVariance + ((generatedIndex % 4) * 4f),
|
||||
RadiusOffset = template.AsteroidField.RadiusOffset + ((generatedIndex % 4) * 18000f),
|
||||
RadiusVariance = template.AsteroidField.RadiusVariance + ((generatedIndex % 3) * 12000f),
|
||||
HeightVariance = template.AsteroidField.HeightVariance + ((generatedIndex % 4) * 4000f),
|
||||
},
|
||||
ResourceNodes = resourceNodes,
|
||||
Planets = planets,
|
||||
@@ -287,14 +287,14 @@ public sealed partial class ScenarioLoader
|
||||
private static Vector3 ComputeGeneratedSystemPosition(int generatedIndex, int attempt)
|
||||
{
|
||||
const int armCount = 4;
|
||||
const float baseInnerRadius = 9000f;
|
||||
const float radiusStep = 540f;
|
||||
const float baseInnerRadius = 9f;
|
||||
const float radiusStep = 0.54f;
|
||||
const float armOffset = MathF.PI * 2f / armCount;
|
||||
|
||||
var armIndex = (generatedIndex + attempt) % armCount;
|
||||
var armDepth = generatedIndex / armCount;
|
||||
var radius = baseInnerRadius + (armDepth * radiusStep) + Jitter(generatedIndex * 17 + attempt, 0, 900f);
|
||||
var angle = (armIndex * armOffset) + (radius / 8200f) + Jitter(generatedIndex, 1 + attempt, 0.16f);
|
||||
var radius = baseInnerRadius + (armDepth * radiusStep) + Jitter(generatedIndex * 17 + attempt, 0, 0.9f);
|
||||
var angle = (armIndex * armOffset) + (radius / 8.2f) + Jitter(generatedIndex, 1 + attempt, 0.16f);
|
||||
var x = MathF.Cos(angle) * radius;
|
||||
var z = MathF.Sin(angle) * radius * 0.58f;
|
||||
var y = ComputeSystemHeight(radius, generatedIndex, attempt);
|
||||
@@ -304,9 +304,9 @@ public sealed partial class ScenarioLoader
|
||||
private static Vector3 ComputeFallbackGeneratedSystemPosition(int generatedIndex)
|
||||
{
|
||||
const int ringCount = 5;
|
||||
const float fallbackRadius = 42000f;
|
||||
const float fallbackRadius = 42f;
|
||||
var angle = (generatedIndex % ringCount) * (MathF.PI * 2f / ringCount) + (generatedIndex / ringCount) * 0.22f;
|
||||
var radius = fallbackRadius + (generatedIndex / ringCount) * 1800f;
|
||||
var radius = fallbackRadius + (generatedIndex / ringCount) * 1.8f;
|
||||
return new Vector3(
|
||||
MathF.Cos(angle) * radius,
|
||||
ComputeSystemHeight(radius, generatedIndex, 99),
|
||||
@@ -334,7 +334,7 @@ public sealed partial class ScenarioLoader
|
||||
{
|
||||
SourceKind = "asteroid-belt",
|
||||
Angle = ((MathF.PI * 2f) / nodeCount) * index + Jitter(generatedIndex, 180 + index, 0.22f),
|
||||
RadiusOffset = 120f + Jitter(generatedIndex, 200 + index, 36f),
|
||||
RadiusOffset = 120000f + Jitter(generatedIndex, 200 + index, 36000f),
|
||||
InclinationDegrees = Jitter(generatedIndex, 280 + index, 12f),
|
||||
AnchorPlanetIndex = ResolveAsteroidAnchorPlanetIndex(planets),
|
||||
OreAmount = oreAmount,
|
||||
@@ -374,7 +374,7 @@ public sealed partial class ScenarioLoader
|
||||
{
|
||||
SourceKind = "gas-cloud",
|
||||
Angle = gasAnchor.OrbitPhaseAtEpoch * (MathF.PI / 180f) + (((MathF.PI * 2f) / nodeCount) * index) + Jitter(generatedIndex, 240 + index, 0.18f),
|
||||
RadiusOffset = 170f + Jitter(generatedIndex, 260 + index, 44f),
|
||||
RadiusOffset = 170000f + Jitter(generatedIndex, 260 + index, 44000f),
|
||||
InclinationDegrees = Jitter(generatedIndex, 320 + index, 10f),
|
||||
AnchorPlanetIndex = gasAnchorIndex,
|
||||
OreAmount = gasAmount,
|
||||
@@ -415,7 +415,7 @@ public sealed partial class ScenarioLoader
|
||||
{
|
||||
var planetCount = 2 + (int)MathF.Floor(Hash01(generatedIndex, 2) * 7f);
|
||||
var planets = new List<PlanetDefinition>(planetCount);
|
||||
var orbitRadius = 140f + (Hash01(generatedIndex, 3) * 35f);
|
||||
var orbitRadius = 0.24f + (Hash01(generatedIndex, 3) * 0.12f);
|
||||
var sourcePlanets = template.Planets.Count > 0 ? template.Planets : null;
|
||||
|
||||
for (var index = 0; index < planetCount; index += 1)
|
||||
@@ -437,13 +437,13 @@ public sealed partial class ScenarioLoader
|
||||
Shape = profile.Shape,
|
||||
MoonCount = profile.BaseMoonCount + moonVariance,
|
||||
OrbitRadius = orbitRadius,
|
||||
OrbitSpeed = 0.22f / MathF.Sqrt(MathF.Max(1f, orbitRadius / 120f)),
|
||||
OrbitSpeed = 0.11f / MathF.Sqrt(MathF.Max(orbitRadius * orbitRadius * orbitRadius, 0.02f)),
|
||||
OrbitEccentricity = orbitEccentricity,
|
||||
OrbitInclination = orbitInclination,
|
||||
OrbitLongitudeOfAscendingNode = Hash01(generatedIndex, 120 + index) * 360f,
|
||||
OrbitArgumentOfPeriapsis = Hash01(generatedIndex, 140 + index) * 360f,
|
||||
OrbitPhaseAtEpoch = Hash01(generatedIndex, 160 + index) * 360f,
|
||||
Size = profile.BaseSize + (Hash01(generatedIndex, 50 + index) * 10f),
|
||||
Size = profile.BaseSize + (Hash01(generatedIndex, 50 + index) * (profile.BaseSize * 0.35f)),
|
||||
Color = templatePlanet?.Color ?? profile.Color,
|
||||
Tilt = -0.45f + (Hash01(generatedIndex, 60 + index) * 0.9f),
|
||||
HasRing = profile.CanHaveRing && Hash01(generatedIndex, 70 + index) > 0.55f,
|
||||
@@ -493,8 +493,8 @@ public sealed partial class ScenarioLoader
|
||||
|
||||
private static float ComputeSystemHeight(float radius, int generatedIndex, int salt)
|
||||
{
|
||||
var normalized = MathF.Min(1f, MathF.Max(0f, (radius - 8000f) / 28000f));
|
||||
var band = 220f + (normalized * 760f);
|
||||
var normalized = MathF.Min(1f, MathF.Max(0f, (radius - 8f) / 28f));
|
||||
var band = 0.22f + (normalized * 0.76f);
|
||||
return (Hash01(generatedIndex, 100 + salt) * 2f - 1f) * band;
|
||||
}
|
||||
|
||||
@@ -528,7 +528,7 @@ public sealed partial class ScenarioLoader
|
||||
int BaseMoonCount,
|
||||
bool CanHaveRing)
|
||||
{
|
||||
public float OrbitGapMax => OrbitGapMin + 44f;
|
||||
public float OrbitGapMax => OrbitGapMin + MathF.Max(0.12f, OrbitGapMin * 0.45f);
|
||||
}
|
||||
|
||||
private static SolarSystemDefinition CreateSolSystem()
|
||||
@@ -546,29 +546,29 @@ public sealed partial class ScenarioLoader
|
||||
{
|
||||
Id = "sol",
|
||||
Label = "Sol",
|
||||
Position = [18200f, 24f, -11800f],
|
||||
Position = [18.2f, 0.02f, -11.8f],
|
||||
StarKind = "main-sequence",
|
||||
StarCount = 1,
|
||||
StarColor = "#fff1b8",
|
||||
StarGlow = "#ffd35a",
|
||||
StarSize = 58f,
|
||||
StarSize = 696340f,
|
||||
GravityWellRadius = 240f,
|
||||
AsteroidField = new AsteroidFieldDefinition
|
||||
{
|
||||
DecorationCount = 240,
|
||||
RadiusOffset = ScaleSolOrbitRadiusFromAu(2.82f),
|
||||
RadiusVariance = 180f,
|
||||
HeightVariance = 22f,
|
||||
RadiusOffset = 422000000f,
|
||||
RadiusVariance = 180000000f,
|
||||
HeightVariance = 22000000f,
|
||||
},
|
||||
ResourceNodes =
|
||||
[
|
||||
new ResourceNodeDefinition { SourceKind = "asteroid-belt", Angle = 0.2f, RadiusOffset = 126f, InclinationDegrees = 4f, AnchorPlanetIndex = 3, OreAmount = 1000f, ItemId = "ore", ShardCount = 9 },
|
||||
new ResourceNodeDefinition { SourceKind = "asteroid-belt", Angle = 1.8f, RadiusOffset = 148f, InclinationDegrees = -6f, AnchorPlanetIndex = 3, OreAmount = 1000f, ItemId = "ore", ShardCount = 9 },
|
||||
new ResourceNodeDefinition { SourceKind = "asteroid-belt", Angle = 3.5f, RadiusOffset = 138f, InclinationDegrees = 8f, AnchorPlanetIndex = 4, OreAmount = 1000f, ItemId = "ore", ShardCount = 9 },
|
||||
new ResourceNodeDefinition { SourceKind = "asteroid-belt", Angle = 5.1f, RadiusOffset = 164f, InclinationDegrees = -5f, AnchorPlanetIndex = 4, OreAmount = 1000f, ItemId = "ore", ShardCount = 9 },
|
||||
new ResourceNodeDefinition { SourceKind = "gas-cloud", Angle = 0.9f, RadiusOffset = 210f, InclinationDegrees = 3f, AnchorPlanetIndex = 4, OreAmount = 1000f, ItemId = "gas", ShardCount = 12 },
|
||||
new ResourceNodeDefinition { SourceKind = "gas-cloud", Angle = 2.7f, RadiusOffset = 228f, InclinationDegrees = -4f, AnchorPlanetIndex = 5, OreAmount = 1000f, ItemId = "gas", ShardCount = 12 },
|
||||
new ResourceNodeDefinition { SourceKind = "gas-cloud", Angle = 4.8f, RadiusOffset = 186f, InclinationDegrees = 6f, AnchorPlanetIndex = 6, OreAmount = 1000f, ItemId = "gas", ShardCount = 10 },
|
||||
new ResourceNodeDefinition { SourceKind = "asteroid-belt", Angle = 0.2f, RadiusOffset = 126000f, InclinationDegrees = 4f, AnchorPlanetIndex = 3, OreAmount = 1000f, ItemId = "ore", ShardCount = 9 },
|
||||
new ResourceNodeDefinition { SourceKind = "asteroid-belt", Angle = 1.8f, RadiusOffset = 148000f, InclinationDegrees = -6f, AnchorPlanetIndex = 3, OreAmount = 1000f, ItemId = "ore", ShardCount = 9 },
|
||||
new ResourceNodeDefinition { SourceKind = "asteroid-belt", Angle = 3.5f, RadiusOffset = 138000f, InclinationDegrees = 8f, AnchorPlanetIndex = 4, OreAmount = 1000f, ItemId = "ore", ShardCount = 9 },
|
||||
new ResourceNodeDefinition { SourceKind = "asteroid-belt", Angle = 5.1f, RadiusOffset = 164000f, InclinationDegrees = -5f, AnchorPlanetIndex = 4, OreAmount = 1000f, ItemId = "ore", ShardCount = 9 },
|
||||
new ResourceNodeDefinition { SourceKind = "gas-cloud", Angle = 0.9f, RadiusOffset = 210000f, InclinationDegrees = 3f, AnchorPlanetIndex = 4, OreAmount = 1000f, ItemId = "gas", ShardCount = 12 },
|
||||
new ResourceNodeDefinition { SourceKind = "gas-cloud", Angle = 2.7f, RadiusOffset = 228000f, InclinationDegrees = -4f, AnchorPlanetIndex = 5, OreAmount = 1000f, ItemId = "gas", ShardCount = 12 },
|
||||
new ResourceNodeDefinition { SourceKind = "gas-cloud", Angle = 4.8f, RadiusOffset = 186000f, InclinationDegrees = 6f, AnchorPlanetIndex = 6, OreAmount = 1000f, ItemId = "gas", ShardCount = 10 },
|
||||
],
|
||||
Planets =
|
||||
[
|
||||
@@ -605,7 +605,7 @@ public sealed partial class ScenarioLoader
|
||||
PlanetType = planetType,
|
||||
Shape = shape,
|
||||
MoonCount = moonCount,
|
||||
OrbitRadius = ScaleSolOrbitRadiusFromAu(orbitRadiusAu),
|
||||
OrbitRadius = orbitRadiusAu,
|
||||
OrbitSpeed = ComputeSolOrbitSpeed(orbitRadiusAu),
|
||||
OrbitEccentricity = orbitEccentricity,
|
||||
OrbitInclination = orbitInclination,
|
||||
@@ -614,9 +614,16 @@ public sealed partial class ScenarioLoader
|
||||
OrbitPhaseAtEpoch = phaseAtEpoch,
|
||||
Size = planetType switch
|
||||
{
|
||||
"gas-giant" => label == "Saturn" ? 66f : 72f,
|
||||
"ice-giant" => 48f,
|
||||
_ => label == "Earth" ? 28f : label == "Mars" ? 22f : label == "Venus" ? 26f : 20f,
|
||||
"gas-giant" => label == "Saturn" ? 58232f : 69911f,
|
||||
"ice-giant" => label == "Uranus" ? 25362f : 24622f,
|
||||
_ => label switch
|
||||
{
|
||||
"Mercury" => 2440f,
|
||||
"Venus" => 6052f,
|
||||
"Earth" => 6371f,
|
||||
"Mars" => 3390f,
|
||||
_ => 5000f,
|
||||
},
|
||||
},
|
||||
Color = color,
|
||||
Tilt = tilt,
|
||||
@@ -624,9 +631,6 @@ public sealed partial class ScenarioLoader
|
||||
};
|
||||
}
|
||||
|
||||
private static float ScaleSolOrbitRadiusFromAu(float orbitRadiusAu) =>
|
||||
MathF.Round(500f * MathF.Pow(orbitRadiusAu, 0.70f));
|
||||
|
||||
private static float ComputeSolOrbitSpeed(float orbitRadiusAu)
|
||||
{
|
||||
const float earthAngularSpeed = 0.11f;
|
||||
|
||||
Reference in New Issue
Block a user