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:
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -104,8 +104,8 @@ export class BodyDetailSceneComponent implements AfterViewInit, OnDestroy {
|
||||
readonly notFound = signal(false);
|
||||
/** The date the body is drawn for, as the dock's strip prints it; empty at the present. */
|
||||
readonly date = signal('');
|
||||
/** Set when a body is shown, until the next frame has put the camera on its Sun's side. */
|
||||
private cameraToSunSide = false;
|
||||
/** The side of the equator the Sun stood on at the last frame, 1 north or -1 south; 0 once a body is shown. */
|
||||
private sunSide = 0;
|
||||
|
||||
constructor(
|
||||
private readonly engine: EngineService,
|
||||
@@ -193,7 +193,7 @@ export class BodyDetailSceneComponent implements AfterViewInit, OnDestroy {
|
||||
this.body = this.bodies.find((body) => body.id === viewModel.id);
|
||||
this.planet?.rotation.set(0, 0, 0);
|
||||
this.sunLight?.position.copy(SUN_LIGHT_POSITION);
|
||||
this.cameraToSunSide = true;
|
||||
this.sunSide = 0;
|
||||
|
||||
this.disposeRing();
|
||||
this.disposeGlow();
|
||||
@@ -295,13 +295,21 @@ export class BodyDetailSceneComponent implements AfterViewInit, OnDestroy {
|
||||
} else {
|
||||
this.planet.rotation.y += deltaSeconds * 0.08;
|
||||
}
|
||||
if (this.cameraToSunSide) {
|
||||
// Above or below the equator, whichever side the Sun is on. Held above it, the page opened
|
||||
// Saturn on the unlit face of its rings from 2025 until 2039, while the Sun is south of
|
||||
// them — the face Earth does not see either.
|
||||
const sunSide = this.sunLight.position.y < 0 ? -1 : 1;
|
||||
if (sunSide !== this.sunSide) {
|
||||
// Above or below the equator, whichever side the Sun is on, when a body is shown and again
|
||||
// whenever the Sun crosses it, as the clock runs or is set: held above it, the page opened
|
||||
// Saturn on the unlit face of its rings from 2025 until 2039, while the Sun is south of them —
|
||||
// the face Earth does not see either — and the Clock set to 2045 left it on the other one.
|
||||
// Between crossings the camera is the reader's to orbit where they like.
|
||||
this.sunSide = sunSide;
|
||||
const camera = this.engine.getCamera();
|
||||
camera.position.y = Math.abs(camera.position.y) * (this.sunLight.position.y < 0 ? -1 : 1);
|
||||
this.cameraToSunSide = false;
|
||||
camera.position.y = Math.abs(camera.position.y) * sunSide;
|
||||
// Aimed again before this frame is drawn: the controls aimed it from where it was, and the
|
||||
// frame drawn from here otherwise had the body 22.6 degrees off the middle of the view.
|
||||
if (this.controls) {
|
||||
camera.lookAt(this.controls.target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user