Commit Graph

14 Commits

Author SHA1 Message Date
jing
bf4d1d16de Wave 3 spec for Opus: the layer registry + generic GeoJSON/imagery factories
Refactor quakes/fires onto a data-driven registry, then seed ~6 verified new
real feeds (adsb.lol military [needs proxy], NWS alerts, GDACS disasters,
Launch Library, NOAA aurora, NSW RFS bushfires) under a categorized collapsible
HUD, plus NASA GIBS time-dimensioned imagery. GDELT news is investigate-first.
CORS/shape ground truth for all 8 candidates probed and tabled in §1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 18:22:56 +10:00
jing
403884e480 wave2: adversarial-review fixes (9 confirmed findings)
Ran a 7-reviewer × per-finding-verifier workflow over the Wave 2 code; 9 of 13
raw findings survived adversarial verification. Fixes:

- quakes.js: refresh now UPDATES revised quakes, not just dedupes — a signature
  (mag|place|depth|time) per id detects USGS revisions and rebuilds that entity,
  so a quake revised M4.4→M5.2 no longer stays visually understated (SPEC2 §2
  "update, don't duplicate").
- aircraft.js: (a) poll() drops its result if the clock scrubbed off-live mid-
  fetch, so late live data never paints over the replay view; (b) re-enabling the
  aircraft layer while scrubbed now restores the replayed frame instead of
  leaving it hidden; (c) transient (non-404) history errors schedule a bounded
  retry so a paused clock doesn't wedge on an error frame.
- satellites.js: ground track centers on the VIEWER clock (not wall-clock now)
  so it stays under a scrubbed satellite; and it hides when the Satellites layer
  is toggled off (no stray dashed line).
