Skip to content
Merged
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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ jobs:
node-version: '24'
- run: npx prettier --check .
- run: npx eslint .
- run: node --test tests/*.test.js
17 changes: 13 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,25 @@ Comments are part of the standard — treat them as required, not optional.

See the stack section below for the exact doc-comment syntax in this repo's language.

## Stack: Vanilla JS
## Stack: Vanilla JS + Three.js (WebGPU)

- No build step. Open `index.html` directly, or serve the folder:
`npx serve .` / `python3 -m http.server`.
- Plain ES modules: no bundler, no framework. Keep it dependency-free.
- Plain ES modules: no bundler, no framework, no `package.json`.
- **Three.js is the one allowed runtime dependency.** It is pinned to an exact version in
the import maps (`webgpu/index.html`) and loaded from a CDN. Keep everything else
dependency-free, and when bumping Three.js change every import map in one commit.
- `webgpu/` is the primary renderer and `canvas/` is the fallback. `legacy/` is kept for
reference and is not maintained.
- Format with Prettier: `npx prettier --check .` (run `--write` to fix).
- House style with ESLint: `npx eslint .` (run `--fix` to fix). Enforces the
"breathing" layout Prettier can't — a blank line before a function's final
"breathing" layout Prettier can't: a blank line before a function's final
expression, between class members, and around declaration groups.
- Shared logic lives in `shared/`; entry points stay thin.
- Unit tests: `node --test tests/*.test.js`.
- Shared logic lives in `shared/`; entry points stay thin. Look definitions live only in
`shared/looks.js`.
- Shaders are TSL. Read the "Shader pitfalls" in `docs/ARCHITECTURE.md` before editing
them (shared nodes across `If` branches, `pow` with negative bases, seeded buffers).

## Before you finish

Expand Down
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- WebGPU renderer (`webgpu/`) built on Three.js and TSL: a true 3D globe with a real sun and terminator, a ray-marched atmosphere, living clouds with shadows, ocean glint, relief, procedural city lights, aurora, a lit moon, orbit rings with satellites and shooting stars.
- Five switchable looks, cross-faded live without recompiling shaders: Realistic, Cartoon, Neon, Hologram and Ink & Paper. Choose one with the new look lenses, `?look=<id>`, or the `look` scene setting.
- GPU compute particles: impact sparks in a ring buffer and about 40k ambient orbital motes, advected entirely by compute kernels.
- One-time GPU bakes of the static planet fields (biomes, relief, ocean, clouds, sky), so the globe shader only samples textures.
- Bloom, chromatic fringe, scanlines, vignette and grain as a per-look post-processing pipeline.
- New `look`, `clouds` and `bloom` scene settings. The schema is now version 2, and fields can be scoped to renderers with `renderers: [...]`.
- Landing page with a live globe in a frame and a look picker that re-dresses the whole page.
- `shared/looks.js`, the dependency-free single source of truth for the looks.
- Unit tests for the scene schema and look data (`node --test tests/`).
- Initial project scaffold from [Studio51 Standards](https://github.com/studio51/standards).
- Cinematic arrival (`intro`): ~3.2s choreographed bloom on load (nebula → stars → sphere → atmosphere → land → night/aurora/orbits → beams), with rotation easing in from stillness.
- Six cinematic canvas layers: orbiting moon, rare comet, shimmering constellations, ocean sun glint, HQ heartbeat wave, and city surges.
Expand All @@ -18,6 +27,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- Refreshed interface: glass panels, a settings drawer, look lenses, and a redrawn brand. Every surface themes itself per look (glass, chunky cartoon cards, neon, hologram wireframe, paper and ink), and the Canvas fallback shares the same chrome.
- The root page is now a landing page for the WebGPU renderer. The Canvas 2D renderer is the automatic fallback and keeps your URL options.
- The Canvas renderer hides settings it cannot draw (the schema marks them by renderer).
- Orbit now depends on Three.js (pinned, loaded from a CDN through an import map), the one allowed runtime dependency. AGENTS.md and the docs say so.
- The SVG renderer moved to `legacy/svg/` and is no longer maintained.
- Canvas renderer performance: zero trig in the hot loops (precomputed sin/cos with angle-sum projection), tier-sorted land dots drawn as contiguous runs, low-res offscreen terminator upscaled to screen, and cached GeoJSON rebuilt only when the sun moves.
- Adaptive quality in `BaseEngine`: an EMA of frame time nudges a dpr multiplier (down to 0.55) with cooldown hysteresis so weak GPUs get a softer image instead of dropped frames.
- Richer visuals: deeper offset-lit ocean, wider atmospheric rim with a crisp shell line, 4-layer beams, twin impact rings, 3-pass aurora, warmer city lights.
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,24 @@

# Orbit

> A real-time 3D globe that visualizes live activity as luminous beams arcing across the Earth — extracted from games.directory as a standalone, drop-in widget.
> A real-time 3D globe that visualizes live activity as luminous beams arcing across the Earth, extracted from games.directory as a standalone, drop-in widget.

[![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
[![CI](https://github.com/studio51/orbit/actions/workflows/ci.yml/badge.svg)](https://github.com/studio51/orbit/actions/workflows/ci.yml)

Orbit is the centerpiece hero globe for the [games.directory](https://games.directory)
landing page, packaged here as a self-contained plugin with no dependencies on
the rest of the site. Clone it, serve it, and play with the live controls — or
the rest of the site. Clone it, serve it, and play with the live controls, or
drop it into your own page and point it at your own HQ, cities, and activity
types. It ships **rendered two ways**, Canvas 2D (recommended) and SVG, over one
shared core, with a day/night terminator, aurora, parallax starfield, meteors,
and more — all tunable live.
types. It renders on **WebGPU** (a true 3D sphere with GPU compute particles) in five
switchable looks, and falls back to Canvas 2D where WebGPU is unavailable.

## Docs

- [Architecture](docs/ARCHITECTURE.md)
- [Install & setup](docs/INSTALL.md)
- [Usage](docs/USAGE.md)
- [Looks](docs/LOOKS.md)
- [Changelog](CHANGELOG.md)
- [Contributing](CONTRIBUTING.md)
- [Security](SECURITY.md)
Expand Down
18 changes: 11 additions & 7 deletions canvas/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Orbit · Activity Globe (Canvas)</title>
<link
rel="icon"
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Ccircle cx='16' cy='16' r='7' fill='%235ad1ff'/%3E%3Ccircle cx='16' cy='16' r='13' fill='none' stroke='%235ad1ff' stroke-width='1.6'/%3E%3C/svg%3E"
/>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap"
href="https://fonts.googleapis.com/css2?family=Instrument+Sans:wght@400;500;600;700&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="../shared/ui.css" />
Expand All @@ -23,17 +27,17 @@

<div class="brand">
<div class="mark">
<div class="glyph">g</div>
<div class="glyph" aria-hidden="true"></div>
<div class="name"><b>Orbit</b></div>
</div>
<div class="tag">Live activity · worldwide</div>
<div class="tag">Live activity, worldwide</div>
</div>

<aside class="ticker" id="ticker" aria-hidden="true"></aside>

<aside class="panel">
<div class="panel-head">
<span class="live"><span class="blip"></span>LIVE FEED</span>
<span class="live"><span class="blip"></span>Live feed</span>
</div>
<div class="total">
<span id="total-count">0</span>
Expand Down Expand Up @@ -76,7 +80,7 @@
<span class="fw-label">Fireworks on</span>
<select id="fw-trigger" class="fw-select" title="Activity that sets off fireworks"></select>
</div>
<div class="hint">drag the globe to spin · tap a swatch to recolour</div>
<div class="hint">Drag to spin, tap a swatch to recolour.</div>

<button class="scene-expander" id="scene-toggle" aria-pressed="false" aria-controls="scene">
<svg
Expand All @@ -93,7 +97,7 @@
d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"
></path>
</svg>
<span class="se-label">Scene &amp; effects</span>
<span class="se-label">Scene settings</span>
<svg class="chev" viewBox="0 0 12 12">
<path
d="M2 4l4 4 4-4"
Expand All @@ -111,7 +115,7 @@
<div id="loading">
<div>
<div class="spinner"></div>
loading world map…
Preparing the globe
</div>
</div>

Expand Down
6 changes: 6 additions & 0 deletions canvas/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ const ticker = createTicker(document.getElementById('ticker'), VERBS);
engine.on('beam', ({ type, city, color }) => ticker.push(type, city.name, color));
engine.on('surge', ({ city }) => ticker.special(`${city.name} is lighting up right now`));

// Reached through the WebGPU page's fallback: say so, once.
if (location.hash === '#no-webgpu') {
ticker.special('WebGPU is not available here, so this is the Canvas version');
}

// Controls + FPS meter are demo-only; the production hero stays clean.
if (demo) {
engine.fps = createFpsMeter('canvas');
Expand All @@ -75,6 +80,7 @@ if (demo) {
host: document.getElementById('scene'),
toggle: document.getElementById('scene-toggle'),
scene,
renderer: 'canvas',
onChange: (key, structural) => {
if (structural) engine.rebuildFor(key);
engine.applyScene();
Expand Down
Loading
Loading