Frame a giant so its disc sits inside the ring its neighbours are named on

The system view names a star's neighbours on a ring at 0.74 of the view's tighter half-extent,
whatever the star. A giant was framed to fill the frame, 2.4 radii out, which the three-radius
closest approach then overrode, so it settled at 3 radii with its disc projecting to 0.758 of the
half-extent: in the app, Betelgeuse's disc was 379 px on a 1 000 px view against the 370 px ring,
and HD 39374 and HD 38118 were printed on it at about 1.3:1 contrast.

The star's term in the framing distance now places its silhouette, asin(R / d), at half the
tighter half-extent, which puts the camera 4.4 radii out on a 50° view. Measured on :4302 at
1600 by 1000: Betelgeuse settles at 13.9 AU (closest approach 9.5) with a 250 px disc and Antares at
14.1 AU; the nearest corner of any neighbour's name is 292 px from the centre, so none is on
either disc (it was two and one of them before). Closing in to the closest approach can still bring
the disc over the names; that is the viewer's choice, not the arrival's.

This also gives the star's term a job: at 2.4 radii it never exceeded the closest approach, so
dropping it changed nothing (the previous commit's one surviving control). Controls, each failing
its named test: the star left out of the framing (1 of 788 failed), the star framed to fill the
frame (2 of 788), and the disc taken as flat (1 of 788).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-29 20:23:43 +02:00
co-authored by Claude Opus 5.5
parent 5aac46de08
commit d1aa22ed61
3 changed files with 25 additions and 2 deletions
@@ -989,6 +989,8 @@ describe('GalaxySystemSceneComponent camera-flight transitions', () => {
expect(radius / SUN_RADIUS_AU).toBeLessThan(760);
expect(scene.controls.minDistance).toBeCloseTo(closestApproachAu(radius), 9);
expect(scene.controls.minDistance).toBeGreaterThan(9);
// Settled where its disc stays inside the ring of its neighbours' names, not pressed up to it.
expect(engine.getCamera().position.length()).toBeGreaterThan(1.2 * scene.controls.minDistance);
});
it('draws a star nothing gives a size or temperature for as a grey point, not as the Sun', async () => {
@@ -63,6 +63,17 @@ describe('systemFramingDistanceAu', () => {
expect(systemFramingDistanceAu(0, undefined, 0.00465)).toBe(systemFramingDistanceAu(0));
});
it("holds a giant's disc inside the ring its neighbours are named on, and the camera clear of its closest approach", () => {
// The ring is at 0.74 of the tighter half-extent; the disc is kept to half of it, in either window.
const betelgeuseAu = 2.72;
for (const viewport of [{ fovDegrees: 50, aspect: 1.6 }, { fovDegrees: 50, aspect: 0.6 }]) {
const distance = systemFramingDistanceAu(0, viewport, betelgeuseAu);
const tight = Math.tan((25 * Math.PI) / 180) * Math.min(1, viewport.aspect);
expect(Math.tan(Math.asin(betelgeuseAu / distance)) / tight).toBeCloseTo(0.5, 9);
expect(distance).toBeGreaterThan(closestApproachAu(betelgeuseAu));
}
});
it('caps the distance so a far-flung companion cannot shrink the star to nothing', () => {
expect(systemFramingDistanceAu(1000)).toBe(systemFramingDistanceAu(5000));
});
@@ -59,6 +59,15 @@ const MIN_FRAMING_DISTANCE_AU = 0.06;
*/
const MAX_FRAMING_DISTANCE_AU = 200;
/**
* How much of the view's tighter half-extent a giant's disc may take on arrival: inside the ring
* the system view names the star's neighbours on, at 0.74 of it, and clear of the names hung
* inward from it, whose nearest corners come within 292 px of the centre on a 1 000 px view.
* Framed to fill the frame instead, Betelgeuse settled at the three-radius closest approach with
* a disc of 379 px, past the 370 px ring, and its neighbours' names on it.
*/
const STAR_FRAME_FRACTION = 0.5;
/** Framing for a star with no known planets, where there is nothing to fit. */
const EMPTY_SYSTEM_FRAMING_DISTANCE_AU = 3;
@@ -116,8 +125,9 @@ export function systemFrameRadiusAu(distanceAu: number, viewport: SystemViewport
*/
export function systemFramingDistanceAu(framedRadiusAu: number, viewport: SystemViewport = DEFAULT_SYSTEM_VIEWPORT, starRadiusAu = 0): number {
// A giant drawn at its own radius can be wider than the system around it — Betelgeuse's 584
// solar radii are 2.7 AU — or than the empty framing, and the camera must not settle inside it.
const star = (starRadiusAu * (1 + FRAME_MARGIN)) / tightHalfExtent(viewport);
// solar radii are 2.7 AU — or than the empty framing. Its disc is a sphere's, whose silhouette
// from d subtends asin(R / d): the distance that makes it the fraction above of the view.
const star = starRadiusAu * Math.sqrt(1 + 1 / (STAR_FRAME_FRACTION * tightHalfExtent(viewport)) ** 2);
if (!Number.isFinite(framedRadiusAu) || framedRadiusAu <= 0) {
return Math.max(EMPTY_SYSTEM_FRAMING_DISTANCE_AU, star);
}