Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
node_modules
dist
.git
.github
coverage
*.log
.DS_Store
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
node_modules
dist
coverage
.DS_Store
*.local
.env
*.tsbuildinfo
22 changes: 22 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# SolarSystem agent guide

SolarSystem is a client-only React, TypeScript, Vite, and React Three Fiber educational orbital explorer.

## Durable architecture rules

- Physical and simulated positions use AU in a J2000-style ecliptic frame. Rendering scale never belongs in astronomy data or orbit providers.
- The simulated UTC timestamp is the source of truth. Derive orbit and rotation state from it; do not accumulate per-frame body movement.
- Keep astronomical math deterministic, testable, and independent from React and Three.js.
- Address bodies by stable IDs and resolve parent/child orbits through the generic hierarchy.
- Keep authoritative/scientific data separate from visual metadata and document data sources.
- Treat the Keplerian model as educational approximation, not an authoritative ephemeris.

## Commands

- `npm run dev` — Vite development server
- `npm test` — unit test suite
- `npm run typecheck` — strict TypeScript check
- `npm run build` — production build
- `docker compose up --build` — production container at port 8080

Use explicit TypeScript types, avoid `any`, add math tests with new orbital behavior, and update `docs/HANDOFF.md` after substantial work.
13 changes: 13 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM node:24-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM nginx:1.28-alpine AS production
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --quiet --tries=1 --spider http://127.0.0.1:8080/healthz || exit 1
52 changes: 51 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,51 @@
# SolarSystem
# SolarSystem

An interactive, browser-based 3D Solar System explorer. The initial milestone includes the Sun, eight planets, Pluto, Earth's Moon, deterministic time controls, selection, camera focus, and three complementary views.

The 3D view is an **educational Keplerian model**, not an authoritative ephemeris. See [project scope](docs/PROJECT.md) and [architecture](docs/ARCHITECTURE.md).

## Local development

Requires Node.js 24 or a current supported Node.js release.

```bash
npm ci
npm run dev
```

Vite prints the local development URL. Run verification with:

```bash
npm test
npm run typecheck
npm run build
npm run preview
```

## Docker

The production image uses a Node build stage and serves only the compiled static app from nginx on HTTP port `8080`.

```bash
docker build -t solarsystem:local .
docker run --name solarsystem -p 8080:8080 -d solarsystem:local
```

Open `http://localhost:8080`. Confirm health with `curl http://localhost:8080/healthz`, then remove the container with:

```bash
docker stop solarsystem
docker rm solarsystem
```

Alternatively, `docker compose up --build -d` starts the same stateless service with its restart policy and health check; `docker compose down` stops it.

### Unraid

Install the image as a normal Docker container and map host port `8080` (or another available host port) to container TCP port `8080`. It needs no volume, database, privileged mode, host networking, or special capabilities.

### Cloudflare Tunnel

Keep the tunnel outside this repository. Point the public hostname's tunnel service at the container's local HTTP endpoint, such as `http://<container-or-host>:8080`. TLS termination and credentials remain with Cloudflare infrastructure.

The Dockerfile is compatible with future publication as `ghcr.io/kevinatlee/solarsystem`; registry automation is intentionally deferred.
13 changes: 13 additions & 0 deletions compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
services:
solarsystem:
build: .
image: solarsystem:local
ports:
- "8080:8080"
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://127.0.0.1:8080/healthz"]
interval: 30s
timeout: 3s
start_period: 5s
retries: 3
34 changes: 34 additions & 0 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Architecture

## Modules

- `src/data`: typed physical, informational, visual, and J2000 orbital data.
- `src/domain`: reusable celestial-body and vector contracts.
- `src/math`: canonical conversions and Julian-date utilities.
- `src/orbits`: interchangeable circular/Keplerian providers plus generic hierarchy composition.
- `src/simulation`: the central timestamp/rate clock and deterministic rotation calculation.
- `src/visualization`: mode definitions and AU-to-scene display transforms.
- `src/scene`: React Three Fiber rendering, orbit paths, labels, selection, and camera controls.
- `src/ui`: ordinary React control and information surfaces.

## Simulation and coordinates

The canonical state is a UTC timestamp in milliseconds. Each rendered update follows `timestamp → orbital provider → local position → hierarchy composition → visualization transform → Three.js`. Nothing increments a planet around its orbit, so pause, reversal, and arbitrary jumps do not accumulate drift.

Astronomical distances and positions use AU; radii use km; orbital periods use days; rotation periods use hours; angles use radians. Planet vectors are heliocentric or parent-relative in an ecliptic/J2000-style frame. Scene axes map ecliptic `(x, y, z)` to Three.js `(x, z, y)`, making the ecliptic plane horizontal.

