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> @
18 KiB
sessionId
| sessionId |
|---|
| session-260715-175938-19ek |
Requirements
Overview & Goals
Build an interactive, web-based 3D star map in the visual style of Star Citizen's in-game starmap, but populated with real NASA/astronomical data instead of fictional systems. Users can browse a galaxy-scale view of nearby stars, drill into an individual star system to see its planets, and inspect a specific body (planet/moon/exoplanet) in detail.
This is a greenfield project — the current star-map repo only contains an unrelated Claude Code plugin marketplace (agents/commands/skills), so there is no existing app code to build on.
Scope
In Scope
- Angular web app rendering a 3D scene with Three.js (
WebGPURenderer, TSL node materials, automatic WebGL2 fallback). - Build-time ETL pipeline that fetches NASA/astronomy data sources once and bakes them into static assets:
- Nearby stars (HYG database — combined Hipparcos/Yale/Gliese catalog) → galaxy-scale star field.
- Solar-system bodies (JPL Horizons/SSD) → orbital elements for planets/moons.
- Exoplanets (NASA Exoplanet Archive, Planetary Systems TAP table) → attached to host stars.
- Deep-sky objects (nebulae/galaxies, e.g. OpenNGC/Messier) → galaxy-view backdrop.
- Galaxy overview: node/point field of stars, pan/zoom/rotate camera, click-to-select, labels.
- System drill-down: orbit ellipses and planet/exoplanet markers computed from orbital elements, reached via a continuous camera-flight transition from the galaxy view.
- Body detail view: focused scene + info panel for a selected planet/moon/exoplanet (radius, mass, orbital data, NASA facts).
- Search/filter UI to jump directly to a star, system, or body.
Out of Scope
- Live/runtime querying of NASA APIs from the browser (data is pre-baked at build time).
- Gameplay mechanics (travel time, fuel, missions) — this is an exploration/visualization tool, not a game.
- User accounts, persistence, or multiplayer features.
- Full Gaia catalog (millions of stars) — HYG's curated subset is used for performance.
User Stories
- As a space enthusiast, I want to see a 3D map of nearby real stars so I can explore the stellar neighborhood the way I'd explore Star Citizen's map.
- As a user, I want to click a star and zoom smoothly into its system to see its real planets and exoplanets.
- As a user, I want to click a planet/moon to see detailed real NASA data about it.
- As a user, I want to search for a star or planet by name and jump straight to it.
- As a user, I want visual cues (glow, size, color) that reflect real stellar/planetary properties (spectral type, magnitude, radius).
Functional Requirements
- Galaxy view renders all HYG stars within a reasonable distance (e.g. ≤ a few hundred light-years) as an interactive point/instanced field with correct relative 3D positions derived from RA/Dec/distance.
- Selecting a star triggers a camera-flight transition into that system's view (not an instant scene swap) for a fluid, Star-Citizen-like feel.
- System view renders the star, its planets (from JPL orbital elements) and any known exoplanets, with orbit paths drawn as ellipses.
- Selecting a body opens a body-detail view/route with a dedicated scene and an info panel of real data.
- Search returns matches across stars, planets, and exoplanets by name and navigates to the correct view.
- The app must run in a modern browser without a WebGPU-capable GPU (auto-fallback to WebGL2 via
WebGPURenderer).
Non-Functional Requirements
- Initial star-field load should be fast; star position data is delivered as a compact binary buffer, not verbose JSON, to keep payload size and parse time low.
- Rendering must stay interactive (target 60fps on mid-range hardware) for the galaxy view's star count.
- ETL pipeline is re-runnable (idempotent) so data can be refreshed periodically without code changes.
Technical Design
Current Implementation
The repository currently contains only an unrelated Claude Code plugin marketplace (agents/, commands/, skills/, README.md). There is no existing frontend, backend, or data pipeline code — this design starts from a clean slate and defines the initial project structure.
Key Decisions
- Rendering stack: Angular + Three.js
WebGPURendererusing TSL (Three.js Shading Language) node materials.WebGPURendererautomatically falls back to a WebGL2 backend when WebGPU is unavailable, so this is chosen over the legacyWebGLRendererto be future-aligned while keeping broad compatibility. Rendering runs insideNgZone.runOutsideAngular()to avoid change-detection overhead on every animation frame. - Data pipeline: Build-time static ETL (chosen over a runtime backend). A standalone Node/TS script (
tools/etl) fetches all NASA/astronomy sources once (or on a schedule) and writes optimized static assets (src/assets/data/) that the Angular app loads directly — no backend server required at runtime. - Zoom/navigation architecture: Hybrid model. Galaxy view and System view share one continuous scene and camera that flies smoothly between the two scales (matching Star Citizen's fluid zoom). Body-detail view is a separate, focused scene/route, since its content (a single close-up object) and camera needs are unrelated to the galaxy/system camera rig.
- Multi-scale coordinate handling: Galaxy view operates in parsecs, System view in AU — roughly an 8-order-of-magnitude difference that causes floating-point precision and clipping problems if rendered naively in one Three.js unit space. Solution: maintain two independent coordinate scales (
galaxy-scaleandsystem-scale) with a re-centering ("floating origin") step on every camera-driven scale transition — the active system's star is recentered to the origin before switching the camera's near/far planes and unit scale, avoiding z-fighting/jitter at either extreme. - Planet positions are computed, not fetched live: JPL data provides orbital elements (semi-major axis, eccentricity, inclination, etc.), not per-frame positions. Positions are derived client-side via a simplified Kepler propagator against an app-level "current epoch" value, which also allows optional time-scrubbing later.
Data Pipeline (ETL)
tools/etl/ (Node + TypeScript, run via npm run etl):
fetchStars.ts— downloads the HYG database (Hipparcos/Yale/Gliese combined catalog), converts RA/Dec/parallax → Cartesian XYZ (parsecs), filters by distance cutoff, packs into a binaryFloat32Arraybuffer (stars.bin) + a smallstars-index.json(id, name, spectral type, magnitude, buffer offset).fetchSolarSystem.ts— queries JPL Horizons/SSD for planets/major moons, extracts orbital elements, writesbodies.json.fetchExoplanets.ts— queries the NASA Exoplanet Archive TAP service (Planetary Systemstable) for confirmed exoplanets + host star coordinates, cross-references host stars to the HYG index by name/coordinates, writesexoplanets.json.fetchDeepSky.ts— pulls a nebula/galaxy catalog (OpenNGC/Messier) with RA/Dec/distance, writesdeepsky.json.build.ts— orchestrates the above and validates output (no missing cross-references, reasonable file sizes).- Output lands in
src/assets/data/and is committed/regenerated like any other static asset.
Data Models / Contracts
interface StarRecord {
id: number;
name: string;
x: number; y: number; z: number; // parsecs, galaxy-scale, Sun at origin
magnitude: number;
spectralType: string;
colorIndex: number;
}
interface OrbitalElements {
semiMajorAxisAu: number;
eccentricity: number;
inclinationDeg: number;
longitudeOfAscendingNodeDeg: number;
argumentOfPeriapsisDeg: number;
meanAnomalyAtEpochDeg: number;
epochJd: number;
}
interface BodyRecord {
id: string;
systemStarId: number;
name: string;
kind: 'planet' | 'moon' | 'dwarf';
radiusKm: number;
orbit: OrbitalElements;
}
interface ExoplanetRecord {
id: string;
hostStarId: number | null; // null if not cross-referenced
name: string;
radiusEarth?: number;
massEarth?: number;
orbit: Partial<OrbitalElements>;
}
interface DeepSkyRecord {
id: string;
name: string;
kind: 'nebula' | 'galaxy' | 'cluster';
x: number; y: number; z: number; // parsecs
angularSizeDeg: number;
}
Components
EngineService(core/engine/engine.service.ts) — owns the Three.jsWebGPURenderer, the render loop (outsideNgZone), and resize handling. Injected once per canvas host.GalaxySystemSceneComponent— hosts the shared continuous scene for Galaxy + System views; ownsCameraRigControllerthat animates between galaxy-scale and system-scale framing (the floating-origin recenter step lives here).StarFieldRenderer— builds aTHREE.Points/instanced mesh fromstars.binwith a TSL-based glow/color node material driven by magnitude and spectral type.SystemOrbitsRenderer— draws orbit ellipses and planet/exoplanet markers for the currently focused star system, using the Kepler propagator.KeplerPropagator(shared/astro/kepler.ts) — pure function(s) convertingOrbitalElements+ epoch → Cartesian position; independently unit-testable.BodyDetailSceneComponent— separate route/component with its own dedicated scene for a close-up view of one selected body, plus anInfoPanelComponentshowing its data.SearchComponent— text search acrossstars-index.json,bodies.json,exoplanets.json; on match, dispatches a navigation action.NavigationStore(Angular signals-based) — holdsviewLevel: 'galaxy' | 'system',selectedStarId,selectedBodyId; consumed by scene components and routed body-detail view.
File Structure
tools/
etl/
fetchStars.ts
fetchSolarSystem.ts
fetchExoplanets.ts
fetchDeepSky.ts
build.ts
src/
assets/data/
stars.bin
stars-index.json
bodies.json
exoplanets.json
deepsky.json
app/
core/
engine/
engine.service.ts
features/
galaxy-system/
galaxy-system-scene.component.ts
star-field-renderer.ts
system-orbits-renderer.ts
camera-rig-controller.ts
body-detail/
body-detail-scene.component.ts
info-panel.component.ts
search/
search.component.ts
shared/
astro/
kepler.ts
coordinates.ts
models/
star.model.ts
body.model.ts
exoplanet.model.ts
deepsky.model.ts
state/
navigation.store.ts
Architecture Diagram
graph TD
subgraph ETL["Build-time ETL (tools/etl)"]
HYG[HYG Star Catalog] --> Fetch1[fetchStars.ts]
JPL[JPL Horizons/SSD] --> Fetch2[fetchSolarSystem.ts]
EXO[NASA Exoplanet Archive TAP] --> Fetch3[fetchExoplanets.ts]
NGC[OpenNGC/Messier] --> Fetch4[fetchDeepSky.ts]
Fetch1 --> Build[build.ts]
Fetch2 --> Build
Fetch3 --> Build
Fetch4 --> Build
Build --> Static[src/assets/data/*.bin,*.json]
end
subgraph App["Angular App"]
Static --> DataLoader[Data Loader Service]
DataLoader --> NavStore[NavigationStore]
NavStore --> GalaxyScene[GalaxySystemSceneComponent]
NavStore --> BodyScene[BodyDetailSceneComponent]
GalaxyScene --> Engine[EngineService: WebGPURenderer]
BodyScene --> Engine
Search[SearchComponent] --> NavStore
GalaxyScene -- selects star --> NavStore
GalaxyScene -- selects body --> BodyScene
end
Risks
- WebGPURenderer + TSL maturity: the node-material pipeline is still evolving; some effects (custom shaders, post-processing) may need TSL rewrites rather than legacy
ShaderMaterial. Mitigated by relying mostly on built-in node materials for stars/orbits. - Float precision at galaxy scale: mitigated via the floating-origin recenter strategy described in Key Decisions.
- Data cross-referencing: exoplanets from the Exoplanet Archive must be matched to HYG host stars by name/coordinates; some may fail to match and should be flagged rather than silently dropped (
hostStarId: null). - NASA API rate limits/availability: ETL scripts should cache raw responses locally so re-runs don't always hit live endpoints.
Testing
Validation Approach
Since this is a new build, validation focuses on (a) correctness of the astronomical math and ETL output, and (b) the interactive scene behaving as specified.
Key Scenarios
- ETL
build.tsrun producesstars.bin/stars-index.json/bodies.json/exoplanets.json/deepsky.jsonwith no missing/undefined required fields. coordinates.tsRA/Dec/parallax → XYZ conversion matches known reference values (e.g. Sirius, Proxima Centauri positions) within a small tolerance.kepler.tspropagator reproduces expected planet positions for simple test cases (e.g. Earth's position at a known epoch) within tolerance.- Clicking a star in the galaxy view triggers a camera-flight transition and lands in the correct system view (
NavigationStore.viewLevel === 'system'andselectedStarIdset). - Selecting a body navigates to
BodyDetailSceneComponentwith the correctselectedBodyIdand populated info panel. - Search returns and navigates to the correct entity for star/body/exoplanet name queries.
Edge Cases
- Exoplanets whose host star can't be matched to a HYG record (
hostStarId: null) are excluded from system view but don't crash the app. - Systems with zero known planets/exoplanets still render (star only, no orbit renderer errors).
- WebGPU unavailable in the test browser: renderer falls back to WebGL2 without throwing.
Test Changes
- Unit tests (Jest/Karma per Angular defaults) for
coordinates.tsandkepler.tspure functions. - Unit tests for ETL cross-referencing logic (
fetchExoplanets.tshost-star matching) using mocked API fixtures. - Component tests for
NavigationStorestate transitions (galaxy → system → body).
Delivery Steps
✓ Step 1: Scaffold Angular project and Three.js WebGPU rendering foundation
An Angular app boots and renders an empty Three.js scene via WebGPURenderer with automatic WebGL2 fallback.
- Generate the Angular workspace (standalone components, routing enabled) with
src/app/core,features,sharedfolders per the agreed file structure. - Implement
EngineServicethat creates aTHREE.WebGPURenderer, a baseScene/PerspectiveCamera, and a render loop running viaNgZone.runOutsideAngular(). - Implement a canvas host component that attaches the renderer to a
<canvas>and handles resize. - Add a placeholder starfield (procedural points) just to prove the render loop, camera, and resize logic work end-to-end.
✓ Step 2: Build the NASA/astronomy data ETL pipeline
Running npm run etl produces the static data files consumed by the app.
- Implement
tools/etl/fetchStars.tsto download the HYG catalog and convert RA/Dec/parallax to XYZ (parsecs) viashared/astro/coordinates.ts, packing results intostars.bin+stars-index.json. - Implement
tools/etl/fetchSolarSystem.tsto pull orbital elements for solar-system bodies from JPL Horizons/SSD intobodies.json. - Implement
tools/etl/fetchExoplanets.tsto query the NASA Exoplanet Archive TAPPlanetary Systemstable and cross-reference host stars to the HYG index, writingexoplanets.json. - Implement
tools/etl/fetchDeepSky.tsfor nebula/galaxy catalog data intodeepsky.json. - Implement
tools/etl/build.tsto orchestrate all fetch scripts, cache raw API responses, and validate output completeness.
✓ Step 3: Implement the galaxy view star field with selection and labels
Users can pan/zoom/rotate a real star field and click a star to select it.
- Implement
StarFieldRendererto loadstars.bin/stars-index.jsonand build an instanced/points mesh with a TSL node material driving glow/color from magnitude and spectral type. - Implement camera pan/zoom/rotate controls for the galaxy scale.
- Implement picking (raycast or GPU picking) to select a star on click, updating
NavigationStore.selectedStarId. - Implement label overlays (CSS2D or DOM overlay) showing star names near the camera focus.
✓ Step 4: Implement system view and the galaxy-to-system camera transition
Selecting a star flies the camera smoothly into that system, showing its real planets and orbits.
- Implement
shared/astro/kepler.tsKepler propagator convertingOrbitalElements+ epoch to Cartesian position. - Implement
SystemOrbitsRendererto draw orbit ellipses and planet/exoplanet markers for the star inNavigationStore.selectedStarId, loading matching records frombodies.json/exoplanets.json. - Implement
CameraRigControllerwith the floating-origin recenter step and animated transition between galaxy-scale and system-scale framing. - Wire
NavigationStore.viewLeveltoggling between'galaxy'and'system'to driveGalaxySystemSceneComponent.
✓ Step 5: Implement the body detail view and info panel
Selecting a planet/moon/exoplanet opens a dedicated close-up scene with real data.
- Implement
BodyDetailSceneComponentas a separate route with its ownEngineService-backed scene focused on one selected body. - Implement
InfoPanelComponentdisplaying the body's real data (radius, mass, orbital elements, kind). - Wire body selection from
SystemOrbitsRendererto updateNavigationStore.selectedBodyIdand navigate to the body-detail route.
✓ Step 6: Implement search and cross-view navigation
Users can search by name and jump directly to the matching star, system, or body.
- Implement
SearchComponentqueryingstars-index.json,bodies.json, andexoplanets.jsonfor name matches. - On selecting a search result, dispatch the appropriate
NavigationStoreupdate (galaxy star, system body, or exoplanet) and trigger the corresponding camera transition or route change. - Ensure consistent state across
GalaxySystemSceneComponentandBodyDetailSceneComponentwhen navigation originates from search rather than in-scene clicks.