Starship flight tracker: records SpaceX tracker feed, orbit fits, events (bunko.me/tracker)
  • Python 71.7%
  • JavaScript 18.5%
  • CSS 6.6%
  • HTML 2.9%
  • Dockerfile 0.3%
Find a file
bunko 411d9e5544 README: webcast reader's misread and vehicle-label checks
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NY4jVhZJeANRpBbmNzvCCD
2026-09-29 17:07:15 -05:00
tests Past flights: a lost ship's plan past the end of its data is set aside 2026-09-29 17:06:57 -05:00
tracker Past flights: a lost ship's plan past the end of its data is set aside 2026-09-29 17:06:57 -05:00
.dockerignore Flight tracker data core: ingest, raw archive, orbit fits, Flight 14 analysis 2026-09-29 12:20:30 -05:00
.gitignore Track the Flight 14 test fixtures (ignore only the top-level data dir) 2026-09-29 12:20:40 -05:00
docker-compose.yml Phase 3: live pipeline, API and replay-as-live 2026-09-29 13:59:53 -05:00
Dockerfile Webcast overlay reader: second-by-second speed/altitude from the X broadcast 2026-09-29 13:17:08 -05:00
pytest.ini Flight tracker data core: ingest, raw archive, orbit fits, Flight 14 analysis 2026-09-29 12:20:30 -05:00
README.md README: webcast reader's misread and vehicle-label checks 2026-09-29 17:07:15 -05:00
requirements-dev.txt Flight tracker data core: ingest, raw archive, orbit fits, Flight 14 analysis 2026-09-29 12:20:30 -05:00
requirements.txt Phase 3: live pipeline, API and replay-as-live 2026-09-29 13:59:53 -05:00

Flight Tracker

Records and analyses Starship flights from SpaceX's public vehicle tracker feed, for bunko.me/tracker (UI and API come in later phases; design mockup lives in the proxy repo's design lab at static/proto/lab/tracker/).

Services

Container What it does
tracker-ingest Polls starship_tracker_public.json (both SpaceX hosts). Idle: once a minute, one snapshot archived per 15 min. Armed (T-0 −3 h…+6 h via starship-dashboard /api/next, anything airborne, or an open flight): waits for the next 30 s generation, then polls every 2 s until it lands. Per-host backoff on 429/403/5xx. Detects liftoff/landing.
tracker-webcast Reads the X broadcast overlay: speed (km/h), altitude (km) and the T± clock, 2 frames/s at 720p. Waits for a flight (T-0 and watchUrl from starship-dashboard /api/next), reads from T−15 min to T+4 h, restarts on stream errors. Rows go to the webcast table keyed by broadcast id. ~10% of a core at real time; capped at 1 CPU / 512 MB.

| tracker-web | FastAPI on the web network (port 8000), public at bunko.me/tracker. Serves recorded flights from their cached analysis and runs the live pipeline: polls the DB every second, grows the live flight fix by fix, streams events to clients. Also replays any recorded flight through that same pipeline on a simulated clock. Capped 1 CPU / 512 MB, max 2 simultaneous replays. |

API (tracker/web.py)

Endpoint Returns
GET /health ok + ingest heartbeat age
GET /api/status ingest + webcast health, T-0, live flight summary
GET /api/flights recorded flights (positions: false = a past flight with the webcast only)
GET /api/flights/{id} summary: liftoff, landing window, events (tracking / spacex / webcast), arcs, burns, webcast lag
GET /api/flights/{id}/track fixes + derived speeds, dense coast paths, webcast series (lag-corrected) — ~60 KB gzipped for Flight 14
GET /api/flights/{id}/state?t= state at a moment: measured (last fix + age), estimated (orbit fit / between fixes / last fix), webcast, best readouts with their source, predicted path (stops at 120 km — ballistic is meaningless in the atmosphere), phase
GET /api/live SSE: hello, track, then fix, summary (when events/arcs change), webcast, state (1/s), predicted (every 10 s)
GET /api/live?replay=ID&speed=N&from=S the same stream for a recorded flight on a simulated clock (≤ 60×; S = seconds from liftoff) — a dress rehearsal of the live pipeline. 409 for a webcast-only flight (no fixes to stream), 503 while a flight is live

Live processing is incremental (process.ArcGrower, model.LiveFlight): each fix costs < 1 s even mid-orbit, and a live-grown flight ends with exactly the batch analysis's events (tested).

UI (tracker/static/, served by tracker-web at /)

The console layout chosen in the design lab: globe (or map) pinned on the left, state + events on the right, altitude/speed profile below, time controls at the bottom. Relative URLs, so it works at / and behind Caddy at /tracker/.

  • ./ — live when a flight is on (SSE); otherwise the latest recorded flight, with a banner saying so
  • ?flight=ID&t=S — replay, scrubbed in the browser (1×–300×); readouts, orbit and prediction come from /state
  • ?rehearse=ID&speed=N&from=S — a recorded flight through the live pipeline (≤ 60×)
  • &view=map — flat map instead of the globe

Every readout names its source (webcast, orbit fit, between tracker fixes, last tracker fix). The big numbers use whichever is most current: the orbit fit in coast, the webcast when it's fresh or (live, arriving 15–30 s late) still newer than the last fix. Predictions stop at the 120 km entry interface. Events are marked SpaceX timeline / tracking / webcast; a SpaceX item that tracking confirms is shown once. Chart colours are the validated pair (measured #2B9FD6, predicted #C08428).

