Fill in the Sun's faint neighbours from Gaia, and fold the Gliese entries they repeat

Gaia's G < 12 cut took a quarter of what lies within 10 pc: the red,
brown and white dwarfs most of the neighbourhood is made of, which the
map only had where Gliese happened to list them. Teegarden's Star was
missing, and with it its three planets' host.

A second query fetches the complement out to 50 pc: G >= 12 or no G,
parallax > 20 mas, with pmra/pmdec so the J2016 -> J2000 propagation
applies. The quality filter was chosen by counting. Parallax over error
> 5 keeps 39 751 of the 39 764 sources that pass the floor, but the Gaia
Catalogue of Nearby Stars (GCNS; Gaia Collaboration, Smart et al. 2021)
rejects 11 752 of them as spurious: median G 20.2, astrometric excess
noise 5.4 mas against 0.15 for the ones it keeps, 6 933 toward the
Galactic centre. So the query joins the GCNS main table (EDR3 astrometry
and source ids, which DR3 carries unchanged) and keeps 28 012 rows; the
error cut stays and costs no GCNS source, brown dwarfs included. The
query has its own row-count floor (28 012) and the row-limit cap, and is
ordered by (phot_g_mean_mag, source_id); a second ETL run reproduced
stars.bin, stars-meta.bin and stars-index.json byte for byte. Its ids
start at 1 050 000 000, clear of the main query's and under 2^30, which
V8 keeps unboxed: numbered from 2 000 000 000 they made the app's boot
task 230 ms longer (medians of five interleaved runs, 1.41 s against
1.18). validateStars now refuses an id outside 0 to 2^30.

The Gliese entries these stars duplicate were not folded: HYG carries
them with positions off by up to a minute of arc and photometric
distances, so they missed the 15" tolerance or failed the distance test.
Their proper motions, which Gliese measured well, give them away:
isSameStar now takes two entries moving within 20 % of each other as one
star up to 60" apart, whatever their distances, brightness still
permitting. Of the 602 Gliese-only rows left without a counterpart, 253
have such a Gaia entry; with every entry shifted a quarter degree, none
does. fetchStars passes HYG's motions only for rows without Hipparcos
astrometry: given them too, 15 Hipparcos stars took a co-moving
companion's Gaia entry and the cross-catalogue pairs under an arcsecond
went from 23 to 35. Of HYG's 1 200 stars fainter than V 12 within 25 pc,
1 024 now sit on a Gaia position (325 before); of the 176 left alone, 43
still have a Gaia entry 3-60" away (218 without the motion rule), some of
them real companions.

Measured on the rebuilt catalogue, against the GCNS (sources with
parallax > 100, 40 and 20 mas):
  within 10 pc  336 -> 372  (GCNS 312; the map adds 60 HYG-only stars)
  within 25 pc  3 652 -> 5 560  (GCNS 5 111)
  within 50 pc  13 702 -> 40 916  (GCNS 40 231)
452 331 stars (+27 214). Proxima, Barnard's Star, Wolf 359, Rigel, Deneb
and Alnilam are all present by name; Teegarden's Star is Gaia DR3
35227046884571776 at 3.83 pc and hosts its three planets. Luhman 16 is
not in Gaia DR3 with a parallax (5353626573555863424 has a two-parameter
solution) and stays absent. 94 more exoplanets find a host (2 071), none
changes host. TRAPPIST-1 is now drawn (Gaia DR3 2635476908753563008,
12.47 pc) but its planets are not yet matched to it.

Merge gate, ceilings unchanged: HYG rows without a Gaia counterpart
12 352 -> 11 554 (ceiling 15 000; 10 886 before the naked-eye stars),
cross-catalogue pairs under an arcsecond 23 -> 23 (ceiling 100). The
gate's comment now accounts for the survivors by magnitude.

