Keep a body page's camera on its Sun's side when the clock takes the Sun across the equator, aimed at the body in that frame

The page put its camera on the side of the equator its Sun lights once, when a body was shown.
The Clock tab the same page gained in 74535ac changes the date without showing the body again, so
on Saturn's page, opened today with the Sun and the camera south of the rings, Go to 2045 moved
the Sun 25.7 degrees north (sunLight y +3.063) and left the camera south (-0.6): the unlit face
of the rings, the view the camera side was there to avoid. A clock left running through the May
2025 equinox did the same.

tick now remembers which side the Sun stood on at the last frame, and moves the camera across
only when that side changes: when a body is shown (the side is reset to 0 then) and when the Sun
crosses the equator, as the clock runs or is set. Between crossings the camera is the reader's to
orbit, below the rings if they like. The move came after the controls had aimed the camera for
the frame, which was drawn straight after with the body 22.6 degrees (2 atan(0.6/3)) off the
middle of the view; the camera now looks at the controls' target again before that frame.

Live on :4301 (this worktree's ng serve), Saturn's page opened today: camera -0.6, Sun -0.935.
Clock tab, 2045-06-01T00:00, Go: camera +0.6, Sun +3.063, the rings' lit face; 265 frames drawn
across the move, every one aimed within 0.00 degrees of Saturn. The camera then put south by
hand stayed there (-0.6 a second later). On Earth's page the Sun crosses twice a year, so at a
month a second the camera changes side about every six seconds.

Tests: the page followed into 2045 with the camera north and aimed at Saturn in that frame; and a
camera the reader takes over the rings while the Sun stays south is left there, which the old
single-flip rule only held through the flag it cleared and no test covered. Guarded mutants (full
suite), each failing only its named test: the side chosen only when a body is shown ('follows the
Sun across Saturn's equator'); the side never remembered, so the camera is mirrored back every
frame ('leaves the camera where the reader orbits it'); no lookAt after the move ('follows the
Sun across', on the aim).

Unit suite 860/860, tsc -p tsconfig.app.json and etl:typecheck clean.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-30 17:27:27 +02:00
co-authored by Claude Opus 5.5
parent 2f49002027
commit a9e910c2ea
2 changed files with 41 additions and 9 deletions
@@ -181,4 +181,28 @@ describe('BodyDetailSceneComponent', () => {
expect(page.sunLight.position.y).toBeGreaterThan(0);
expect(camera.position.y).toBeGreaterThan(0);
});
it('follows the Sun across Saturn’s equator when the clock is set past the 2039 equinox, and aims at Saturn in that same frame', async () => {
await open('saturn', '2032-06-01T12:00Z');
const camera = engine.getCamera();
expect(camera.position.y).toBeLessThan(0);
// What the page's own Clock tab does: 2045, the Sun 25.7 degrees north of the rings.
time.setDate(new Date('2045-06-01T12:00Z'));
engine.tick(0.016);
expect(page.sunLight.position.y).toBeGreaterThan(0);
expect(camera.position.y).toBeGreaterThan(0);
// The frame drawn straight after the move: aimed from where the camera was, it had Saturn 22.6
// degrees off the middle of the view.
const toSaturn = new THREE.Vector3().sub(camera.position);
expect(camera.getWorldDirection(new THREE.Vector3()).angleTo(toSaturn)).toBeLessThan(1e-9);
});
it('leaves the camera where the reader orbits it while the Sun stays on one side', async () => {
await open('saturn', '2032-06-01T12:00Z');
const camera = engine.getCamera();
// Taken over the rings, to their unlit face, on purpose.
camera.position.y = 0.6;
engine.tick(0.016);
expect(camera.position.y).toBeGreaterThan(0);
});
});