Bootstrap: build spec for Opus 4.8, dev server with CORS proxy, preview config
SPEC.md is the complete execution plan: architecture, verified data-feed CORS probes, per-layer component specs, verification protocol, and the gated partly.party/godsigh deployment phase. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
commit
0fec269512
11
.claude/launch.json
Normal file
11
.claude/launch.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"version": "0.0.1",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "godsigh",
|
||||||
|
"runtimeExecutable": "python3",
|
||||||
|
"runtimeArgs": ["serve.py", "8137"],
|
||||||
|
"port": 8137
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.DS_Store
|
||||||
|
*.pyc
|
||||||
|
__pycache__/
|
||||||
18
README.md
Normal file
18
README.md
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# GODSIGH
|
||||||
|
|
||||||
|
A browser-based 3D/4D OSINT world view — CesiumJS digital-twin globe fusing public sensor feeds (satellite orbits, live ADS-B aircraft, AIS shipping, infrastructure vectors) on one scrubbable timeline. Inspired by Bilawal Sidhu's *God's Eye View*.
|
||||||
|
|
||||||
|
**Status: pre-build.** The complete build & deployment instructions live in [SPEC.md](SPEC.md) — start there.
|
||||||
|
|
||||||
|
## Quickstart (dev)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python3 serve.py 8137
|
||||||
|
# open http://127.0.0.1:8137
|
||||||
|
```
|
||||||
|
|
||||||
|
`serve.py` is a zero-dependency static server that also proxies the OpenSky ADS-B API (which blocks cross-origin browser calls) at `proxy/opensky`.
|
||||||
|
|
||||||
|
## Target
|
||||||
|
|
||||||
|
`https://partly.party/godsigh/` — deployment is the final build phase, gated on John.
|
||||||
245
SPEC.md
Normal file
245
SPEC.md
Normal file
@ -0,0 +1,245 @@
|
|||||||
|
# GODSIGH — Build Spec & Execution Instructions
|
||||||
|
|
||||||
|
**Audience:** Claude Opus 4.8, executing autonomously in this repo.
|
||||||
|
**Author:** Claude Fable 5, 2026-07-13, from a working session with John.
|
||||||
|
**Repo:** `ssh://git@100.71.119.27:222/monster/GODSIGH.git` (branch `main`)
|
||||||
|
**Final destination:** `https://partly.party/godsigh/` (games VPS, `forum-nginx` container, Cloudflare-fronted) — **deployment is the LAST phase and is gated on John.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. Mission
|
||||||
|
|
||||||
|
Build a browser-based 3D/4D OSINT "world view" globe in the spirit of Bilawal Sidhu's *God's Eye View*: a CesiumJS digital-twin globe with a space-time slider along the bottom, where independent public sensor feeds — satellite orbits (Celestrak TLEs), live aircraft (OpenSky ADS-B), ship traffic (AIS), and static infrastructure vectors (pipelines, choke points, ports) — are all synced to one clock. Two rendering skins: a dark high-contrast **Data Mode** and a satellite-imagery **Photo Mode**.
|
||||||
|
|
||||||
|
Everything uses free, public, unclassified data. No API keys are required for the MVP (AIS live data optionally takes a free aisstream.io key later).
|
||||||
|
|
||||||
|
**Done looks like:** open `http://127.0.0.1:8137`, see a dark globe over the Persian Gulf with ~60 named satellites tracing glowing orbits, thousands of live aircraft, demo tanker traffic through the Strait of Hormuz (including one flagged "dark vessel" AIS gap), pipeline/choke-point overlays, and demo event markers that appear/disappear as you scrub the timeline ±6 hours. Then, on John's go-ahead, the same thing live at `partly.party/godsigh`.
|
||||||
|
|
||||||
|
## 1. Ground truth — already verified 2026-07-13 (do not re-litigate)
|
||||||
|
|
||||||
|
These endpoints were probed with browser-like `Origin` headers from this machine:
|
||||||
|
|
||||||
|
| Feed | URL | Status | CORS | Consequence |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| Celestrak TLEs | `https://celestrak.org/NORAD/elements/gp.php?GROUP=stations&FORMAT=tle` | 200 | `ACAO: *` | Browser may fetch directly; we proxy anyway for safety |
|
||||||
|
| OpenSky ADS-B | `https://opensky-network.org/api/states/all` | 200 | `ACAO:` pinned to their own origin | **Browser CANNOT fetch directly — must go through our proxy** |
|
||||||
|
| EOX Sentinel-2 cloudless | `https://tiles.maps.eox.at/wmts/1.0.0/s2cloudless-2023_3857/default/g/{z}/{y}/{x}.jpg` | 200 | `ACAO: *` | Photo Mode basemap, no key |
|
||||||
|
| Carto dark basemap | `https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png` (subdomains a–d) | 200 | `ACAO: *` | Data Mode basemap, no key |
|
||||||
|
|
||||||
|
OpenSky anonymous quota: ~400 credits/day; a **global** `states/all` call costs 4 credits (≈100 calls/day). The response carries `x-rate-limit-remaining`. Budget accordingly (see §6.3).
|
||||||
|
|
||||||
|
Classified US satellites (e.g. USA 234 / Topaz) are **not** in Celestrak's public catalog — do not waste time looking. Russian (Persona/Kosmos) and Chinese (Gaofen) military satellites **are** publicly tracked. Amateur "classfd" TLE lists are a roadmap item only.
|
||||||
|
|
||||||
|
## 2. Locked architecture decisions
|
||||||
|
|
||||||
|
1. **No build step.** Vanilla ES modules + CDN globals. No npm, no bundler, no framework. `index.html` loads CesiumJS and satellite.js as script-tag globals; our code is plain `<script type="module">` files importing each other relatively.
|
||||||
|
- Cesium: `https://cdn.jsdelivr.net/npm/cesium@1/Build/Cesium/Cesium.js` + `.../Build/Cesium/Widgets/widgets.css` (global `Cesium`; the IIFE build self-detects its base URL so workers/assets load from the CDN).
|
||||||
|
- satellite.js: `https://cdn.jsdelivr.net/npm/satellite.js@5/dist/satellite.min.js` (global `satellite`).
|
||||||
|
- Before the deploy phase, pin both to the exact resolved versions (read `Cesium.VERSION` in the console) so prod doesn't drift.
|
||||||
|
2. **Subpath-safe URLs everywhere.** The app must work at both `http://127.0.0.1:8137/` and `https://partly.party/godsigh/`. Every fetch and asset reference is **relative** (`proxy/opensky?...`, `css/style.css`) — never a leading `/`.
|
||||||
|
3. **Dev server = `serve.py`** (already in repo): stdlib-only static server + same-origin proxy at `proxy/opensky` and `proxy/celestrak`, port 8137. In production, nginx location blocks replace the proxy (§9.4) — `serve.py` is dev-only and must not ship into the web root.
|
||||||
|
4. **Cesium Entities vs Primitives split:** satellites/ships/infra/events are Cesium **entities** (few hundred, get time-dynamics + the built-in InfoBox for free). Aircraft are a **`BillboardCollection` primitive** (thousands; entities would crawl) with a custom click overlay.
|
||||||
|
5. **Time model:** Cesium clock spans **now −6h → now +6h**, `CLAMPED`, multiplier 1, starting at now, `shouldAnimate: true`. The built-in timeline + animation widgets ARE the space-time slider — do not build a custom one. Time-dynamic layers (satellites, demo ships, events) use `SampledPositionProperty`/`availability` so scrubbing "just works". Live-only layers (aircraft, live AIS) are hidden with a status note when the clock is scrubbed >10 min from wall-clock now (§6.6).
|
||||||
|
6. **No fake data masquerading as real.** Demo/synthetic layers (ships, events) are labeled `[DEMO]` in entity names, in the HUD, and in the README.
|
||||||
|
|
||||||
|
## 3. Repo workflow
|
||||||
|
|
||||||
|
- Work on `main` (John's solo repo; no PR ceremony). Commit per phase with messages like `phase 2: satellite layer — celestrak TLEs + SGP4 sampling`.
|
||||||
|
- End commit messages with: `Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>`
|
||||||
|
- Push after each phase so John can watch progress in Gitea.
|
||||||
|
- Already in repo when you start: `serve.py`, `SPEC.md` (this file), `README.md` (stub), `.claude/launch.json` (preview server config), `.gitignore`.
|
||||||
|
|
||||||
|
## 4. File map (target state)
|
||||||
|
|
||||||
|
```
|
||||||
|
GODSIGH/
|
||||||
|
├── SPEC.md # this file
|
||||||
|
├── README.md # user-facing docs; finish in phase 8
|
||||||
|
├── serve.py # dev server + CORS proxy (exists; don't ship to web root)
|
||||||
|
├── deploy.sh # phase 9 only — mirrors GAMES/* deploy doctrine
|
||||||
|
├── .claude/launch.json # preview config: python3 serve.py 8137 (exists)
|
||||||
|
├── index.html # CDN script tags, HUD skeleton, cesiumContainer
|
||||||
|
├── css/style.css # dark HUD styling
|
||||||
|
└── js/
|
||||||
|
├── config.js # all URLs, keys, poll intervals, theater camera, colors
|
||||||
|
├── main.js # viewer bootstrap, basemap modes, clock, live-gating, picking
|
||||||
|
├── ui.js # HUD panel: layer toggles, per-feed status lines, LIVE/SCRUB chip
|
||||||
|
└── layers/
|
||||||
|
├── satellites.js # Celestrak TLE → SGP4 → SampledPositionProperty entities
|
||||||
|
├── aircraft.js # OpenSky poll → BillboardCollection
|
||||||
|
├── ships.js # demo AIS fleet + dark-vessel gap + optional aisstream.io live
|
||||||
|
├── infra.js # static pipelines, choke points, facilities
|
||||||
|
└── events.js # [DEMO] time-anchored event markers
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Phase plan
|
||||||
|
|
||||||
|
Execute in order. **Verify each phase in the browser before committing** (§7 protocol). Phases 1–8 need no input from John. Phase 9 is gated.
|
||||||
|
|
||||||
|
- **Phase 0 — bootstrap:** clone repo, `python3 serve.py 8137` (or preview_start with the provided launch.json), confirm the empty page serves and `proxy/opensky?lamin=24&lomin=50&lamax=27&lomax=57` returns JSON through it.
|
||||||
|
- **Phase 1 — globe core:** viewer + dual basemaps + clock/timeline + camera + HUD skeleton (§6.1).
|
||||||
|
- **Phase 2 — satellites** (§6.2).
|
||||||
|
- **Phase 3 — aircraft** (§6.3).
|
||||||
|
- **Phase 4 — ships** (§6.4).
|
||||||
|
- **Phase 5 — infrastructure + events** (§6.5).
|
||||||
|
- **Phase 6 — interaction polish:** mode toggle, live-scrub gating, picking overlay, status lines (§6.6).
|
||||||
|
- **Phase 7 — full verification pass** (§7).
|
||||||
|
- **Phase 8 — README** (§8).
|
||||||
|
- **Phase 9 — deploy to partly.party/godsigh** (§9) — **stop and confirm with John first.**
|
||||||
|
|
||||||
|
## 6. Detailed component specs
|
||||||
|
|
||||||
|
### 6.1 Globe core (`main.js`, `index.html`, `css/style.css`)
|
||||||
|
|
||||||
|
```js
|
||||||
|
const now = Cesium.JulianDate.now();
|
||||||
|
const start = Cesium.JulianDate.addHours(now, -6, new Cesium.JulianDate());
|
||||||
|
const stop = Cesium.JulianDate.addHours(now, 6, new Cesium.JulianDate());
|
||||||
|
|
||||||
|
const viewer = new Cesium.Viewer('cesiumContainer', {
|
||||||
|
baseLayer: false, // we manage imagery ourselves — avoids any Cesium ion token
|
||||||
|
baseLayerPicker: false, geocoder: false, sceneModePicker: false,
|
||||||
|
navigationHelpButton: false, homeButton: false, fullscreenButton: false,
|
||||||
|
timeline: true, animation: true, infoBox: true, selectionIndicator: true,
|
||||||
|
shouldAnimate: true,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Basemaps:** add two `UrlTemplateImageryProvider` layers — Carto `dark_all` (Data Mode, `subdomains:['a','b','c','d']`, credit "© OpenStreetMap contributors © CARTO") and EOX `s2cloudless-2023_3857` (Photo Mode, credit "Sentinel-2 cloudless by EOX — modified Copernicus Sentinel data 2023"). Photo layer starts `show = false`. Mode toggle flips the two `show` flags. Keep both credits — attribution is a license requirement for Carto, OSM and EOX.
|
||||||
|
- **Clock:** set `startTime/stopTime/currentTime`, `clockRange: Cesium.ClockRange.CLAMPED`, `multiplier: 1`, then `viewer.timeline.zoomTo(start, stop)`.
|
||||||
|
- **Atmosphere/lighting:** `viewer.scene.globe.enableLighting = true` (live day/night terminator — the single cheapest "wow"). Default sky box/atmosphere are fine.
|
||||||
|
- **Camera:** `viewer.camera.setView({ destination: Cesium.Cartesian3.fromDegrees(53.0, 25.5, 2_800_000) })` — opens on the Persian Gulf theater.
|
||||||
|
- **HUD:** fixed left panel (`<aside id="hud">`) over the canvas: title `GODSIGH ▸ WORLD VIEW`, mode toggle buttons, one row per layer (checkbox + name + colored status dot + status text), a `LIVE / SCRUBBED` chip, and a footer crediting data sources. Styling: near-black translucent panels (`rgba(10,14,18,.82)` + `backdrop-filter: blur(8px)`), 1px `rgba(255,255,255,.08)` borders, `ui-monospace` font, cyan `#35e0ff` accents. Don't fight Cesium's widget CSS; the default timeline/animation widgets already look the part.
|
||||||
|
|
||||||
|
### 6.2 Satellites (`layers/satellites.js`)
|
||||||
|
|
||||||
|
**Fetch** (via `proxy/celestrak?...`, dedupe by NORAD number, cap ~100 sats):
|
||||||
|
|
||||||
|
| Query | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| `GROUP=resource&FORMAT=tle` | Commercial/civil EO constellation — filter names by keywords: `WORLDVIEW, LEGION, PLEIADES, CAPELLA, GAOFEN, SKYSAT, ICEYE, UMBRA, LANDSAT, SENTINEL-1, SENTINEL-2, GEOEYE, KOMPSAT, CARTOSAT, RESURS` |
|
||||||
|
| `GROUP=stations&FORMAT=tle` | Keep only `ISS (ZARYA)` and `CSS (TIANHE)` |
|
||||||
|
| `NAME=GAOFEN&FORMAT=tle` | Chinese military EO series incl. Gaofen-11 (may 404/empty — tolerate) |
|
||||||
|
| `NAME=COSMOS 2486&FORMAT=tle`, `NAME=COSMOS 2506&FORMAT=tle` | Russian Persona-class recon (URL-encode the space) |
|
||||||
|
|
||||||
|
TLE text parses as name/line1/line2 triplets. A failed source must not sink the layer — `Promise.allSettled`, report per-source counts in the status line.
|
||||||
|
|
||||||
|
**Propagation:** for each sat, `satellite.twoline2satrec(l1, l2)`, then build a `SampledPositionProperty` sampled every **60 s** across the full clock window (721 samples/sat):
|
||||||
|
|
||||||
|
```js
|
||||||
|
const date = Cesium.JulianDate.toDate(t);
|
||||||
|
const pv = satellite.propagate(satrec, date);
|
||||||
|
if (!pv || !pv.position) continue; // decayed/bad TLEs — skip sample
|
||||||
|
const gmst = satellite.gstime(date);
|
||||||
|
const geo = satellite.eciToGeodetic(pv.position, gmst); // radians + km
|
||||||
|
posProp.addSample(t, Cesium.Cartesian3.fromRadians(geo.longitude, geo.latitude, geo.height * 1000));
|
||||||
|
```
|
||||||
|
|
||||||
|
Set Lagrange interpolation (degree 5). ~100 sats × 721 samples ≈ 72k SGP4 calls — chunk the loop (e.g. 10 sats per `setTimeout(0)` tick) so first paint isn't blocked; update the status line as chunks land.
|
||||||
|
|
||||||
|
**Entity per sat:** `point` (pixelSize 6, color-coded), `label` (name, `10px ui-monospace`, offset above, `translucencyByDistance` so labels fade at distance), `path` (`width 1`, `resolution 120`, `PolylineGlowMaterialProperty` at ~0.35 alpha, `leadTime = trailTime = periodSec / 2` where `periodSec = (2π / satrec.no) * 60` — `satrec.no` is rad/min), `description` HTML: NORAD id, source query, period min, inclination `satrec.inclo` (rad→deg), apogee/perigee from `a = (398600.4418 / n²)^(1/3)` km with `n = satrec.no/60` rad/s, `e = satrec.ecco`.
|
||||||
|
|
||||||
|
**Colors:** GAOFEN `#ff4d4d` · COSMOS `#ff9f40` · WORLDVIEW/LEGION `#4dd2ff` · CAPELLA `#9d7bff` · PLEIADES `#7bff9d` · SENTINEL `#ffd24d` · ISS/CSS `#ffffff` · default `#66ffcc`.
|
||||||
|
|
||||||
|
Separate HUD toggles for "Satellites" and "Orbit paths" (paths ON by default).
|
||||||
|
|
||||||
|
### 6.3 Aircraft (`layers/aircraft.js`)
|
||||||
|
|
||||||
|
- **Fetch** `proxy/opensky` (optionally with `?lamin=&lomin=&lamax=&lomax=` from `config.js`; default **global** — same 4-credit cost as any bbox > 400 deg², and a fully lit planet is the demo). Response: `{ time, states: [...] }`; state array indices: `0 icao24, 1 callsign, 2 origin_country, 5 lon, 6 lat, 7 baro_altitude, 8 on_ground, 9 velocity(m/s), 10 true_track(deg)`. Skip null lon/lat and `on_ground`.
|
||||||
|
- **Render:** one shared canvas-drawn triangle glyph (32×32, white — draw `(16,2)→(28,30)→(16,23)→(4,30)`, close, fill) in a `BillboardCollection`; per-billboard `color` tint by altitude band (< 3000 m `#ffb347`, < 9000 m `#ffe74d`, else `#4dd2ff`), `rotation: -Cesium.Math.toRadians(track || 0)`, position at `baro_altitude` meters, `scale` ~0.55. Set `id: { layer:'aircraft', icao24, callsign, country, alt, vel, track }` for picking. Rebuild the collection wholesale each poll (8k billboards rebuilds fine).
|
||||||
|
- **Poll:** every **90 s** while the layer is enabled AND the clock is live (§6.6) AND `!document.hidden` — that's ~40% of the anonymous daily budget for a long session, acceptable. On HTTP 429, back off ×4 and show `rate-limited — anonymous OpenSky quota` in the status line. Show count + data timestamp + `x-rate-limit-remaining` when available.
|
||||||
|
- **Picking:** `ScreenSpaceEventHandler` LEFT_CLICK → `scene.pick`; if `picked?.id?.layer === 'aircraft'`, fill a small fixed overlay div (callsign, country, altitude m/ft, speed kt, heading); otherwise hide it and let the entity InfoBox handle sats/ships.
|
||||||
|
|
||||||
|
### 6.4 Ships (`layers/ships.js`)
|
||||||
|
|
||||||
|
**Demo fleet (always available, everything named `… [DEMO]`):** ~8 vessels crossing the clock window as `SampledPositionProperty` waypoint tracks (`[lon, lat, hourOffset]`, linear interpolation, height 0), rendered as diamond-glyph billboards (shared canvas, like aircraft) with fixed `rotation` from the great-circle bearing of the track (`θ = atan2(sinΔλ·cosφ₂, cosφ₁·sinφ₂ − sinφ₁·cosφ₂·cosΔλ)`, rotation `= -toRadians(θ)`), plus name label and InfoBox description (flag, type, speed, route note). Include:
|
||||||
|
|
||||||
|
1. 2–3 eastbound + 2 westbound tankers/LNG through the Hormuz deep channel, e.g. `[[56.9,25.4,-6],[56.4,26.2,-2.5],[55.6,26.55,0],[54.5,26.2,3],[53.5,25.7,6]]` and reverses. Green `#7bff9d`.
|
||||||
|
2. One **"toll-route" LPG carrier** hugging the northern (Iranian-waters) coastline: waypoints tracking ~27.0–26.5 N between 54.5–56.5 E. Yellow `#ffe74d`, description notes the escort-route storyline.
|
||||||
|
3. One **dark vessel** `#ff4d4d`: `availability` = two `TimeInterval`s — `[start, now−2h]` and `[now+1h, stop]`. Between its last-seen and reacquired positions draw a **red dashed polyline** (`PolylineDashMaterialProperty`) with a mid-track label `AIS DARK GAP [DEMO]`. This demos the dark-vessel-detection concept while scrubbing.
|
||||||
|
4. 5–6 stationary gray tankers clustered at Fujairah anchorage (~25.10–25.25 N, 56.5–56.7 E) — the "floating parking lot".
|
||||||
|
|
||||||
|
**Live AIS (optional, later):** if `CONFIG.AISSTREAM_API_KEY` is set, open `wss://stream.aisstream.io/v0/stream`, on open send `{ APIKey, BoundingBoxes: [[[10,30],[35,70]]], FilterMessageTypes: ["PositionReport"] }` (boxes are `[lat,lon]` pairs, SW/NE), upsert one entity per MMSI (cap ~500, evict stalest). Free key from aisstream.io. Verify the actual message shape at runtime before trusting field names. WebSockets are not CORS-blocked, so no proxy needed. Demo fleet hides when live AIS is on.
|
||||||
|
|
||||||
|
### 6.5 Infrastructure + events (`layers/infra.js`, `layers/events.js`)
|
||||||
|
|
||||||
|
All coordinates approximate — say so in each description. Static entities, no time dynamics (except events' availability).
|
||||||
|
|
||||||
|
**Choke points** — red ring (`ellipse` ~80 km semi-axes, outline + `rgba(255,0,60,.06)` fill) + label: Strait of Hormuz `(56.5, 26.6)`, Bab el-Mandeb `(43.33, 12.58)`, Suez `(32.35, 30.45)`, Malacca `(101.0, 2.5)`.
|
||||||
|
|
||||||
|
**Pipelines** — amber `#ffbf40` dashed polylines (`clampToGround: true`) + midpoint label:
|
||||||
|
- East–West Petroline: `(49.68,25.94) → (46.7,24.6) → (44.5,24.3) → (41.5,24.2) → (38.06,24.09)` (Abqaiq → Yanbu)
|
||||||
|
- Habshan–Fujairah: `(53.61,23.75) → (54.6,24.2) → (55.5,24.8) → (56.33,25.12)`
|
||||||
|
|
||||||
|
**Facilities** — small square points + labels: Ras Tanura oil terminal `(50.16,26.64)`, Abqaiq processing `(49.68,25.94)`, Yanbu port `(38.06,24.09)`, Kharg Island terminal `(50.32,29.23)`, Fujairah oil terminal `(56.37,25.18)`, Jebel Ali desalination `(55.02,25.01)`, Ras Al-Khair desalination `(49.19,27.55)`, Al Udeid Air Base `(51.32,25.12)`, Haifa refinery `(35.05,32.79)`.
|
||||||
|
|
||||||
|
**Events `[DEMO]`** — 3–4 pulsing markers (point `pixelSize` via `CallbackProperty` sine ~8±4) whose `availability` starts at their event time, so they *appear when the slider crosses them*: e.g. `now−3h` strike marker inland Iran `(51.4, 32.6)`, `now−2h` "satellite pass correlation" note at the same spot, `now+1h` tanker-incident marker in the Strait. Descriptions state clearly these are illustrative placeholders for a future real event feed.
|
||||||
|
|
||||||
|
### 6.6 Interaction glue (`main.js`, `ui.js`)
|
||||||
|
|
||||||
|
- **Live-scrub gating:** on `clock.onTick` (throttled to ~1/s), compute `|currentTime − JulianDate.now()|`. If > 600 s: hide aircraft + live-AIS collections, set chip to `SCRUBBED — live layers paused`, and skip OpenSky polls (saves quota). Back within 600 s: restore. The timeline's rightmost 6 h are future — satellites keep predicting there (correct behavior — orbits are deterministic), live layers stay dark.
|
||||||
|
- **`ui.js` API:** `addLayer(id, name, defaultOn, onToggle)` and `setStatus(id, text, state)` where state ∈ ok/warn/err → green/amber/red dot. Every layer reports load counts and failures here — **no silent failures.**
|
||||||
|
- **`config.js`:** every URL, poll interval, bbox, camera start, color table, and the empty `AISSTREAM_API_KEY` slot in one file, commented.
|
||||||
|
|
||||||
|
## 7. Verification protocol (phase 7, and after every phase)
|
||||||
|
|
||||||
|
Use the preview tools (`.claude/launch.json` defines server `godsigh` → `python3 serve.py 8137`):
|
||||||
|
|
||||||
|
1. `preview_start`, then `preview_console_logs` (level error) — zero uncaught errors allowed.
|
||||||
|
2. `preview_network` — confirm 200s: Carto tiles, EOX tiles (toggle Photo Mode first), `proxy/celestrak…` ×4–5, `proxy/opensky…`. 429 from OpenSky is acceptable if the HUD shows the rate-limit status.
|
||||||
|
3. `preview_screenshot` in both modes; `preview_snapshot` to confirm HUD text/toggles.
|
||||||
|
4. Behavior checks: click a satellite → InfoBox with orbital params; click an aircraft → overlay; scrub timeline back 3 h → aircraft vanish + chip flips + demo ships/satellites move + `now−3h` event marker pops in; drag to future → satellites keep orbiting; toggle every layer on/off; toggle both basemap modes.
|
||||||
|
5. Acceptance checklist: ☐ ≥40 satellites with orbit paths ☐ aircraft count in status (or explicit rate-limit message) ☐ dark-vessel gap renders with dashed red line ☐ all infra labels ☐ events appear/disappear with slider ☐ both basemaps ☐ zero console errors ☐ every fetch path relative (grep for `fetch('/` and `src="/` — must be empty).
|
||||||
|
|
||||||
|
## 8. README (phase 8)
|
||||||
|
|
||||||
|
Rewrite `README.md` for a visitor: what it is, screenshot, quickstart (`python3 serve.py 8137`), data-source table with attribution (OpenSky CC terms — non-commercial; Carto/OSM attribution; EOX Sentinel-2 attribution; Celestrak), explicit list of what is DEMO data, and the roadmap: live AIS via aisstream key · GPS-jamming layer (gpsjam.org daily H3 data) · historical recording (cron job appending OpenSky/AIS snapshots to JSONL for true time-travel — see the `jobs` skill heartbeat convention if built) · classified-sat amateur TLEs · oil-futures side panel · Cesium ion token for terrain/photorealistic 3D tiles · agentic event ingestion.
|
||||||
|
|
||||||
|
## 9. Phase 9 — Deployment (FINAL — gated)
|
||||||
|
|
||||||
|
**Stop. Confirm with John before starting this phase.** He will generate a fresh Cloudflare API token when ready. Two prerequisites from the local machine doctrine:
|
||||||
|
|
||||||
|
1. **Read `~/Documents/GAMES/CLAUDE.md`** and one existing deploy script (e.g. `GAMES/cratewars/deploy.sh`) and mirror the doctrine: bundle → rsync to `/tmp` staging on `humanjing@100.71.119.27` → `docker cp` into `forum-nginx` web root under `godsigh/` → curl verify. Ignore the stale `~/Documents/VPS_DEPLOYMENT_GUIDE.md` / `DEPLOYMENT_STATUS.md`.
|
||||||
|
2. **Run `/ship-check`** before exposing anything (the proxy endpoint is the only dynamic surface — upstream is pinned to OpenSky so there's no open-proxy/SSRF hole, but let ship-check confirm).
|
||||||
|
|
||||||
|
Deployment specifics:
|
||||||
|
|
||||||
|
- **Ship only** `index.html`, `css/`, `js/` (+ README). Never `serve.py`, `SPEC.md`, `.claude/`.
|
||||||
|
- **Pin CDN versions** (§2.1) before shipping.
|
||||||
|
- **Proxy in prod:** add an nginx location to the `forum-nginx` config (find it via `docker exec forum-nginx nginx -T`; **check whether the conf is a host bind-mount or container-local — if container-local, a restart wipes it, so persist it the same way the existing site config persists**):
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
location = /godsigh/proxy/opensky {
|
||||||
|
proxy_pass https://opensky-network.org/api/states/all$is_args$args;
|
||||||
|
proxy_ssl_server_name on;
|
||||||
|
proxy_set_header Host opensky-network.org;
|
||||||
|
proxy_set_header User-Agent "godsigh/1.0 (partly.party)";
|
||||||
|
add_header Cache-Control "no-store" always;
|
||||||
|
}
|
||||||
|
location = /godsigh/proxy/celestrak {
|
||||||
|
proxy_pass https://celestrak.org/NORAD/elements/gp.php$is_args$args;
|
||||||
|
proxy_ssl_server_name on;
|
||||||
|
proxy_set_header Host celestrak.org;
|
||||||
|
proxy_set_header User-Agent "godsigh/1.0 (partly.party)";
|
||||||
|
add_header Cache-Control "max-age=3600" always;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
then `docker exec forum-nginx nginx -t && docker exec forum-nginx nginx -s reload`.
|
||||||
|
- **Cloudflare:** ask John for the fresh token at this point; purge `partly.party/godsigh/*` after deploy. Beware Cloudflare caching the proxy endpoints — the `no-store` header above matters; if aircraft data looks frozen, check `cf-cache-status`.
|
||||||
|
- **Verify like the deploy-map says — data endpoints, not just the index:** `curl -sI https://partly.party/godsigh/` (200), `curl -s 'https://partly.party/godsigh/proxy/opensky?lamin=24&lomin=50&lamax=27&lomax=57' | head -c 300` (JSON, not HTML), one Celestrak proxy call, and a browser load checking tiles + console. Report exact curl results to John. Non-2xx = not deployed; say so plainly.
|
||||||
|
|
||||||
|
## 10. Known pitfalls
|
||||||
|
|
||||||
|
- `Viewer({ baseLayer: false })` is the modern (≥1.104) way to get a token-free viewer; do not pass `imageryProvider` (removed) and do not touch Cesium ion.
|
||||||
|
- `satellite.propagate` returns falsy/NaN positions for decayed or malformed TLEs — guard every sample; skip sats with < 50 valid samples.
|
||||||
|
- Billboard `rotation` is screen-space CCW; headings are CW-from-north → always negate. Looks right top-down, slightly off at oblique tilt — accepted.
|
||||||
|
- OpenSky callsigns are 8-char space-padded — `.trim()`.
|
||||||
|
- Celestrak `NAME=` queries can return `No GP data found` as plain text — detect (`line1[0] !== '1'`) and treat as empty, not fatal.
|
||||||
|
- Don't enable `requestRenderMode` — the animating clock needs continuous render.
|
||||||
|
- Keep the Cesium credit container visible (attribution is a license condition, not decoration).
|
||||||
|
- If jsdelivr's `cesium@1` alias ever serves a breaking change mid-build, pin to the last known-good exact version and note it in the README.
|
||||||
|
|
||||||
|
## 11. Explicitly out of scope for this build
|
||||||
|
|
||||||
|
GPS-jamming heatmap, real strike/event feeds, oil-futures charts, historical data recording, AI agent ingestion, Cesium ion terrain/3D tiles, accounts/auth. List them in the README roadmap; build none of them.
|
||||||
51
serve.py
Normal file
51
serve.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""GODSIGH dev server: static files + same-origin proxy for feeds that block CORS."""
|
||||||
|
import sys
|
||||||
|
import urllib.error
|
||||||
|
import urllib.request
|
||||||
|
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
|
||||||
|
|
||||||
|
UPSTREAMS = {
|
||||||
|
# OpenSky pins Access-Control-Allow-Origin to its own domain, so the browser
|
||||||
|
# can't call it directly — we forward it here instead.
|
||||||
|
"opensky": "https://opensky-network.org/api/states/all",
|
||||||
|
# Celestrak sends ACAO:* today; proxied too so the app keeps working if that changes.
|
||||||
|
"celestrak": "https://celestrak.org/NORAD/elements/gp.php",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Handler(SimpleHTTPRequestHandler):
|
||||||
|
def do_GET(self):
|
||||||
|
if not self.path.startswith("/proxy/"):
|
||||||
|
return super().do_GET()
|
||||||
|
name, _, query = self.path[len("/proxy/"):].partition("?")
|
||||||
|
base = UPSTREAMS.get(name)
|
||||||
|
if not base:
|
||||||
|
return self.send_error(404, f"unknown upstream {name!r}")
|
||||||
|
url = base + ("?" + query if query else "")
|
||||||
|
try:
|
||||||
|
req = urllib.request.Request(url, headers={"User-Agent": "godsigh-dev/0.1"})
|
||||||
|
with urllib.request.urlopen(req, timeout=45) as r:
|
||||||
|
body = r.read()
|
||||||
|
status, ctype = r.status, r.headers.get("Content-Type", "text/plain")
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
body = e.read() or str(e).encode()
|
||||||
|
status, ctype = e.code, "text/plain"
|
||||||
|
except Exception as e:
|
||||||
|
body, status, ctype = str(e).encode(), 502, "text/plain"
|
||||||
|
self.send_response(status)
|
||||||
|
self.send_header("Content-Type", ctype)
|
||||||
|
self.send_header("Access-Control-Allow-Origin", "*")
|
||||||
|
self.send_header("Cache-Control", "no-store")
|
||||||
|
self.send_header("Content-Length", str(len(body)))
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(body)
|
||||||
|
|
||||||
|
def log_message(self, fmt, *args):
|
||||||
|
sys.stderr.write("%s - %s\n" % (self.address_string(), fmt % args))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
port = int(sys.argv[1]) if len(sys.argv) > 1 else 8137
|
||||||
|
print(f"GODSIGH serving on http://127.0.0.1:{port}")
|
||||||
|
ThreadingHTTPServer(("127.0.0.1", port), Handler).serve_forever()
|
||||||
Loading…
Reference in New Issue
Block a user