1
0

The sky the real catalogue served, rather than the one it was assumed to serve

Three fixes, all of them found by pointing the thing at celestrak.org and
looking at what came back.

**The catalogue was 100% Starlink.** The supplemental feed is 10,766
objects against a 6,000-object ceiling, so a first-wins merge spent the
whole budget on it and served a sky with no ISS, no GPS, no weather
satellites — every other group fetched, parsed, and thrown away. `merge`
now reserves the ceiling for the small groups and lets the fill group
take the remainder: 1,044 of everything else and 4,956 Starlinks. The
truncation warning is what caught it, which is the argument for the
no-silent-caps rule; it now reports the split rather than only the fact
that a cap was hit.

**You could get outside the sky.** The dome sat at 1.2 board spans and
the camera orbits to 1.5, so pulling all the way back put the viewer
outside it looking in, with half the constellation behind the camera.
2.0 is inside the far plane at 3.0 and outside the orbit.

**Size attenuation was wrong in principle.** A satellite does not get
bigger because you zoomed the map in. Fixed pixel size, at roughly what a
naked-eye Starlink actually looks like — the attenuated version shrank to
sub-pixel specks at the far end of the zoom and read as noise.

Measured over San Francisco at 21:19 local: 324 above the horizon, 267
fully lit, 96 at local midnight. The terminator does what it should.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-06 21:20:56 -07:00
parent a229fb2721
commit b20fe41a68
4 changed files with 184 additions and 55 deletions
+28 -14
View File
@@ -311,23 +311,37 @@ export interface SatelliteLayer {
/**
* The dome's radius, as a fraction of the board's longest side.
*
* It has to clear the city — a dot inside the buildings would be occluded by
* them, which is the one thing that is definitely wrong — and it has to stay
* inside the camera's far plane, which `scene.ts` sets at three board spans. At
* 1.2 the dome is outside every building and comfortably clear of the far plane
* even with the camera pulled all the way back.
* Three constraints, and the middle one is the one that is easy to miss:
*
* 1. Outside the city, or the buildings occlude the sky.
* 2. **Outside the camera's maximum orbit**, which `scene.ts` sets at
* `boardSpan * 1.5`. This was 1.2 first, which put the far end of the zoom
* *outside the dome* — pull back far enough and you were looking at the sky
* from space, with half the constellation behind the camera. A sky you can
* leave is not a sky.
* 3. Inside the far plane, which `scene.ts` sets at `boardSpan * 3`.
*
* 2.0 sits in the middle of the only window that satisfies all three.
*/
const DOME_RADIUS_FACTOR = 1.2;
const DOME_RADIUS_FACTOR = 2.0;
/**
* How large a dot is drawn, in scene units, before attenuation.
* How large a dot is drawn, in **pixels**, at any camera distance.
*
* Everything on the dome is the same distance away, so `sizeAttenuation` cannot
* separate near from far here the way it does for a starfield — it only makes
* the whole layer shrink as the camera retreats, which is what keeps the sky
* looking like a sky rather than like a fixed-size overlay pasted on the frame.
* `sizeAttenuation` is off, and that is the physically honest choice rather than
* a convenience. Everything on this dome is the same distance away and the dome
* is a stand-in for something 550 km up: a satellite does not get bigger because
* you zoomed the map in, and a point of light at effectively infinite distance
* has an apparent size set by the eye rather than by the range. Attenuation was
* on first and did the wrong thing twice over — it shrank the whole sky as the
* camera pulled back, and at the far end of the zoom it took a 324-object
* constellation down to specks a pixel across that read as noise in the sky
* texture.
*
* Three and a half pixels is about what an actual naked-eye Starlink looks like
* against a dark sky, which is the reference this is aiming at.
*/
const DOT_SIZE_FACTOR = 0.012;
const DOT_PIXELS = 3.5;
/** Ceiling on dots, so the buffers are allocated once and never grow. */
const MAX_DOTS = 4096;
@@ -392,8 +406,8 @@ export function createSatelliteLayer(boardSpan: number): SatelliteLayer {
geo.setDrawRange(0, 0);
const material = new THREE.PointsMaterial({
size: boardSpan * DOT_SIZE_FACTOR,
sizeAttenuation: true,
size: DOT_PIXELS,
sizeAttenuation: false,
vertexColors: true,
transparent: true,
// Dots are drawn over the sky and over each other; letting them write depth