Applied 4 confirmed findings from the multi-agent review (5th was a documented deliberate deviation): - aircraft: self-healing visibilitychange listener — the live feed no longer stalls permanently after tab hide->show while the clock is paused (MAJOR) - SPEC §9: nginx proxy_pass with $is_args$args needs a resolver or prod 502s while nginx -t still passes — documented + hardened the deploy step (MAJOR) - ships: live-AIS map now evicts true least-recently-updated (move-to-end), not oldest-inserted - ships: corrected inverted Hormuz lane direction labels (tracks with decreasing longitude sail west/inbound, not east) - README: full data-source + attribution table, real-vs-DEMO breakdown, architecture, roadmap Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
24 KiB
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
- No build step. Vanilla ES modules + CDN globals. No npm, no bundler, no framework.
index.htmlloads 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(globalCesium; 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(globalsatellite). - Before the deploy phase, pin both to the exact resolved versions (read
Cesium.VERSIONin the console) so prod doesn't drift.
- Cesium:
- Subpath-safe URLs everywhere. The app must work at both
http://127.0.0.1:8137/andhttps://partly.party/godsigh/. Every fetch and asset reference is relative (proxy/opensky?...,css/style.css) — never a leading/. - Dev server =
serve.py(already in repo): stdlib-only static server + same-origin proxy atproxy/openskyandproxy/celestrak, port 8137. In production, nginx location blocks replace the proxy (§9.4) —serve.pyis dev-only and must not ship into the web root. - 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
BillboardCollectionprimitive (thousands; entities would crawl) with a custom click overlay. - 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) useSampledPositionProperty/availabilityso 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). - 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 likephase 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 andproxy/opensky?lamin=24&lomin=50&lamax=27&lomax=57returns 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)
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
UrlTemplateImageryProviderlayers — Cartodark_all(Data Mode,subdomains:['a','b','c','d'], credit "© OpenStreetMap contributors © CARTO") and EOXs2cloudless-2023_3857(Photo Mode, credit "Sentinel-2 cloudless by EOX — modified Copernicus Sentinel data 2023"). Photo layer startsshow = false. Mode toggle flips the twoshowflags. Keep both credits — attribution is a license requirement for Carto, OSM and EOX. - Clock: set
startTime/stopTime/currentTime,clockRange: Cesium.ClockRange.CLAMPED,multiplier: 1, thenviewer.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: titleGODSIGH ▸ WORLD VIEW, mode toggle buttons, one row per layer (checkbox + name + colored status dot + status text), aLIVE / SCRUBBEDchip, and a footer crediting data sources. Styling: near-black translucent panels (rgba(10,14,18,.82)+backdrop-filter: blur(8px)), 1pxrgba(255,255,255,.08)borders,ui-monospacefont, cyan#35e0ffaccents. 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):
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=fromconfig.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 andon_ground. - Render: one shared canvas-drawn triangle glyph (32×32, white — draw
(16,2)→(28,30)→(16,23)→(4,30), close, fill) in aBillboardCollection; per-billboardcolortint by altitude band (< 3000 m#ffb347, < 9000 m#ffe74d, else#4dd2ff),rotation: -Cesium.Math.toRadians(track || 0), position atbaro_altitudemeters,scale~0.55. Setid: { 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 showrate-limited — anonymous OpenSky quotain the status line. Show count + data timestamp +x-rate-limit-remainingwhen available. - Picking:
ScreenSpaceEventHandlerLEFT_CLICK →scene.pick; ifpicked?.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:
- 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. - 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. - One dark vessel
#ff4d4d:availability= twoTimeIntervals —[start, now−2h]and[now+1h, stop]. Between its last-seen and reacquired positions draw a red dashed polyline (PolylineDashMaterialProperty) with a mid-track labelAIS DARK GAP [DEMO]. This demos the dark-vessel-detection concept while scrubbing. - 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 toSCRUBBED — 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.jsAPI:addLayer(id, name, defaultOn, onToggle)andsetStatus(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 emptyAISSTREAM_API_KEYslot 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):
preview_start, thenpreview_console_logs(level error) — zero uncaught errors allowed.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.preview_screenshotin both modes;preview_snapshotto confirm HUD text/toggles.- 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−3hevent marker pops in; drag to future → satellites keep orbiting; toggle every layer on/off; toggle both basemap modes. - 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('/andsrc="/— 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:
- Read
~/Documents/GAMES/CLAUDE.mdand one existing deploy script (e.g.GAMES/cratewars/deploy.sh) and mirror the doctrine: bundle → rsync to/tmpstaging onhumanjing@100.71.119.27→docker cpintoforum-nginxweb root undergodsigh/→ curl verify. Ignore the stale~/Documents/VPS_DEPLOYMENT_GUIDE.md/DEPLOYMENT_STATUS.md. - Run
/ship-checkbefore 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). Neverserve.py,SPEC.md,.claude/. -
Pin CDN versions (§2.1) before shipping.
-
Proxy in prod: add an nginx location to the
forum-nginxconfig (find it viadocker 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).CRITICAL — resolver required. A
proxy_passwhose value contains a variable (here$is_args$args, and theset $ups_*host below) makes nginx resolve the upstream at request time, not config-load time. Without aresolverdirective in scope, every proxied request returns HTTP 502 "no resolver defined to resolve …" — andnginx -tstill PASSES, so the config test gives false confidence. Confirm one exists (docker exec forum-nginx nginx -T | grep -i resolver); if not, add one athttp/serverscope:
resolver 1.1.1.1 8.8.8.8 valid=300s ipv6=off; # add once if absent
location = /godsigh/proxy/opensky {
set $ups_opensky opensky-network.org;
proxy_pass https://$ups_opensky/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 {
set $ups_celestrak celestrak.org;
proxy_pass https://$ups_celestrak/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. nginx -t passing is NOT sufficient — it does not catch a missing resolver. The proxy curl below (must return JSON, not a 502 or HTML) is the hard gate.
- Cloudflare: ask John for the fresh token at this point; purge
partly.party/godsigh/*after deploy. Beware Cloudflare caching the proxy endpoints — theno-storeheader above matters; if aircraft data looks frozen, checkcf-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 passimageryProvider(removed) and do not touch Cesium ion.satellite.propagatereturns falsy/NaN positions for decayed or malformed TLEs — guard every sample; skip sats with < 50 valid samples.- Billboard
rotationis 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 returnNo GP data foundas 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@1alias 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.