Webcast overlay reader (tracker/webcast.py, tracker/webcast_run.py)

  • Frames normalised to 1280×720 grayscale. Clock bottom-centre (22 px digits) or top-left (16 px); four gauge slots L1 L2 / R1 R2 — first of a pair is SPEED, second ALTITUDE (icons can take a slot). "SUPER HEAVY" under the left pair / "STARSHIP" under the right while both vehicles are shown; otherwise it's the stack (before staging) or the ship.
  • Flights 9–13 use the same overlay, with three differences handled: Flights 9–10 draw gauge digits a pixel shorter in a slightly different cut; Flights 10 and 12 show a G-FORCE gauge in R2 late in the flight (rejected by its one-letter unit "G" — "KM/H" is ~25 px wide, "KM" ~13, "G" ~6); Flight 9's coast (T+31–47 min) put the ship's gauges either side of the clock (slots C1/C2). Flights 1–8 used an older text-row overlay the reader doesn't know.
  • Characters are cut out by connected components and matched to reference glyphs (tracker/webcast_glyphs.npz, trained from 44 hand-read frames of Flights 9–14 in tests/data/webcast-*; clock and gauge digits each matched only against their own; leave-one-out: 44/44 frames exact).
  • Frame mission time = clock + time since the clock last ticked (the video lags reality 10–30 s, but every reading carries its exact T+). A clock that disagrees with the video's own timing by > 2 s is held back unless three readings agree (a real jump).
  • A reading is flagged suspect when more of its neighbours (up to three each side) disagree with it than agree, or when it sits in a run of readings (≤ 20 s) that neither neighbouring run can physically reach — misreads come in agreeing pairs and, on Flight 9's centre layout, 12-second runs ("117" read as "11.7"). An altitude never has two decimals.
  • "SUPER HEAVY" read under a pair more than 10 min after staging, over 9,000 km/h or above 130 km is bright ground (the payload-bay camera), not the booster: those readings are the ship's. webcast_run relabel <broadcast> --staging-met S re-applies these rules to stored readings.
  • Backfill a replay: docker compose run --rm --no-deps tracker-webcast python -m tracker.webcast_run backfill <x.com/i/broadcasts/…> --start S --end E (S/E = seconds into the video).
  • Retrain after a layout change: add hand-read frames to tests/data/webcast-labels.json + strips to webcast-frames.npz, then wc.train(*wc.load_fixture(...)).

Past flights (tracker/past_flights.json, tracker/past.py)

Flights 9–13 flew before this tracker (and 9–11 before SpaceX's public tracker, which started with Flight 12), so they have no fixes: speed, altitude and events come from the webcast replay alone. The registry holds each flight's X broadcast, where T-0 falls in the replay, hot-staging time, liftoff (GCAT, J. McDowell) and SpaceX's published timeline (archived mission page). Events are read off the overlay: liftoff (T+0), engine cutoff (speed stops rising), highest point, entry interface (first reading under 120 km), touchdown — or where the overlay's data ends. The webcast delay is the one measured on Flight 14 (4.5 s). Checked on Flight 14 against its tracking: cutoff T+8:21 (tracking window 7:53–8:33), entry within 6 s, touchdown to the second.

# read a replay (≈ 8× real time on one core), then register it
docker compose run --rm --no-deps tracker-webcast python -m tracker.webcast_run backfill \
  https://x.com/i/broadcasts/<id> --start <broadcast_t0_s − 60> --staging-met <staging_s>
docker compose run --rm --no-deps tracker-web python -m tracker.cli past [starship-flight-N …]

The UI shows these with the readouts, events and profile but no map track, orbit or rehearsal.

Data (./data, not in git)

  • raw/YYYY-MM-DD.jsonl[.gz] — lossless archive of every stored snapshot; everything else can be rebuilt from it.
  • tracker.db — SQLite (WAL): fixes, flights, events (+ detail JSON), webcast (overlay readings), status (ingest/webcast health).
  • analysis/<flight>.json — cached analysis (orbit states included); written by cli analyze and by tracker-web 20 min after a live flight lands.

What the feed actually is (learned on Flight 14)

  • A new generation_time every 30 s (sometimes 40 s); published 1–11 s later (sawtooth). spacex.com's own tracker reads the same file, so there's no faster public position source.
  • current = one fix (ECEF + lat/lon/alt, GPS time, mission_time). speed is always 0. trajectory is a 15-min ballistic forecast, not history.
  • mission_time is liftoff-relative only after liftoff → liftoff = gps_time − mission_time (±0.05 s).
  • Position time-tags have ~0.5 s noise, occasionally 1–1.6 s (km along-track): isolated misses are bad fixes, not manoeuvres.

Analysis (tracker/process.py)

Coast arcs are grown fix by fix against a two-body + J2 orbit fit (tracker/orbit.py, robust least squares). Clean arcs give velocity, orbit elements and predictions; where two arcs meet gives burn time and Δv (Flight 14 orbital insertion: T+25:20, 96 m/s — SpaceX: 25:17–25:36; physics: ~97 m/s). Elsewhere speed comes from local robust smoothing. Events are tagged tracking or spacex (CMS timeline); timeline items planned after the vehicle was already down are set aside, not shown as events.

Commands

docker compose up -d --build                     # run the recorder
docker compose run --rm --no-deps tracker-ingest python -m tracker.cli status
docker compose run --rm --no-deps -v /path:/imp:ro tracker-ingest python -m tracker.cli import /imp/capture.jsonl
docker compose run --rm --no-deps -v $PWD/tests/data:/td:ro tracker-ingest \
  python -m tracker.cli analyze ship41 2026-09-28T09:00:00+00:00 2026-09-28T18:00:00+00:00 \
  --id starship-flight-14 --name "Starship Flight 14" --timeline /td/flight-14-timeline.json
uv run --with-requirements requirements.txt --with-requirements requirements-dev.txt python -m pytest -q

Tests pin the Flight 14 results (tests/data/flight-14-raw.jsonl.gz is the merged raw recording).