Strategy C, Stage 0 — the free, local, ~1-week terrestrial-AIS tracking pre-check — is BUILT, fully reviewed, and run-ready (2026-06-08). It is the cheap go/no-go gate that decides whether a live AIS feed can track the PortWatch weekly transit count closely enough (within ~2 ships) to be worth building the expensive Stage 1 (fleet + satellite). New modules live in the existing hormuz_bet crate (~/coding/hormuz_bet); two new binaries: the ais-collect daemon and track-report. Final state: cargo clippy --all-targets -- -D warnings clean; cargo test --lib = 46 passed / 5 ignored; build warning-free. Built subagent-driven (5 units, each two-stage subagent review + a final holistic review). Not a git repo (owner rule: never commit / git init). It has NOT been run for real yet — it must first collect ~7–10 days on an always-on host, and two reality-coupled field-name checks must be confirmed against live payloads before the counts are trusted. This realises the Strategy C plan whose kill-tests passed in hormuz-bet-basis-risk; it is a separate build from the Spec #1 backtest crate in hormuz-bet-build-spec1.
Stage 0 of hormuz-bet Strategy C: a one-week, zero-cost calibration harness that measures how well a cheap terrestrial AIS feed can nowcast the PortWatch weekly count, before any money or satellite spend is committed.
Build Status (2026-06-08)
Strategy C, Stage 0 — “free, local, ~1-week terrestrial-AIS tracking pre-check” — is BUILT, fully reviewed, and run-ready. It is not yet run for real (collection + two field-name confirmations come first; see below).
What “built” means here
New modules added to the existing hormuz_bet crate, project dir /Users/levander/coding/hormuz_bet (same crate as hormuz-bet-build-spec1, not a new project).
Built subagent-driven: 5 units, each with a two-stage subagent review, plus a final holistic review over the whole stage.
Strategy C is the Phase-2 AIS nowcast of the weekly TOTAL / 7-day-MA buckets (its kill-tests passed → GO conditional in hormuz-bet-basis-risk). The one open gate from those kill-tests was: can a live AIS track reproduce the PortWatch n_total within ~2–4 ships? — terrestrial receivers may undercount mid-strait. Stage 0 answers exactly that gate, for free, before committing to the expensive Stage 1 (fleet + satellite).
The gate, in one line
Run the collector ~7–10 days on an always-on machine, then run track-reportafter the next PortWatch Tuesday drop covers the collected days, and read the verdict:
GO — de-biased 7-day-sum error within ~2-ship margin AND the stationarity gap is stationary AND ballpark ok → green-light Stage 1 (fleet + satellite).
NO-GO — kill Strategy C (or escalate to better data).
INCONCLUSIVE — keep collecting; the harness gains power as more days accrue.
Architecture As Built — Modules
All new modules live inside the existing hormuz_bet crate. The data path is: AIS observations → in-polygon transit detection + dedup → our daily counts in SQLite → compared against the first-published PortWatch snapshot → calibration verdict.
geo — geometry
Ray-cast point-in-polygon test.
A provisional Hormuz box (geo::hormuz_zone) — currently a rectangle lon 56.20–56.95 / lat 26.25–26.70. This is a best-guess box pending owner sign-off (see Open Items).
ais — feed ingestion
An Observation type, an AisSourcetrait (so feeds are swappable), and an aisstream.io WSS client implementing it.
transit — PortWatch’s counting rule, reproduced
Implements the PortWatch transit-call rule: in-polygon position + 48h per-MMSI UTC dedup (one vessel counted once per 48h window) + a teleport / sanity spoof filter (rejects physically impossible jumps) + a vessel-type cache → daily counts.
pw_snapshots — every PortWatch snapshot, with first-published = the earliest snapshot for a given day (not the latest revision). This is the scoring target.
track — the calibration verdict
De-biased 7-day-SUM error (window_sum_error over pairs.windows(7)), residual autocorrelation, a ballpark check, stationarity-vs-GFW, and the final verdict. (The two existential fixes below live here.)
gfw — Global Fishing Watch dark-event proxy
Pulls GFW dark-event data as a proxy for AIS jamming / outage intensity, used by the stationarity gate.
portwatch::snapshot — daily PortWatch snapshot
Takes a daily PortWatch snapshot, reusing the Spec #1 PortWatch client (the portwatch/ client from hormuz-bet-build-spec1).
Binaries
ais-collect — the always-on collector daemon (ingests the WSS feed, writes observations / daily counts).
track-report — run after a PortWatch drop; prints the calibration verdict.
The Two Existential Red-Team Fixes (wired into the live verdict path)
These two are the difference between a calibration that catches a fatal flaw and one that hides it. Both are wired into the live verdict path, not just tests.
Fix 1 — GO gates on the de-biased 7-day CUMULATIVE SUM error, NOT daily error
A systematic undercount (terrestrial feed missing some mid-strait vessels every day) is small per day but accumulates over the week — and the market resolves on the weekly sum, so a daily-error gate would wave through a feed that is quietly losing ships all week. Fix: GO gates on the de-biased 7-day cumulative SUM error — window_sum_error computed over pairs.windows(7) — so an accumulating systematic undercount is caught, not averaged away.
Fix 2 — a non-stationary jamming gap forces NO-GO before the GO gate
If the gap between our AIS count and PortWatch is drifting (e.g. GPS jamming is worsening, so our feed degrades over time), then a de-bias term fitted on past days will not hold in future — the calibration is unstable. Fix: a non-stationary gap (|stationarity_r| ≥ 0.6 against the GFW jamming proxy) forces NO-GO before the GO gate is even evaluated. A stationary, predictable gap can be de-biased; a drifting one cannot, so it must kill the strategy rather than be corrected.
Other Correctness Properties (all wired)
First-published scoring — calibration scores against the earliest PortWatch snapshot for a day, not the latest revision. (You must beat the number as it first published, since that is what resolves; later revisions are hindsight.)
Coverage-% exclusion — days with sub-0.5 coverage are dropped, never zero-filled. A partially-observed day must not masquerade as a low-count day.
Silent-dead-feed hardening — a zero-message UTC day forces coverage = 0 (so it is excluded, not read as a genuine zero-ship day), plus a live [FEED SILENT] warning after ~2 min of no messages so a dead collector is noticed immediately rather than silently corrupting the calibration.
Two reality-coupled verification steps before trusting any counts (do on first run)
Two sets of field names were transcribed from spec best-guess and MUST be confirmed against live payloads before the counts mean anything:
The aisstream.io envelope field names + bbox corner order.
The GFW v3 Events API dataset id / request body / response schema.
Both are exercised by the #[ignore] tests, which need a free AISSTREAM_API_KEY and a GFW_API_TOKEN:
cargo test -- --ignored --nocapture
If the live envelope differs from the best-guess, fix the field mapping before reading any track-report output.
How To Run The Pre-check
Stage 0 run sequence (needs an always-on host + free API keys)
# 0. one-time: confirm the two reality-coupled schemas against live payloadscargo test -- --ignored --nocapture # needs AISSTREAM_API_KEY + GFW_API_TOKEN# 1. collect ~7–10 days on an always-on machine (daemon)cargo run --bin ais-collect# 2. AFTER the next PortWatch Tuesday drop covers the collected days:cargo run --bin track-report # prints GO / NO-GO / INCONCLUSIVE
GO → green-light Stage 1 (fleet + satellite). NO-GO → kill Strategy C (or escalate data). INCONCLUSIVE → keep collecting.
Open Owner Items
Two human decisions before / during the run
Sign off / refine the provisional polygon — geo::hormuz_zone is currently a box (lon 56.20–56.95 / lat 26.25–26.70). Confirm or tighten it so the in-polygon transit rule matches the PortWatch chokepoint6 definition (see hormuz-bet-portwatch-data).
Choose the always-on host for the ais-collect daemon (the ~1-week collection needs uninterrupted uptime; gaps become excluded coverage).
Related
hormuz-bet — project index; Strategy C is the Phase-2 AIS-nowcast play, and this is its Stage 0 gate.
hormuz-bet-basis-risk — where the Strategy C kill-tests passed (GO conditional); the single open gate (“can live AIS track PortWatch n_total within ~2–4 ships?”) is exactly what Stage 0 measures. Also the source of the dark-vessel / jamming / spoofing hazard that the teleport filter and GFW stationarity gate defend against.
hormuz-bet-build-spec1 — the Spec #1 backtest crate in the same repo; Stage 0 reuses its portwatch/ client for the daily snapshot. (Separate build, same hormuz_bet crate.)
hormuz-bet-ais-providers — the Phase-2 feed landscape (aisstream, GFW, terrestrial-only caveat) this stage instantiates.
hormuz-bet-portwatch-data — the chokepoint6 definition the provisional polygon must match, and the snapshot source for first-published scoring.
hormuz-bet-backtesting — the validation discipline (first-published, coverage exclusion, no zero-fill) this calibration mirrors.