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:
@@ -1,6 +1,7 @@
|
||||
import * as THREE from "three";
|
||||
import { MOON_RENDER_SCALE } from "./viewerConstants";
|
||||
import type {
|
||||
ShipSnapshot,
|
||||
PlanetSnapshot,
|
||||
Vector3Dto,
|
||||
WorldSnapshot,
|
||||
@@ -12,6 +13,10 @@ import type {
|
||||
} from "./viewerTypes";
|
||||
import type { ZoomBlend } from "./viewerConstants";
|
||||
|
||||
export const KILOMETERS_PER_AU = 149_597_870.7;
|
||||
export const DISPLAY_UNITS_PER_KILOMETER = 0.0000015;
|
||||
export const DISPLAY_UNITS_PER_LIGHT_YEAR = 2600;
|
||||
|
||||
export function formatInventory(entries: { itemId: string; amount: number }[]): string {
|
||||
if (entries.length === 0) {
|
||||
return "empty";
|
||||
@@ -30,6 +35,61 @@ export function formatVector(vector: Vector3Dto): string {
|
||||
return `${vector.x.toFixed(1)}, ${vector.y.toFixed(1)}, ${vector.z.toFixed(1)}`;
|
||||
}
|
||||
|
||||
function formatNumber(value: number, fractionDigits: number) {
|
||||
return new Intl.NumberFormat("en-US", {
|
||||
minimumFractionDigits: fractionDigits,
|
||||
maximumFractionDigits: fractionDigits,
|
||||
}).format(value);
|
||||
}
|
||||
|
||||
export function formatLocalDistance(value: number): string {
|
||||
return `${formatNumber(value, 0)} km`;
|
||||
}
|
||||
|
||||
export function formatSystemDistance(value: number): string {
|
||||
return `${formatNumber(value, 2)} AU`;
|
||||
}
|
||||
|
||||
export function formatGalaxyDistance(value: number): string {
|
||||
return `${formatNumber(value, 2)} ly`;
|
||||
}
|
||||
|
||||
export function formatAdaptiveDistanceFromKilometers(kilometers: number): string {
|
||||
const absoluteKilometers = Math.max(0, kilometers);
|
||||
const meters = absoluteKilometers * 1000;
|
||||
const astronomicalUnits = absoluteKilometers / KILOMETERS_PER_AU;
|
||||
const lightYears = absoluteKilometers / (KILOMETERS_PER_AU * 63_241.077);
|
||||
|
||||
if (lightYears >= 0.1) {
|
||||
return `${formatNumber(lightYears, 2)} ly`;
|
||||
}
|
||||
|
||||
if (astronomicalUnits >= 0.1) {
|
||||
return `${formatNumber(astronomicalUnits, astronomicalUnits >= 10 ? 1 : 3)} AU`;
|
||||
}
|
||||
|
||||
if (absoluteKilometers >= 1) {
|
||||
return `${formatNumber(absoluteKilometers, absoluteKilometers >= 100 ? 0 : 2)} km`;
|
||||
}
|
||||
|
||||
return `${formatNumber(meters, meters >= 100 ? 0 : 1)} m`;
|
||||
}
|
||||
|
||||
export function formatShipSpeed(ship: ShipSnapshot): string {
|
||||
const speed = Math.max(0, ship.travelSpeed);
|
||||
const unit = ship.travelSpeedUnit;
|
||||
if (unit === "ly/s") {
|
||||
return `${formatNumber(speed, 3)} ly/s`;
|
||||
}
|
||||
if (unit === "AU/s") {
|
||||
return `${formatNumber(speed, 4)} AU/s`;
|
||||
}
|
||||
if (speed >= 1000) {
|
||||
return `${formatNumber(speed / 1000, 2)} km/s`;
|
||||
}
|
||||
return `${formatNumber(speed, 0)} m/s`;
|
||||
}
|
||||
|
||||
export function formatBytes(bytes: number): string {
|
||||
if (bytes >= 1024 * 1024) {
|
||||
return `${(bytes / 1024 / 1024).toFixed(2)} MB`;
|
||||
@@ -71,6 +131,26 @@ export function toThreeVector(vector: Vector3Dto): THREE.Vector3 {
|
||||
return new THREE.Vector3(vector.x, vector.y, vector.z);
|
||||
}
|
||||
|
||||
export function scaleGalaxyScalar(lightYears: number): number {
|
||||
return lightYears * DISPLAY_UNITS_PER_LIGHT_YEAR;
|
||||
}
|
||||
|
||||
export function scaleLocalScalar(kilometers: number): number {
|
||||
return kilometers * DISPLAY_UNITS_PER_KILOMETER;
|
||||
}
|
||||
|
||||
export function scaleGalaxyVector(vector: THREE.Vector3): THREE.Vector3 {
|
||||
return vector.clone().multiplyScalar(DISPLAY_UNITS_PER_LIGHT_YEAR);
|
||||
}
|
||||
|
||||
export function toDisplayGalaxyVector(vector: Vector3Dto): THREE.Vector3 {
|
||||
return scaleGalaxyVector(toThreeVector(vector));
|
||||
}
|
||||
|
||||
export function scaleLocalVector(vector: THREE.Vector3): THREE.Vector3 {
|
||||
return vector.clone().multiplyScalar(DISPLAY_UNITS_PER_KILOMETER);
|
||||
}
|
||||
|
||||
export function currentWorldTimeSeconds(world: WorldState | undefined, worldTimeSyncMs: number): number {
|
||||
if (!world) {
|
||||
return 0;
|
||||
@@ -150,7 +230,7 @@ export function celestialRenderRadius(size: number, scale: number, minRadius: nu
|
||||
}
|
||||
|
||||
export function computeMoonRenderRadius(planet: PlanetSnapshot, moonIndex: number, seed: number): number {
|
||||
return celestialRenderRadius(computeMoonSize(planet, moonIndex, seed), MOON_RENDER_SCALE, 2.5, 1.04);
|
||||
return celestialRenderRadius(computeMoonSize(planet, moonIndex, seed), 0.00011, 0.025, 0.62);
|
||||
}
|
||||
|
||||
export function starHaloOpacity(starKind: string): number {
|
||||
|
||||
Reference in New Issue
Block a user