gzip -9 sizes against the catalogue before both changes: stars.bin
4 711 922 -> 5 030 741 B, stars-meta.bin 2 576 213 -> 2 784 614 B,
stars-index.json 3 724 855 -> 4 003 584 B (+806 KB, 7.3 %). Boot on the
dev server, five interleaved cold runs: the task that indexes the
catalogue after the data lands, median 1 072 -> 1 182 ms; HUD shown,
median 2 622 -> 2 716 ms. This machine measured 0.82-1.30 s for the
same baseline task today, above audit #25's 627-843 ms. The drawn-star
budget is unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-24 20:35:21 +02:00
co-authored by Claude Opus 5.5
parent c64eea0803
commit 74b93a0428
10 changed files with 163 additions and 33 deletions
+26
View File
@@ -79,6 +79,32 @@ describe('isSameStar', () => {
expect(isSameStar(companion, at(43109, 131.69 + arcsecOfRa(2.7, 6.42), 6.42, 40, { name: 'Ashlesha', magnitude: 3.38 }))).toBe(false);
});
// GJ 1035 and GJ 3052 as HYG has them from Gliese, against their Gaia entries: 21″ away at
// about the same distance, and 6.6″ away at half Gaia's distance. Their motions agree to 2 and 3 %.
it('matches a Gliese entry to the Gaia entry moving with it, a minute of arc away or at another distance', () => {
const gj1035 = at(1, 19.9245, 84.1612, 14.4, { magnitude: 13.1, source: 'gaia', pmRaMasYr: -981.9, pmDecMasYr: 475.6 });
const gliese1035 = at(118058, 19.9245 + arcsecOfRa(21.2, 84.1612), 84.1612, 13.7, { name: 'GJ 1035', magnitude: 14.77, pmRaMasYr: -978.0, pmDecMasYr: 458.1 });
expect(isSameStar(gj1035, gliese1035)).toBe(true);
expect(isSameStar(gj1035, { ...gliese1035, pmRaMasYr: undefined, pmDecMasYr: undefined })).toBe(false);
const gj3052 = at(2, 11.0897, 9.1262, 25.0, { magnitude: 12.6, source: 'gaia', pmRaMasYr: 813.1, pmDecMasYr: -2.6 });
const gliese3052 = at(118014, 11.0897 + arcsecOfRa(6.6, 9.1262), 9.1262, 12.3, { name: 'GJ 3052', magnitude: 13.8, pmRaMasYr: 799.7, pmDecMasYr: -20.9 });
expect(isSameStar(gj3052, gliese3052)).toBe(true);
});
it('does not match two entries moving differently past fifteen arcseconds, nor co-moving ones past a minute', () => {
const kept = at(1, 120, 30, 10, { magnitude: 12, source: 'gaia', pmRaMasYr: 1000, pmDecMasYr: 0 });
expect(isSameStar(kept, at(2, 120 + arcsecOfRa(20, 30), 30, 10, { magnitude: 13, pmRaMasYr: 750, pmDecMasYr: 0 }))).toBe(false);
expect(isSameStar(kept, at(2, 120 + arcsecOfRa(20, 30), 30, 10, { magnitude: 13, pmRaMasYr: 850, pmDecMasYr: 0 }))).toBe(true);
expect(isSameStar(kept, at(2, 120 + arcsecOfRa(65, 30), 30, 10, { magnitude: 13, pmRaMasYr: 1000, pmDecMasYr: 0 }))).toBe(false);
});
it("keeps a co-moving primary out of its companion's entry", () => {
// A binary shares its motion, so only the brightness tells GJ 9160 from its companion 13.5″ away.
const companion = at(1, 69.54, -14.3, 24.4, { magnitude: 15.1, source: 'gaia', pmRaMasYr: -78.5, pmDecMasYr: -150.8 });
expect(isSameStar(companion, at(2, 69.54 + arcsecOfRa(13.5, -14.3), -14.3, 24.4, { magnitude: 7.3, pmRaMasYr: -78.5, pmDecMasYr: -150.8 }))).toBe(false);
});
it('matches on direction rather than on 3D proximity', () => {
// The distinction the merge rests on. These two are 60 pc apart in space and are the same
// star; a 3D-proximity test would have to be so loose it swallowed real neighbours.
+30 -4
View File
@@ -47,6 +47,22 @@ export const MERGE_ANGULAR_TOLERANCE_DEG = 15 / 3600;
*/
export const MERGE_CERTAIN_ANGULAR_TOLERANCE_DEG = 3 / 3600;
/**
* Angular separation, in degrees, within which two entries that cross the sky together are one
* star, and how closely their proper motions must agree (as a fraction of the kept entry's) to
* say so. Past fifteen arcseconds, and past a distance conflict, a Gliese-only entry is still
* often the same star: its position is off by up to a minute of arc and its distance is
* photometric — GJ 1035 sits 21″ from its Gaia entry, GJ 3052 at half Gaia's distance. What
* gives them away is their motion, which Gliese measured well: a few per cent from Gaia's.
*
* Once the nearby faint Gaia stars joined, 253 of the 602 Gliese-only stars left without a
* counterpart had a Gaia entry within a minute of arc moving within a fifth of their own motion;
* shifted a quarter of a degree, none did. Brightness still has its say, so a co-moving companion
* is not folded into its primary, and `fetchStars` gives HYG's motions only to those rows.
*/
export const MERGE_COMOVING_ANGULAR_TOLERANCE_DEG = 60 / 3600;
export const MERGE_PROPER_MOTION_TOLERANCE = 0.2;
/**
* How much fainter, and how much brighter, an entry may be than the one it is folded into and
* still be the same star. Bands differ, and not symmetrically: a red dwarf is three magnitudes
@@ -159,10 +175,19 @@ export function directionCosine(a: StarRecord, b: StarRecord): number {
return Math.max(-1, Math.min(1, ax * bx + ay * by + az * bz));
}
/** Whether both entries have a proper motion and `entry`'s is within tolerance of `kept`'s. */
function movesWith(kept: StarRecord, entry: StarRecord): boolean {
if (kept.pmRaMasYr === undefined || kept.pmDecMasYr === undefined || entry.pmRaMasYr === undefined || entry.pmDecMasYr === undefined) {
return false;
}
const difference = Math.hypot(entry.pmRaMasYr - kept.pmRaMasYr, entry.pmDecMasYr - kept.pmDecMasYr);
return difference < MERGE_PROPER_MOTION_TOLERANCE * Math.hypot(kept.pmRaMasYr, kept.pmDecMasYr);
}
/**
* Whether `entry` describes the star already `kept`: the same direction, the brightness not in
* conflict and — unless the directions agree closely enough to settle it — the distance not in
* conflict either.
* conflict and — unless the directions agree closely enough to settle it, or the two move
* together — the distance not in conflict either.
*/
export function isSameStar(kept: StarRecord, entry: StarRecord): boolean {
const [near, far] = [distanceOf(kept), distanceOf(entry)].sort((p, q) => p - q);
@@ -175,7 +200,8 @@ export function isSameStar(kept: StarRecord, entry: StarRecord): boolean {
}
const separationDeg = Math.acos(directionCosine(kept, entry)) / DEG_TO_RAD;
if (separationDeg > MERGE_ANGULAR_TOLERANCE_DEG) {
const comoving = movesWith(kept, entry);
if (separationDeg > (comoving ? MERGE_COMOVING_ANGULAR_TOLERANCE_DEG : MERGE_ANGULAR_TOLERANCE_DEG)) {
return false;
}
const fainterBy = entry.magnitude - kept.magnitude;
@@ -183,7 +209,7 @@ export function isSameStar(kept: StarRecord, entry: StarRecord): boolean {
return false;
}
if (separationDeg <= MERGE_CERTAIN_ANGULAR_TOLERANCE_DEG) {
if (comoving || separationDeg <= MERGE_CERTAIN_ANGULAR_TOLERANCE_DEG) {
return true;
}
+7
View File
@@ -26,6 +26,13 @@ export interface StarRecord {
* the better-measured parallax wins.
*/
source?: string;
/**
* Proper motion in milliarcseconds a year, in right ascension (times cos δ) and declination,
* where the source measured one. Only the ETL sets these, for `star-merge.ts` to recognise two
* entries of one star whose positions disagree; the assets do not carry them.
*/
pmRaMasYr?: number;
pmDecMasYr?: number;
}
/** HYG id used for the Sun itself, so solar-system bodies can reference their host star. */