Files
star-map/.junie/plans/nasa-star-map.md
T
Senrokai d7e8ea1d4d @
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>
@
2026-08-03 16:50:10 +02:00

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

  1. Rendering stack: Angular + Three.js WebGPURenderer using TSL (Three.js Shading Language) node materials. WebGPURenderer automatically falls back to a WebGL2 backend when WebGPU is unavailable, so this is chosen over the legacy WebGLRenderer to be future-aligned while keeping broad compatibility. Rendering runs inside NgZone.runOutsideAngular() to avoid change-detection overhead on every animation frame.
  2. 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.
  3. 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.
  4. 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-scale and system-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.
  5. 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 binary Float32Array buffer (stars.bin) + a small stars-index.json (id, name, spectral type, magnitude, buffer offset).
  • fetchSolarSystem.ts — queries JPL Horizons/SSD for planets/major moons, extracts orbital elements, writes bodies.json.
  • fetchExoplanets.ts — queries the NASA Exoplanet Archive TAP service (Planetary Systems table) for confirmed exoplanets + host star coordinates, cross-references host stars to the HYG index by name/coordinates, writes exoplanets.json.
  • fetchDeepSky.ts — pulls a nebula/galaxy catalog (OpenNGC/Messier) with RA/Dec/distance, writes deepsky.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.js WebGPURenderer, the render loop (outside NgZone), and resize handling. Injected once per canvas host.
  • GalaxySystemSceneComponent — hosts the shared continuous scene for Galaxy + System views; owns CameraRigController that animates between galaxy-scale and system-scale framing (the floating-origin recenter step lives here).
  • StarFieldRenderer — builds a THREE.Points/instanced mesh from stars.bin with 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) converting OrbitalElements + 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 an InfoPanelComponent showing its data.
  • SearchComponent — text search across stars-index.json, bodies.json, exoplanets.json; on match, dispatches a navigation action.
  • NavigationStore (Angular signals-based) — holds viewLevel: '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.ts run produces stars.bin/stars-index.json/bodies.json/exoplanets.json/deepsky.json with no missing/undefined required fields.
  • coordinates.ts RA/Dec/parallax → XYZ conversion matches known reference values (e.g. Sirius, Proxima Centauri positions) within a small tolerance.
  • kepler.ts propagator 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' and selectedStarId set).
  • Selecting a body navigates to BodyDetailSceneComponent with the correct selectedBodyId and 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.ts and kepler.ts pure functions.
  • Unit tests for ETL cross-referencing logic (fetchExoplanets.ts host-star matching) using mocked API fixtures.
  • Component tests for NavigationStore state 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, shared folders per the agreed file structure.
  • Implement EngineService that creates a THREE.WebGPURenderer, a base Scene/PerspectiveCamera, and a render loop running via NgZone.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.ts to download the HYG catalog and convert RA/Dec/parallax to XYZ (parsecs) via shared/astro/coordinates.ts, packing results into stars.bin + stars-index.json.
  • Implement tools/etl/fetchSolarSystem.ts to pull orbital elements for solar-system bodies from JPL Horizons/SSD into bodies.json.
  • Implement tools/etl/fetchExoplanets.ts to query the NASA Exoplanet Archive TAP Planetary Systems table and cross-reference host stars to the HYG index, writing exoplanets.json.
  • Implement tools/etl/fetchDeepSky.ts for nebula/galaxy catalog data into deepsky.json.
  • Implement tools/etl/build.ts to 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 StarFieldRenderer to load stars.bin/stars-index.json and 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.ts Kepler propagator converting OrbitalElements + epoch to Cartesian position.
  • Implement SystemOrbitsRenderer to draw orbit ellipses and planet/exoplanet markers for the star in NavigationStore.selectedStarId, loading matching records from bodies.json/exoplanets.json.
  • Implement CameraRigController with the floating-origin recenter step and animated transition between galaxy-scale and system-scale framing.
  • Wire NavigationStore.viewLevel toggling between 'galaxy' and 'system' to drive GalaxySystemSceneComponent.

✓ 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 BodyDetailSceneComponent as a separate route with its own EngineService-backed scene focused on one selected body.
  • Implement InfoPanelComponent displaying the body's real data (radius, mass, orbital elements, kind).
  • Wire body selection from SystemOrbitsRenderer to update NavigationStore.selectedBodyId and 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 SearchComponent querying stars-index.json, bodies.json, and exoplanets.json for name matches.
  • On selecting a search result, dispatch the appropriate NavigationStore update (galaxy star, system body, or exoplanet) and trigger the corresponding camera transition or route change.
  • Ensure consistent state across GalaxySystemSceneComponent and BodyDetailSceneComponent when navigation originates from search rather than in-scene clicks.