A browser-native, scientifically grounded exploration of the universe at every scale.
Open a tab. Fall into Mercury. Pull the camera back through the asteroid belt, past Pluto, out through the Local Bubble, across the Milky Way, beyond Andromeda, until the cosmic web fills the sky. No installs. No video files. Every pixel is math.
The universe is the most interesting object humans have ever measured, and almost nobody can fly through it.
The polished tools that exist either cost money (SpaceEngine), require an install (Celestia), or pin you to one scale at a time (Stellarium for the night sky, Solar System Scope for the planets, Aladin for catalogues). None of them runs in a browser tab. None of them lets a high-school student go from "what does Titan's surface look like" to "where is M87's jet pointed" in the same camera. None of them treats the universe as one continuous thing.
Cosmos Explorer is what happens when you treat the observable universe as a single scene — moons, planets, asteroids, comets, stars, nebulae, galaxies, clusters, voids, filaments, and the cosmic microwave background, all reachable from one camera, all rendered the same way: procedural GLSL shaders running on the visitor's GPU.
Three commitments shape every line of code:
- Real data, every time. Positions come from Gaia DR3, JPL DE441, MPC, NASA Exoplanet Archive, and the SDSS/HyperLEDA catalogues. Coordinates are ICRS J2000.0. Planetary ephemerides go through NASA SPICE kernels when sub-kilometre accuracy is wanted.
- No textures, no shortcuts. Every surface — Mars's dust storms, Jupiter's belts, the Crab Nebula's filaments, M51's spiral arms, Sagittarius A*'s accretion disc — is generated by a procedural shader. The download stays tiny; the zoom stays infinite.
- The web is the platform. No Electron, no native client, no GPU driver gymnastics. WebGL 2.0 and a logarithmic depth buffer. If your laptop renders a YouTube video, it will render the universe.
The result is meant to work for three audiences at once:
- The curious can wander. The opening view is the Local Bubble; one drag and you're at Earth, two scrolls and you're inside Saturn's rings, a search and you're at the Crab Pulsar.
- Educators get a single tool that crosses scales — orbital mechanics, stellar evolution, galactic morphology, and large-scale structure are all the same camera move apart.
- Researchers and tinkerers get an inspectable, MIT-style stack. Every shader is a
.fragfile in this repo. Every catalogue is a TypeScript table or a binary tile. Fork it, replace the cosmology model, point the tile server at a different dataset.
Everything below boots in the browser from pnpm --filter web dev:
| Demo | URL flag | What you see |
|---|---|---|
| Unified scene (default) | / |
Stellar regime with procedural starfield + named-entity markers + working search + time controls + audio |
| Solar system | ?demo=solarsystem |
Sun + 8 planets + 5 dwarf planets + 21 named moons + 260 minor bodies, Kepler-propagated at up to 10⁹× real-time, Saturn's rings shaded by particle density |
| 100 K-star tile stream | ?demo=starfield |
The browser fetches binary star tiles, decodes them in a worker, and uploads a THREE.Points cloud — 16 tiles, ~1.5 MB total, one draw call per tile |
| Planets | ?demo=planets |
Procedural PBR for the 8 solar planets plus exoplanet archetypes — surface noise, atmosphere scattering, ring shading, all GLSL |
| Moons | ?demo=moons |
Galilean moons, Titan, Enceladus, Triton, the irregular Trojan family, sesquinary captures |
| Nebulae | ?demo=nebulae |
Volumetric raymarched HII (Hα/OIII/SII), reflection, dark, planetary, supernova remnant |
| Galaxies | ?demo=galaxies |
Spiral, elliptical, irregular, lenticular morphologies — logarithmic arms, Sérsic bulges, dust lanes, HII knots |
| Exotic objects | ?demo=exotic |
Black hole with weak-field Schwarzschild lensing (background bends), pulsar with rotating lighthouse beams, magnetar with twisted-dipole helical field |
| Cosmic web | ?demo=cosmicweb |
Filaments + voids + cluster nodes + Planck-2018-styled CMB boundary sphere |
Other things that already work end-to-end:
- Search — autocomplete with 100-query LRU, full-text, cone search (RA/Dec/radius), keyboard navigation, race-guarded by request id so a slower earlier keystroke can't overwrite the latest.
- Fly-to — cubic-Bezier with ease-in-out and perpendicular-projection obstacle avoidance; camera lands on the lit hemisphere; Escape cancels mid-flight.
- Time —
playbackSpeed = sim_seconds/real_seconds, log10 slider from 1× to 10⁹×, jump presets (±1 hr / day / month / year / decade), reverse + reset-to-now, Julian↔Gregorian conversion good through BCE. - Live ephemeris — frontend pulls a 730-day window from
/v1/ephemeris/range/{naifId}, cubic-Catmull-Rom interpolates between samples for sub-km chord error, falls back to client-side Kepler when the service is down. - WebSocket streaming — typed dispatch for
ephemeris_push,tile_priority,data_version_update; exponential-backoff reconnect; cache busts on version bumps. - Tile streaming — three-tier lookup (in-memory → IndexedDB via Dexie → network), priority queue, LRU eviction on a 192 MB GPU budget and 500 MB disk budget, AbortController on regime change.
- Procedural audio — Tone.js scale-regime soundscapes that crossfade through Solar System → Stellar → Galactic → Cosmic, master / ambient / effects channels, mute, AUD-001 user-gesture compliance.
- Post-processing — HDR Float16 buffer → bloom pyramid → ACES tonemap → optional FXAA / chromatic aberration / film grain / scanlines / vignette / Schwarzschild lensing, every effect a flat
#defineso the composite shader stays branchless. - Accessibility — skip links, reduced-motion media query → static visuals, high-contrast media query → CRT effect off, aria-live announcements on every selection / fly-to / search.
- Tests — 700+ Vitest unit tests across the web app, 18 Rust integration tests on the tile server, Playwright E2E covering search → fly-to → ephemeris → WebSocket happy-paths.
What is not yet built (and what the roadmap covers): full Gaia DR3 ingestion, the 4 M HyperLEDA / SDSS galaxy tile pyramid, the 1.3 M MPC asteroid catalogue, deployed backend services, and the polish pass on the long tail of exotic-object shaders.
┌────────────────────────────────────────────────────────────────────┐
│ apps/web — React 18 + Three.js r184 + Zustand + Vite │
│ ──────────────────────────────────────────────── │
│ React UI (declarative) ← Zustand stores → Three.js rAF │
│ ↕ │
│ Web Workers │
│ tile decoding · search · physics │
└──────────────┬─────────────────────────────────────────────────────┘
│ HTTP/2 binary tiles + WebSocket (viewport / time)
┌──────────────┴─────────────────────────────────────────────────────┐
│ apps/api — Fastify API gateway │
│ apps/tile-server — Rust / axum, 50k req/s target │
│ apps/ephemeris — FastAPI + SpiceyPy (JPL SPICE / DE441) │
│ apps/etl — Python ingestion (Gaia DR3, SDSS, MPC, JPL) │
│ │
│ PostgreSQL 16 + PostGIS 3.4 · Redis 7 · Elasticsearch 8 │
└────────────────────────────────────────────────────────────────────┘
The two-loop problem. React's reconciliation loop and Three.js's requestAnimationFrame loop are different paradigms running side by side. They bridge through Zustand stores using a state-temperature model — hot state (camera, time) read via getState() with zero subscriptions, warm state (selection, search) throttled at 100 ms, cool state (mode, regime) via standard React subscriptions, cold state (settings, bookmarks) persisted to IndexedDB.
Camera-relative rendering. Every frame subtracts the camera's world position before uploading geometry to the GPU, so float32 doesn't collapse 13.8 Gly away.
Logarithmic depth buffer. A standard 24-bit depth buffer z-fights from astronomical unit scale to gigaparsec scale. gl_FragDepth = log2(z) * logDepthBufFC * 0.5 solves it; every shader writes it under #ifdef USE_LOGARITHMIC_DEPTH_BUFFER.
Scale-regime state machine. Solar System ↔ Stellar ↔ Galactic ↔ Cosmic, each transition gated by a hysteresis band that swaps LOD, tile filters, and ambient audio without flicker.
# 1. Copy the environment template.
cp .env.example .env
# 2. Install dependencies (pnpm workspaces).
pnpm install
# 3. Frontend only — runs against mocked API, every demo works.
pnpm --filter web dev
# 4. With backend (Postgres + Redis + Elasticsearch + API + tile-server + ephemeris).
docker compose -f infra/docker/docker-compose.yml up -d
pnpm --filter web devOpen http://localhost:5173. Try ?demo=solarsystem, ?demo=nebulae, ?demo=exotic to see the gallery scenes.
Useful commands
pnpm test # Vitest across the workspace
pnpm --filter web test:e2e # Playwright
pnpm typecheck # tsc --noEmit, strict
pnpm lint # ESLint + Prettier
pnpm build # Turborepo production buildcosmos-explorer/
├── apps/
│ ├── web/ # React + Three.js renderer
│ ├── api/ # Fastify API gateway
│ ├── tile-server/ # Rust tile server (50k req/s target)
│ ├── ephemeris/ # Python ephemeris (FastAPI + SPICE)
│ └── etl/ # Python ingestion DAGs
├── packages/
│ ├── api-client/ # Generated from OpenAPI
│ ├── tile-decoder/ # Binary tile format
│ ├── coordinate-utils/ # ICRS J2000.0 ↔ Cartesian, Kepler solver
│ └── shared-types/ # Cross-package TypeScript types
├── data/
│ ├── catalogs/ # Constellations, named stars, NGC/IC, Messier
│ ├── seeds/ # Bundled bright-star + solar-system seed data
│ ├── spice/ # JPL SPICE kernels (Git LFS, ~600 MB)
│ ├── tiles/ # Pre-baked demo tiles (100K stars)
│ └── fixtures/ # Test fixtures
└── infra/docker/ # docker-compose for local dev
The project is organised into four work tracks. Each track ships incrementally; the frontend already runs against mocked data while the backend tracks catch up.
| Status | Milestone |
|---|---|
| ✓ | Monorepo scaffold (pnpm + Turborepo), TypeScript strict, ESLint, Prettier |
| ✓ | Docker Compose for Postgres + PostGIS, Redis, Elasticsearch, API gateway |
| ✓ | Fastify health checks + structured logging |
| ✓ | Alembic migrations baseline |
| ✓ | CI workflow (lint + typecheck + test + Rust build) |
| ⌛ | Backend deployed (staging) |
| ⌛ | Auth tiers (anonymous / registered / research) |
| Status | Milestone |
|---|---|
| ⌛ | Gaia DR3 streaming ingestion (~1.8 B stars → tile pyramid) |
| ⌛ | SDSS DR17 + HyperLEDA galaxy ingestion (~4 M galaxies) |
| ⌛ | MPC asteroid catalogue (~1.3 M small bodies) |
| ⌛ | NASA Exoplanet Archive (5.8 K confirmed exoplanets, joined to Gaia host stars) |
| ⌛ | IllustrisTNG cosmic-web mesh import |
| ⌛ | Airflow DAGs for nightly refresh |
| Status | Milestone |
|---|---|
| ✓ | Binary tile format (float16 coords, packed magnitude/colour/spectral) |
| ✓ | Tile decoder package with native + software float16 |
| ✓ | Rust tile server (axum) — stars / galaxies / cosmic-web endpoints, ETag, brotli, CORS |
| ✓ | TileStreamingManager — priority queue, three-tier cache, LRU eviction |
| ✓ | 100 K-star pre-baked demo tiles |
| ✓ | HEALPix order/pixel addressing for galaxies |
| ✓ | Dev-mode middleware serves data/tiles/* without the Rust server |
| ⌛ | PgBouncer pooling for high-concurrency tile reads |
| ⌛ | CDN-fronted tile distribution |
The visual side is the most mature. Every shader is procedural; together they cover the 9 entity families from the spec.
| Status | Family | What's done |
|---|---|---|
| ✓ | Stars | Point-sprite shader with Ballesteros B-V → temperature → Tanner-Helland blackbody RGB (ΔE76 ≤ 3 per spectral class); plus extended star-type gallery |
| ✓ | Solar system | 296-body catalogue, client Kepler propagator with parented moons, Saturn ring system |
| ✓ | Planets | Procedural PBR for 8 solar planets + exoplanet archetypes (Hot Jupiter, Super-Earth, Hycean, …) |
| ✓ | Moons | Galilean, Titan, Enceladus, Triton, Trojan, sesquinary, shepherd variants |
| ✓ | Small bodies | Asteroid field renderer + named comets |
| ✓ | Nebulae | Volumetric raymarching for emission / reflection / dark / planetary / supernova |
| ✓ | Galaxies | Spiral / elliptical / irregular / lenticular morphologies + AGN / morphology-special variants; LOD ladder volumetric → billboard → point |
| ✓ | Exotic | Black hole + Schwarzschild lensing post-pass, pulsar, magnetar, quasi-star, primordial / Planck / topology / TZO variants |
| ✓ | Large-scale structure | Cosmic web filaments + voids, cluster collision, OB / open / globular cluster shaders |
| ✓ | CMB | Planck-2018-styled boundary sphere (false-colour temperature, optional dipole) |
| ⌛ | Polish pass | Long-tail uniform tuning, exotic-object accuracy reviews, FU Ori / Herbig Ae/Be variants |
| Status | Milestone |
|---|---|
| ✓ | Search (autocomplete, full-text, cone) with race-guarded request ids |
| ✓ | Fly-to with obstacle avoidance, lit-hemisphere arrival |
| ✓ | Time controls (log slider, jump presets, reverse, reset-to-now) |
| ✓ | Live ephemeris (730-day cached window, cubic interp, Kepler fallback) |
| ✓ | WebSocket (ephemeris push, tile priority, data-version invalidation) |
| ✓ | Post-processing chain (bloom, FXAA, CA, vignette, scanlines, lensing) |
| ✓ | Procedural ambient audio per scale regime |
| ✓ | Accessibility — skip links, reduced motion, high contrast, aria-live |
| ⌛ | Guided tours (narration timeline, scripted camera + UI choreography) |
| ⌛ | Bookmarks + shareable deep links |
| ⌛ | Mobile + touch input polish |
Beyond that, the long-term ambition is the full census: the 1.8 B-star Gaia stream, the 4 M-galaxy HEALPix pyramid, the 1.3 M MPC catalogue, the IllustrisTNG mesh, and the 5.8 K confirmed exoplanets parented to their Gaia host stars — all reachable from the same camera.
License is not yet finalised. If you'd like to use any part of this work, open an issue.
Built with TypeScript, Three.js, and a logarithmic depth buffer.