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,82 @@
import * as THREE from 'three/webgpu';
import { attribute } from 'three/tsl';
import { StarRecord } from '../../shared/models/star.model';
const MIN_POINT_SIZE = 1.5;
const MAX_POINT_SIZE = 6;
const COLD_STAR_COLOR = new THREE.Color(0.65, 0.75, 1.0);
const NEUTRAL_STAR_COLOR = new THREE.Color(1.0, 1.0, 1.0);
const WARM_STAR_COLOR = new THREE.Color(1.0, 0.6, 0.35);
/**
* Crude but effective B-V color-index -> RGB tint: hot/blue stars (low/negative index) skew
* blue-white, cool/red stars (high index) skew orange-red, matching real spectral colors.
*/
export function colorIndexToRgb(colorIndex: number): THREE.Color {
const t = THREE.MathUtils.clamp((colorIndex + 0.4) / 2.4, 0, 1);
const color = new THREE.Color();
return t < 0.5 ? color.lerpColors(COLD_STAR_COLOR, NEUTRAL_STAR_COLOR, t * 2) : color.lerpColors(NEUTRAL_STAR_COLOR, WARM_STAR_COLOR, (t - 0.5) * 2);
}
/** Brighter stars (lower apparent magnitude) render as bigger points. */
function magnitudeToPointSize(magnitude: number): number {
const t = THREE.MathUtils.clamp(1 - (magnitude + 2) / 12, 0, 1);
return MIN_POINT_SIZE + t * (MAX_POINT_SIZE - MIN_POINT_SIZE);
}
/**
* Builds a `THREE.Points` field from the ETL-generated star positions/index, using a TSL
* `PointsNodeMaterial` whose color/size are driven by per-vertex attributes derived from
* each star's spectral color index and magnitude.
*
* Note: per the Three.js WebGPU backend, point primitives are capped at 1px on native
* WebGPU — `sizeNode` only has a visible effect when `WebGPURenderer` has fallen back to
* its WebGL2 backend. Color variation works on both backends.
*/
export class StarFieldRenderer {
readonly object: THREE.Points;
private readonly geometry: THREE.BufferGeometry;
private readonly material: THREE.PointsNodeMaterial;
constructor(private readonly stars: readonly StarRecord[], positions: Float32Array) {
this.geometry = new THREE.BufferGeometry();
this.geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
const colors = new Float32Array(stars.length * 3);
const sizes = new Float32Array(stars.length);
stars.forEach((star, index) => {
const color = colorIndexToRgb(star.colorIndex);
colors[index * 3] = color.r;
colors[index * 3 + 1] = color.g;
colors[index * 3 + 2] = color.b;
sizes[index] = magnitudeToPointSize(star.magnitude);
});
this.geometry.setAttribute('starColor', new THREE.BufferAttribute(colors, 3));
this.geometry.setAttribute('starSize', new THREE.BufferAttribute(sizes, 1));
this.material = new THREE.PointsNodeMaterial({
colorNode: attribute('starColor', 'vec3'),
sizeNode: attribute('starSize', 'float'),
sizeAttenuation: true,
transparent: true,
depthWrite: false
});
this.object = new THREE.Points(this.geometry, this.material);
}
/** Looks up the HYG star id for a given geometry vertex index (e.g. from a raycast hit). */
starIdAt(vertexIndex: number): number | undefined {
return this.stars[vertexIndex]?.id;
}
dispose(): void {
this.geometry.dispose();
this.material.dispose();
}
}