## Orbital providers and hierarchy

`OrbitalPositionProvider` accepts a typed body and timestamp and returns a parent-relative AU vector. Shared element propagation uses supplied JPL per-century rates—including mean anomaly—while bodies without rates fall back to period-based phase progression. The circular provider is deliberately illustrative but uses propagated mean longitude (`M + ω + Ω`) for a defensible coplanar phase. The Keplerian provider solves Kepler's equation, derives orbital-plane coordinates, and applies node/inclination/periapsis rotations. `calculateSystemPositions` composes any parent chain by stable ID; Earth/Moon is the first proof of this generic mechanism. A future Horizons, DE, or SPICE adapter can implement the same provider boundary.

## Visualization and scale

Modes choose the provider and layout intent. `toScenePosition` is the boundary where physical AU positions become view coordinates. Distance scale and body radius scale are independent controls with separate calculations: true-distance ratios can coexist with either readable exaggerated bodies or an orbit-safe compact presentation. Selecting true distance chooses compact bodies by default, while the explicit body-size control keeps the exaggerated option available. True-distance mode retains the Moon's physical parent-relative vector in simulation state; when enlarged rendered radii would overlap, a visualization-only minimum separation derived from the two display radii and a small gap moves the satellite marker and orbit path outward along that same vector. The lineup bypasses orbital positions entirely, performs a display-only size layout, and uses neutral presentation lighting rather than the orbital Sun light.

Orbit paths are a presentation concern with off, subdued unified-colour, and tasteful body-colour states. The Keplerian scene retains true orbital inclination, an angled camera, and a subtle labelled J2000 ecliptic reference plane; vertical geometry is not exaggerated.

## Data sources and limits

