Regenerate the star catalogue, fixing 2331 names and 875 colours
Two ETL bugs, both fixed at the source and then re-run against HYG. Star ids,
ordering and positions are all unchanged, so stars.bin is byte-identical and
every exoplanet cross-reference still resolves.
Names. HYG's `gl` column already carries its own catalogue prefix ("Gl 581",
"GJ 3512"), unlike the bare numbers in `hd` and `hip`, so prefixing it again
produced 2331 of 8750 stars named "Gl GJ 1076". That corrupted three surfaces at
once: search, the on-screen labels, and exoplanet host-star name matching, which
compares normalised names and could never match "glgj1076" to "gj1076".
Colours. `Number(row['ci']) || 0` cannot tell a blank cell from a real zero, and
0 is a real B-V colour index meaning a hot blue-white A-type star. All 875
affected stars turned out to be blanks — the catalogue contains no genuine zero
inside the distance cutoff — so several hundred red dwarfs were rendering
blue-white. colorIndex is now `number | null` rather than defaulted, because any
numeric default is indistinguishable from a measurement.
Consumers resolve the gap from the spectral type instead. That needs real
parsing: HYG's `spect` column runs to 134 distinct spellings among the affected
stars alone, including a bare lowercase "m" for 354 of them, plus "k-m" ranges,
"dM4" luminosity prefixes and "K:" uncertainty flags. 622 of the 875 recover a
class this way — 497 of them M-class — and the remaining 253, which carry no
classification at all, fall back to neutral white.
The parse is anchored at the start of the string rather than scanning it. A scan
is the obvious implementation and is quietly wrong: the ETL writes the literal
"Unknown" for unclassified stars, that contains a K, and every one of those 253
would have been classified as an orange K-type. A test covers it.
Also lifts parseOptionalNumber out of fetchExoplanets into lib/csv, where both
fetchers now use it, and gives magnitude a faint default instead of 0 — no
current star is affected, but 0 would mean "as bright as Vega" and render an
unphotometered star as one of the largest points on the map.
Tests: 145 passing, up from 116, including the first coverage of
StarFieldRenderer. Build, both typechecks and the Playwright suite are green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaySiNst4HhDXBHnMy8p5G
This commit is contained in:
+14
-5
@@ -2,13 +2,19 @@ import { writeFileSync } from 'node:fs';
|
||||
|
||||
import { raDecDistanceToXyz } from '../../src/app/shared/astro/coordinates';
|
||||
import { StarRecord, SUN_STAR_ID } from '../../src/app/shared/models/star.model';
|
||||
import { parseCsvObjects } from './lib/csv';
|
||||
import { parseCsvObjects, parseOptionalNumber } from './lib/csv';
|
||||
import { fetchTextCached } from './lib/http';
|
||||
import { dataPath, ensureDataDir } from './lib/paths';
|
||||
|
||||
const HYG_CSV_URL = 'https://raw.githubusercontent.com/astronexus/HYG-Database/main/hyg/CURRENT/hygdata_v41.csv';
|
||||
const HYG_UNKNOWN_DISTANCE_PC = 100000; // HYG's placeholder for unmeasured/unreliable parallax
|
||||
|
||||
/**
|
||||
* Stand-in magnitude for a star with no photometry. Faint rather than 0, because 0 would mean
|
||||
* "as bright as Vega" and render it as one of the largest points on the map.
|
||||
*/
|
||||
const UNKNOWN_MAGNITUDE = 15;
|
||||
|
||||
/** Stars within this distance (parsecs) of the Sun are kept for the galaxy view. */
|
||||
const DISTANCE_CUTOFF_PC = Number(process.env['ETL_STAR_DISTANCE_PC'] ?? 50);
|
||||
|
||||
@@ -26,7 +32,10 @@ function resolveName(row: Record<string, string>): string {
|
||||
return `HD ${row['hd']}`;
|
||||
}
|
||||
if (row['gl']) {
|
||||
return `Gl ${row['gl']}`;
|
||||
// Already a complete designation ("Gl 581", "GJ 3512"), unlike the bare numbers in `hd`
|
||||
// and `hip` — prefixing it again produced 2331 stars named "Gl GJ 1076", which broke
|
||||
// search, the on-screen labels, and exoplanet host-star name matching alike.
|
||||
return row['gl'];
|
||||
}
|
||||
if (row['hip']) {
|
||||
return `HIP ${row['hip']}`;
|
||||
@@ -51,7 +60,7 @@ export async function fetchStars(): Promise<StarRecord[]> {
|
||||
const distancePc = Number(row['dist']);
|
||||
|
||||
if (id === SUN_STAR_ID) {
|
||||
stars.push({ id, name: 'Sol', x: 0, y: 0, z: 0, magnitude: Number(row['mag']), spectralType: row['spect'] || 'G2V', colorIndex: Number(row['ci']) || 0 });
|
||||
stars.push({ id, name: 'Sol', x: 0, y: 0, z: 0, magnitude: parseOptionalNumber(row['mag']) ?? UNKNOWN_MAGNITUDE, spectralType: row['spect'] || 'G2V', colorIndex: parseOptionalNumber(row['ci']) ?? null });
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -73,9 +82,9 @@ export async function fetchStars(): Promise<StarRecord[]> {
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
magnitude: Number(row['mag']) || 0,
|
||||
magnitude: parseOptionalNumber(row['mag']) ?? UNKNOWN_MAGNITUDE,
|
||||
spectralType: row['spect'] || 'Unknown',
|
||||
colorIndex: Number(row['ci']) || 0
|
||||
colorIndex: parseOptionalNumber(row['ci']) ?? null
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user