Draw the system at true scale, drop the halo, and refuse to enter what is off screen

Three changes to what the system view claims, all of them the same claim: that
the sizes on screen mean something.

**The halo is gone.** It was a sprite sized against the arrival frame — 1.12 AU
for the Sun — so it stayed that wide as the camera closed in and ended up a flat
gradient filling the screen, over the photograph it was meant to dress. It
existed to keep the star visible at a framing that holds the whole system, which
is now handled in pixels instead.

**Bodies are drawn at their own radius.** The old marker size was exaggerated
and scaled to the system span, and clamped: Jupiter and Ganymede both ran past
the ceiling and were drawn at one radius, so every moon orbited inside its
planet, and Phobos and Triton sat entirely within Mars and Neptune. True scale
needs no rule against that — physics already puts a moon outside the planet it
orbits. What it costs is visibility at the arrival framing, where every body is
sub-pixel, so the scene floors each marker at 3 px on screen and holds a moon to
half its planet's drawn size. Measured in the Sun's system: at arrival, planets
3 px and moons 1.5 px, against 3 px for everything before; at Jupiter, the
planet 10.8 px at scale 1 with the Galilean moons on their orbits outside it.

The Sun is drawn at its own radius too. Every other star keeps a size derived
from its innermost orbit, because no stellar radius reaches the app — Gaia's
`radius_gspphot` is the obvious next fetch.

**A click cannot enter a system that is not on screen.** The picker tested depth
but not the frame, and a star's hit area is its drawn size plus a slop, so a
click in the last pixels of the view could fly into a system outside it, with
nothing on screen to explain where it had gone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi
This commit is contained in:
2026-09-21 17:26:32 +02:00
co-authored by Claude Opus 5
parent e45c3b6287
commit 3f0abf8717
6 changed files with 135 additions and 218 deletions
@@ -18,6 +18,8 @@ export interface SystemMember {
id: string;
kind: SystemMemberKind;
marker: THREE.Object3D;
/** For a moon, the id of the body it orbits: what its drawn size is held against. */
parentId?: string;
}
const PLANET_COLOR = new THREE.Color(0.55, 0.75, 1.0);
@@ -126,8 +128,8 @@ function buildOrbitLine(elements: OrbitalElements, kind: SystemMemberKind, frame
* The texture is tiny (see `MARKER_TEXTURE_WIDTH`): a marker is a few pixels across, so what
* survives is essentially its average colour, and generating it costs well under a millisecond.
*/
function buildMarker(kind: SystemMemberKind, radiusKm: number | undefined, systemSpanAu: number, appearance: PlanetAppearance | undefined): THREE.Mesh {
const geometry = new THREE.SphereGeometry(bodyMarkerRadiusAu(radiusKm, systemSpanAu), 16, 12);
function buildMarker(kind: SystemMemberKind, radiusKm: number | undefined, appearance: PlanetAppearance | undefined): THREE.Mesh {
const geometry = new THREE.SphereGeometry(bodyMarkerRadiusAu(radiusKm), 16, 12);
const material = appearance
? new THREE.MeshBasicMaterial({ map: planetTexture(appearance, { width: MARKER_TEXTURE_WIDTH, height: MARKER_TEXTURE_HEIGHT }) })
: new THREE.MeshBasicMaterial({ color: colorForKind(kind) });
@@ -241,7 +243,7 @@ export class SystemOrbitsRenderer {
continue; // orphaned moon reference; skip rather than crash.
}
const moon = this.addMoon(body.id, body.orbit, gmForParent(body.parentBodyId), body.radiusKm, parentTracked, ECLIPTIC_FRAME, appearanceForBody(body, bodies, hostLuminositySolar));
members.push({ id: body.id, kind: 'moon', marker: moon.marker });
members.push({ id: body.id, kind: 'moon', marker: moon.marker, parentId: parent.id });
}
// Every exoplanet in a system shares the same line of sight, so the frame is built once.
@@ -374,7 +376,7 @@ export class SystemOrbitsRenderer {
appearance?: PlanetAppearance
): TrackedTopLevelBody {
const orbitLine = buildOrbitLine(elements, kind, frame);
const marker = buildMarker(kind, radiusKm, this.maxTopLevelSemiMajorAxisAu, appearance);
const marker = buildMarker(kind, radiusKm, appearance);
this.object.add(orbitLine, marker);
this.trackDisposable(orbitLine.geometry, orbitLine.material as THREE.Material);
this.trackDisposable(marker.geometry, marker.material as THREE.Material);
@@ -395,7 +397,9 @@ export class SystemOrbitsRenderer {
): TrackedMoon {
const pivot = new THREE.Group();
const orbitLine = buildOrbitLine(elements, 'moon', frame);
const marker = buildMarker('moon', radiusKm, this.maxTopLevelSemiMajorAxisAu, appearance);
// A moon's own orbit is the thing it must not swallow: drawn at the system's exaggeration it
// is the same size as its planet, and every moon here orbits inside one.
const marker = buildMarker('moon', radiusKm, appearance);
pivot.add(orbitLine, marker);
this.object.add(pivot);
this.trackDisposable(orbitLine.geometry, orbitLine.material as THREE.Material);