Add star-map Angular app, ETL pipeline, and caveman plugin

Angular 3D star map (galaxy/system/body views, Three.js rendering,
navigation store) plus the NASA ETL tooling that builds the star,
exoplanet and solar-system datasets, Playwright e2e suite, and the
cs:caveman Claude Code plugin (command, agent, skill).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@
This commit is contained in:
2026-08-03 16:50:10 +02:00
parent 1e1b58b0e9
commit d7e8ea1d4d
99 changed files with 5513 additions and 1 deletions
@@ -0,0 +1,59 @@
import { CartesianCoordinates, distanceBetween, raDegDecDistanceToXyz } from './coordinates';
import { StarRecord } from '../models/star.model';
/** Normalizes a star name for comparison: lowercase, alphanumeric characters only. */
export function normalizeStarName(name: string): string {
return name.toLowerCase().replace(/[^a-z0-9]/g, '');
}
export interface HostStarQuery {
hostname: string;
raDeg: number;
decDeg: number;
distancePc: number;
}
/** Builds a lookup of normalized star name -> star, for fast repeated name matching. */
export function buildStarNameIndex(stars: readonly StarRecord[]): Map<string, StarRecord> {
return new Map(stars.map((star) => [normalizeStarName(star.name), star]));
}
/**
* Cross-references an exoplanet host star to the HYG star index: first by (normalized)
* name, then by nearest-neighbour position matching within `toleranceInPc`. Returns `null`
* when neither approach finds a confident match, rather than guessing.
*
* `nameIndex` should be built once (via {@link buildStarNameIndex}) and reused across calls
* when resolving many queries against the same star list.
*/
export function resolveHostStarId(
query: HostStarQuery,
stars: readonly StarRecord[],
toleranceInPc: number,
nameIndex: Map<string, StarRecord> = buildStarNameIndex(stars)
): number | null {
const byName = nameIndex.get(normalizeStarName(query.hostname));
if (byName) {
return byName.id;
}
if (![query.raDeg, query.decDeg, query.distancePc].every(Number.isFinite)) {
return null;
}
const hostPosition = raDegDecDistanceToXyz(query.raDeg, query.decDeg, query.distancePc);
return findNearestStarWithin(hostPosition, stars, toleranceInPc);
}
function findNearestStarWithin(position: CartesianCoordinates, stars: readonly StarRecord[], toleranceInPc: number): number | null {
let closest: { id: number; distance: number } | null = null;
for (const star of stars) {
const distance = distanceBetween(position, star);
if (distance <= toleranceInPc && (!closest || distance < closest.distance)) {
closest = { id: star.id, distance };
}
}
return closest ? closest.id : null;
}