The eight major planets use JPL Solar System Dynamics, [Approximate Positions of the Planets](https://ssd.jpl.nasa.gov/planets/approx_pos.html), Table 1 (1800–2050), including fitted rates. The UI warns, but does not block exploration, when the Keplerian date is outside that fitted interval. Pluto uses NASA/NSSDC's [Pluto Fact Sheet](https://nssdc.gsfc.nasa.gov/planetary/factsheet/plutofact.html) fixed J2000 mean elements (a≈39.4817 AU, e≈0.2488, i≈17.14°), with period-based propagation; it is an educational approximation rather than the fitted-rate planetary model. Physical radii, rotation periods, and tilts are rounded from NASA/NSSDC planetary fact sheets. The Moon uses standard educational mean elements. Two-body elements omit perturbations and should never be presented as observation-grade coordinates.

The visual layer updates only this small initial body set through React today. If catalogs reach hundreds or thousands of objects, migrate high-frequency body matrices to instancing and mutable render-loop buffers without changing the provider/domain boundaries.
61 changes: 61 additions & 0 deletions docs/HANDOFF.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Handoff

## State

- Branch: `codex/initial-architecture`
- Initial implementation commit: `4df27b2`
- Latest visual-polish commit: `653436a`
- Review: [PR #1](https://github.com/kevinatlee/SolarSystem/pull/1) (open, intentionally unmerged)
- Initial vertical slice implemented: typed body catalog including Pluto, circular and Keplerian providers, generic Earth/Moon hierarchy, deterministic clock/rotation, three visual modes, selection/details/focus, scale/orbit controls, responsive UI, tests, and Docker/nginx deployment.

## Verification

- `npm test`: 18/18 passing, including element-rate, mean-longitude phase, long-frame-gap, body-scale policy, and True Distance satellite-separation regressions
- `npm run typecheck`: passing
- `npm run build`: passing (expected Three.js bundle-size warning)
- Browser smoke test: passing for all bidirectional speed presets, direction switching, out-of-range warning, and focus action; no runtime errors
- Latest visual-polish browser automation: unavailable in the current agent session because the in-app Browser control runtime was not exposed; local development HTTP smoke returned 200. Hands-on verification remains required for lineup lighting, True Distance inner bodies, orbit display states, and angled inclination presentation.
- Production preview: root and direct `/explore/earth` navigation both returned HTTP 200
- Docker: execution not available because the host has no `docker` command; Dockerfile, Compose, nginx health endpoint, and SPA fallback were inspected but not executed

## Decisions and limitations

- AU/J2000-style ecliptic simulation data stays separate from scene scaling.
- JPL approximate elements are educational, not ephemeris-grade; Moon elements are mean approximations.
- Body radii are exaggerated outside lineup; lineup explicitly applies nonlinear radius compression. Under True Distance, the Moon retains its physical simulation vector but may receive a visualization-only minimum display separation when enlarged body radii would overlap.
- Camera focus eases to a body; persistent follow mode and high-volume instancing are future work.

## Pre-merge review corrections

- Propagate JPL mean anomaly from its supplied per-century rate, retaining period fallback for the Moon.
- Base simplified circular phase on propagated mean longitude.
- Preserve full elapsed time across long animation-frame gaps.
- Release camera control after a one-shot focus transition or immediate user interaction.
- Provide forward and reverse variants of every speed magnitude.
- Warn when the Keplerian date is outside JPL's 1800–2050 fitted interval.

## Recommended next task

Final review and hands-on interaction/container testing before squash merge. A subsequent visual milestone can add better procedural materials and camera-follow behavior without changing the simulation layer.

## Hands-on visual review polish

- Size Lineup uses neutral hemisphere/key/fill lighting so every body remains evenly legible and three-dimensional.
- Body size is an explicit presentation control; True Distance selects the compact orbit-safe policy by default while retaining readable exaggeration as an option.
- Orbit display supports Off, subdued Unified colour, and tasteful Body colours.
- Visualization-mode changes ease to appropriate camera presets; the Keplerian preset is angled over a subtle labelled J2000 ecliptic reference without changing true inclinations or exaggerating Z.
- Verification: 17 tests, strict typecheck, and production build pass; local development HTTP smoke returns 200. Focused visual browser automation remains pending due to unavailable Browser control in this session.

## Targeted True Distance satellite correction

- Compact Earth/Moon rendering now derives a minimum visual satellite separation from their rendered radii plus a small gap, consistently for the marker and orbit path.
- The Moon's `localPositionAu` remains the unmodified Keplerian parent-relative orbital result.
- Verification: 18 tests, strict typecheck, and production build pass.

## Pluto and hands-on transparency update

- Pluto is a selectable dwarf planet with a Sun-parented fixed J2000 educational orbit, rotation, label, facts, and all three visualization-mode representations.
- Pluto intentionally has no JPL fitted orbital rates; its source and period-based propagation are documented separately from the eight-planet 1800–2050 model.
- Size Lineup now places the Moon next to Earth with radius-derived clearance.
- Active Model dynamically lists the active mode's faithful geometry, readability adjustments, and JPL/Pluto data limitations.
- Verification: 20 tests, strict typecheck, and production build pass.
26 changes: 26 additions & 0 deletions docs/PROJECT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Product overview

SolarSystem makes the scale and motion of our planetary system approachable through multiple explicit visual interpretations. Its simulation truth remains independent from how a view transforms distance and body size.

## Current scope

- Sun, eight planets, Pluto, and Earth's Moon
- current-time initialization and arbitrary UTC date/time selection
- play, pause, forward, reverse, speed presets, and reset to now
- selectable bodies, educational facts, coordinates, and camera focus
- simplified circular overview with compressed distances
- body-size lineup with intentionally compressed Sun scale
- 3D educational Keplerian view with ellipse shape and orbital orientation
- Docker/nginx self-hosting

## Visualization terminology

- **Simplified Solar System:** circular, coplanar, visibly enlarged bodies; approximate phase positions.
- **Planet lineup:** side-by-side size comparison; orbital distance is irrelevant and radii use an explicitly nonlinear compression to keep the full range readable.
- **Educational Keplerian model:** deterministic two-body approximation using J2000-style orbital elements. It is not a JPL Horizons/DE/SPICE ephemeris.
- **True distance:** preserves orbital-distance ratios while still exaggerating body radii.
- **Compressed distance:** applies a display-only nonlinear transform so outer planets remain explorable.

## Future scope

Additional moons and minor-body categories, category filtering, camera following, improved body materials, runtime configuration when actually needed, and a replaceable high-accuracy ephemeris provider. Accounts, a backend, n-body physics, SPICE integration, and large catalogs are non-goals for this milestone.
14 changes: 14 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#060b15" />
<meta name="description" content="Explore the Solar System through simplified, size-comparison, and educational Keplerian views." />
<title>SolarSystem — interactive orbital explorer</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
23 changes: 23 additions & 0 deletions nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
server {
listen 8080;
listen [::]:8080;
server_name _;
root /usr/share/nginx/html;
index index.html;

location = /healthz {
access_log off;
add_header Content-Type text/plain;
return 200 'healthy\n';
}

location /assets/ {
try_files $uri =404;
expires 1y;
add_header Cache-Control "public, immutable";
}

location / {
try_files $uri $uri/ /index.html;
}
}
Loading