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:
@@ -5,6 +5,7 @@ import {
|
||||
computePlanetLocalPosition,
|
||||
currentWorldTimeSeconds,
|
||||
resolveOrbitalAnchorPosition,
|
||||
toDisplayGalaxyVector,
|
||||
toThreeVector,
|
||||
} from "./viewerMath";
|
||||
import { describeActiveSpace } from "./viewerSelection";
|
||||
@@ -20,6 +21,7 @@ import type {
|
||||
LocalBubbleSnapshot,
|
||||
ResourceNodeDelta,
|
||||
ResourceNodeSnapshot,
|
||||
ShipSnapshot,
|
||||
} from "./contracts";
|
||||
import type {
|
||||
BubbleVisual,
|
||||
@@ -52,6 +54,7 @@ export interface WorldOrbitalContext {
|
||||
|
||||
export interface WorldPresentationContext extends WorldOrbitalContext {
|
||||
activeSystemId?: string;
|
||||
zoomLevel: ZoomLevel;
|
||||
orbitYaw: number;
|
||||
camera: THREE.PerspectiveCamera;
|
||||
systemFocusLocal: THREE.Vector3;
|
||||
@@ -79,12 +82,19 @@ export interface GameStatusParams {
|
||||
export function updateWorldPresentation(context: WorldPresentationContext) {
|
||||
const now = performance.now();
|
||||
const worldTimeSeconds = currentWorldTimeSeconds(context.world, context.worldTimeSyncMs);
|
||||
const renderMode = resolveRenderSpaceMode(context.activeSystemId, context.zoomLevel);
|
||||
|
||||
for (const [shipId, visual] of context.shipVisuals.entries()) {
|
||||
const ship = context.world?.ships.get(shipId);
|
||||
if (!ship) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const visual of context.shipVisuals.values()) {
|
||||
const worldPosition = getAnimatedShipLocalPosition(visual, now);
|
||||
visual.mesh.setPosition(context.toDisplayLocalPosition(worldPosition, visual.systemId));
|
||||
const displayPosition = resolveShipWorldPosition(context, ship, visual, worldPosition);
|
||||
visual.mesh.setPosition(displayPosition);
|
||||
visual.icon.setPosition(rawObject(visual.mesh).position.clone());
|
||||
const shipVisible = visual.systemId === context.activeSystemId;
|
||||
const shipVisible = isShipVisible(renderMode, context.activeSystemId, ship);
|
||||
visual.mesh.setVisible(shipVisible);
|
||||
visual.icon.setVisible(shipVisible && rawObject(visual.icon).visible);
|
||||
const desiredHeading = resolveShipHeading(visual, worldPosition, context.orbitYaw);
|
||||
@@ -148,6 +158,49 @@ export function updateWorldPresentation(context: WorldPresentationContext) {
|
||||
updateSystemSummaryPresentation(context.systemSummaryVisuals, context.camera, context.activeSystemId);
|
||||
}
|
||||
|
||||
type RenderSpaceMode = "galaxy" | "system" | "local";
|
||||
|
||||
function resolveRenderSpaceMode(activeSystemId: string | undefined, zoomLevel: ZoomLevel): RenderSpaceMode {
|
||||
if (!activeSystemId || zoomLevel === "universe") {
|
||||
return "galaxy";
|
||||
}
|
||||
|
||||
return zoomLevel === "local" ? "local" : "system";
|
||||
}
|
||||
|
||||
function isShipVisible(mode: RenderSpaceMode, activeSystemId: string | undefined, ship: ShipSnapshot) {
|
||||
if (ship.spatialState.movementRegime === "ftl-transit") {
|
||||
return mode === "galaxy";
|
||||
}
|
||||
|
||||
if (!activeSystemId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ship.systemId === activeSystemId;
|
||||
}
|
||||
|
||||
export function resolveShipWorldPosition(
|
||||
context: Pick<WorldPresentationContext, "world" | "toDisplayLocalPosition">,
|
||||
ship: ShipSnapshot,
|
||||
visual: ShipVisual,
|
||||
animatedLocalPosition = getAnimatedShipLocalPosition(visual),
|
||||
) {
|
||||
if (ship.spatialState.movementRegime === "ftl-transit") {
|
||||
const destinationNodeId = ship.spatialState.transit?.destinationNodeId;
|
||||
const destinationNode = destinationNodeId ? context.world?.spatialNodes.get(destinationNodeId) : undefined;
|
||||
const originSystem = context.world?.systems.get(ship.systemId);
|
||||
const destinationSystem = destinationNode ? context.world?.systems.get(destinationNode.systemId) : undefined;
|
||||
if (originSystem && destinationSystem) {
|
||||
const origin = toDisplayGalaxyVector(originSystem.galaxyPosition);
|
||||
const destination = toDisplayGalaxyVector(destinationSystem.galaxyPosition);
|
||||
return origin.lerp(destination, THREE.MathUtils.clamp(ship.spatialState.transit?.progress ?? 0, 0, 1));
|
||||
}
|
||||
}
|
||||
|
||||
return context.toDisplayLocalPosition(animatedLocalPosition, ship.systemId);
|
||||
}
|
||||
|
||||
export function updateSystemSummaries(world: WorldState | undefined, systemSummaryVisuals: Map<string, SystemSummaryVisual>) {
|
||||
if (!world) {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user