- main.js: malformed camera hash with blank tokens (#c=,,,,) is now rejected
  instead of applying a bogus (0,0,0) camera — falls back to the default view.
- serve.py: history t-parse catches OverflowError (t=inf/1e999 → 400, not a
  crashed handler); stale-on-error now also covers upstream 5xx, not just 429.

Verified: t=inf/-inf/1e999/nan → 400; malformed hash → default Gulf camera;
ground track 427 km from the scrubbed dot (was ~2 orbits off) and hidden on
layer-off; aircraft re-enable during replay restores 6062 billboards; zero
console errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 17:16:24 +10:00
jing
8f3acf9470 wave2 phase 6: GPS-jam stretch — investigated, held pending permission
Probed gpsjam.org per SPEC2 §7's investigate-first rule. Findings: the data is
real and clean (https://gpsjam.org/data/<YYYY-MM-DD>-h3_4.csv, columns
hex,count_good_aircraft,count_bad_aircraft, H3 res-4, published ~04:00 UTC for
the prior day). But two blockers: no Access-Control-Allow-Origin (browser can't
fetch directly — would need a serve.py proxy), and ambiguous licensing (no
stated terms; derived from ADS-B Exchange, which is non-commercial and
protective of redistribution). Per spec — ambiguous licensing → document and
stop, don't scrape around an unwilling origin — the layer is NOT built; the full
findings are recorded in the README roadmap so John can decide after getting
permission from John Wiseman / ADS-B Exchange.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 16:56:28 +10:00
jing
2e061b2cdf wave2 phase 5: polish pack (military, ground tracks, URL state, screenshots)
1. Military callsigns (aircraft.js): callsigns matching CONFIG.aircraft.
   militaryPrefixes tint red regardless of altitude band and bump larger. New
   "Military filter" HUD row (default off) shows only military aircraft when on;
   status "N military of M". Pick overlay shows Military/Civil class.
2. Satellite ground track (satellites.js): selecting a satellite draws its
   sub-satellite path (height 0, ±half orbit around now) as a dashed polyline in
   the sat's colour; deselect hides it; one track at a time. Recomputed on
   demand from the satrec (no upfront precompute of 97 tracks).
3. Shareable URL state (main.js + ui.js): camera / mode / layer toggles / clock
   offset serialize to location.hash (replaceState, 1 s cadence, only on change)
   and are restored on boot; malformed hashes ignored silently.
4. Screenshot endpoint (serve.py): dev-only POST snap?name= writes a base64 PNG
   body to docs/<name>.png (name sanitized to [a-z0-9-], can't escape docs/).
   Captured docs/screenshot-data.png + screenshot-photo.png (render() called
   synchronously before toDataURL — the preserveDrawingBuffer pitfall).

README rewritten for Wave 2: quakes/fires layer rows (marked REAL), replay,
shared cache, shareable links, ground tracks, military, screenshots, updated
architecture + roadmap.

Verified in browser: 11 military of 6062 + filter; ground track 180 pts on
select / hidden on deselect; URL round-trips camera+mode+layers+clock; both
screenshots written as valid 1280x720 PNGs; subpath audit clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 16:53:05 +10:00
jing
767884146d wave2 phase 4: historical record & replay (true time-travel)
Scrubbing off-live now REPLAYS actual recorded traffic instead of just hiding
aircraft (orbits were already computable in the past; live traffic was not).

- record.py (new, dev-only daemon): every 180s fetches through the local proxy
  (so the shared cache dedupes quota — never hits OpenSky directly), reduces to
  airborne essentials, zlib-compresses into SQLite data/history.db (WAL), keeps
  72h, prunes + checkpoints hourly. Wired into John's ~/.jobs heartbeat.
- serve.py: GET history/aircraft?t=<epoch_s> returns the nearest snapshot within
  ±10 min (read-only per-request sqlite connection — WAL means never locks the
  recorder), else a JSON 404.
- aircraft.js: onClockTick drives replay when not-live. Build loop factored into
  renderPlanes() shared by live + replay. Replayed aircraft show amber ("replay
  · <time> · N aircraft"); 404 → "no history for this time" + hide (this is the
  prod-without-recorder path too). Requests coalesce so a scrub landing mid-fetch
  still resolves to its final snapshot; debounced on requested-time distance.
- data/ gitignored (recorder db is local-only, never committed or deployed).

Verified: history endpoint hit/404/400; frontend replay across -1h/-2h/-3h maps
to distinct snapshots, 404 transitions, live-resume; recorder daemon writes
heartbeat + snapshots.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 16:30:17 +10:00
jing
108680462e wave2 phase 3: shared OpenSky cache (quota-collision fix)
serve.py now implements the OpenSky shared cache v1 contract (SPEC2 §4), so
GODSIGH and godstrument stop racing each other into the anonymous daily quota:

- Serve-from-cache: a fresh (<120 s) GLOBAL states request returns the cached
  body with X-Godsigh-Cache: hit, spending zero upstream quota.
- Write-through: a healthy global fetch writes the raw body to
  ~/.cache/godverse/opensky-states.json (OPENSKY_CACHE_FILE override) atomically
  — unique temp beside the target, then os.replace — so concurrent writers
  never corrupt or expose a half-written cache.
- Stale-on-error: on upstream 429 or connection failure, serve the last-known
  cache with 200 + X-Godsigh-Cache: stale instead of failing the client.
- Bbox requests bypass the cache (the app default is global).

Verified: miss (4.0s real fetch, quota was down to 2) → hit (0.0014s) →
stale (backdated cache + upstream 429 → 200 stale) → bbox bypass (no cache
header). App served 6062 aircraft straight from cache while quota exhausted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 16:12:36 +10:00
jing
3ca9409f31 wave2 phase 2: wildfires layer (real NASA EONET)
New js/layers/fires.js — fetches EONET open wildfire events directly (ACAO:*),
refreshes every 15 min, rebuilt wholesale (no time dynamics — EONET events are
slow-moving). Uses the latest dated geometry per event; Point coords direct,
Polygon reduced to first-ring centroid. Flame-teardrop billboard (new
lib.flameGlyph, drawn in #ff7a1a with dark outline, shared canvas), aggressive
label translucency so dense California/Australia clusters stay legible, cap 500.
HUD row "Wildfires (NASA EONET)".

PITFALL handled: EONET mislabels Content-Type as application/rss+xml but the
body is JSON — we call res.json() and never gate on the header.

Verified in browser: 500 fires, all finite positions, glyphs render over
North America, zero console errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 16:05:28 +10:00
jing
dadc068271 wave2 phase 1: earthquakes layer (real USGS, time-anchored)
New js/layers/quakes.js — fetches the USGS all_day GeoJSON directly (ACAO:*,
no proxy), refreshes every 5 min regardless of scrub state. Quakes are
time-anchored via availability: in-window quakes appear as the slider crosses
their origin time, older-than-window quakes stay visible the whole window
(intervals clamped to [start, stop]). Magnitude ramps amber→red, size scales
with magnitude, quakes <1h old pulse (age-aware callback), labels only for
M>=4.5. Deduped by feature id across refreshes, capped at 400 (smallest mag
dropped first). HUD row "Earthquakes (USGS)" with count/max-mag status.

Also: serve.py now sends Cache-Control: no-store on all responses (was
proxy-only) so iterative js/css edits always take effect on reload — the
module graph was caching stale copies during dev.

Verified in browser: 223 quakes, 11 labeled, time-anchoring confirmed
(187 visible at -6h → 223 at now), zero console errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 16:01:59 +10:00
jing
ac811a75fe Wave 2 spec for Opus: quakes/fires layers, shared OpenSky cache, record/replay, polish pack
Verified ground truth included: USGS + EONET both send ACAO:* (EONET mislabels
its JSON as rss+xml). Defines the ~/.cache/godverse opensky cache contract
shared with godstrument's crossover brief.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 13:33:57 +10:00
jing
7da9eeaeb0 phase 7-8: adversarial-review fixes + README
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>
2026-07-13 12:47:11 +10:00
jing
960dcd0e69 phase 6: interaction polish + readability fixes
- fix ships status: registered the HUD row before setStatus (setStatus no-ops
  on an unregistered row, so the vessel count was silently dropped)
- declutter: drop the 6 stacked Fujairah idle labels (InfoBox still IDs them),
  fade ship + event labels at the global overview (translucencyByDistance)
- separate the two co-located event markers so their labels stop colliding
- verified live: 97 sats, 6300+ aircraft w/ quota, 12 vessels, scrub-gating
  (chip flips to SCRUBBED + aircraft hide), Data+Photo basemaps

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 12:32:04 +10:00
jing
6c7b945848 phases 2-5: satellite, aircraft, ship, infra & event layers
- satellites: Celestrak TLE -> SGP4 -> time-dynamic entities w/ glowing orbit
  paths; per-source cap + reordering so COSMOS/ISS aren't starved by the 72-sat
  Gaofen fleet (now ~97 sats, diverse); chunked propagation, orbital InfoBox
- aircraft: global OpenSky ADS-B -> BillboardCollection primitive (6444 planes
  verified), altitude-banded, quota/scrub/visibility-gated polling, click overlay
- ships: [DEMO] fleet through Hormuz incl. toll-route carrier, dark-vessel AIS
  gap, Fujairah idle cluster; optional aisstream.io live path
- infra: choke-point rings, Petroline + Habshan-Fujairah pipelines, 9 facilities
- events: [DEMO] time-anchored pulsing markers that appear as the slider crosses
- authored + adversarially hardened via parallel agent workflow

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 12:25:51 +10:00
jing
7356d62381 phase 1: globe core — viewer, dual basemaps, clock, HUD, layer contract
- CesiumJS viewer (token-free, baseLayer:false), Carto dark + EOX Sentinel-2
- clock spans now +/-6h CLAMPED; built-in timeline/animation = the space-time slider
- HUD: brand, LIVE/SCRUBBED chip, Data/Photo mode toggle, layer rows, credits
- lighting is mode-aware: OFF in Data (high-contrast asset map), ON in Photo
  (realistic day/night terminator) — refines SPEC 6.1's global-lighting note
- main.js defines the layer-factory contract + dynamic loader; layers land next
- serve.py now forwards OpenSky X-Rate-Limit-* headers for the aircraft status line

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 12:06:05 +10:00
jing
0fec269512 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>
2026-07-13 11:50:09 +10:00