Skip to content

Releases

v1.2.0

Choose a tag to compare

@AlanBacker AlanBacker released this 04 Nov 04:00

Every answer is now checked against the traced curves. When nothing fits, score says so and lists the likely causes. This release also fixes clock and pairing bugs and makes the fit's error bars honest. The six-seed self-test gives the same errors and verdicts as v1.1.1.

Fixed

  • Scoring dropped candidates the search had admitted. The search accepts an event-1 match up to the clock tolerance plus the event's CPA-time tolerance t_tol away. Scoring could shift time by only ±4 s and used its own clock tolerance (default 5 s), so candidates further out were dropped without a word. The time shift now spans at least t_tol (capped at 30 s), and score takes the search's clock tolerance when --rec-start-tol is not given. Candidates rejected by the clock are counted, and an empty result explains itself.
  • Offset pairing lost the time term with large node ids. Node and time were packed into one float. With global grid ids (around 2.5·10¹⁴), the time difference rounded away, so pairs an hour off still matched. The key is now an exact integer (node rank × slot + milliseconds). It is checked against brute force for compact and global ids. Normal search runs used compact ids and were not affected.
  • Fit sigmas were too small for correlated residuals. The covariance is now inflated by the residuals' measured correlation time, max(win/hop, τ). In the self-test scenarios τ is 3–38 points, so sigmas grow by up to 2.7×. Straight synthetic passes are unchanged. The fit reports resid_corr_time, sigma_inflation, n_eff and corr_v_d.
  • detect stdout and its file looked like different schemas. Stdout now also names events_file. The docs say that boxes.json is the event list and that the status is in boxes.diag.json.

New

  • Trace check. Each hypothesis' model curve is compared with the traced points within ±60 s of each CPA, using a robust RMS against the event's own linear fit. It passes at ≤ max(4×, +1 Hz). Bright texture under a wrong curve (speech, music, wind) can lift a brightness score, but it cannot make the model reproduce the trace. With two events, failing hypotheses rank below the rest, and alternatives and margins are computed among those that pass.
  • Verdict keywords, in order of precedence:
    • NO CONSISTENT SITE (two events, nothing passes; diagnosis lists the causes);
    • STRIP ONLY (tracks cross at < 25°);
    • DEGENERATE (one event, three or more other aircraft within 2 %);
    • AMBIGUOUS;
    • unique within scored set.
  • result.json gains n_consistent, scoring (scored, rejected_clock, no_track, delta_max_s) and diagnosis. best.trace_check gives per-event rms_hz, ref_hz, ratio and ok. An f0 scale at its limit is flagged but not penalised.
  • Search stats report the following:
    • crossing_angle_deg (a log line says when every pair crosses at < 25°);
    • flight_pairs (how many different aircraft explain one event);
    • ground_elev_m;
    • clock.
  • trace reports box_coverage. Below 50 % it adds a hint (--no-monotone for a curve that rises before it falls, or a narrower box). fit adds d_fit_min/d_fit_max (3 sigma) and a note when the search's distance window is much wider.

Docs (English and Chinese)

  • One event needs a small area as well as a clock.
  • The silence filter does little in sparse airspace.
  • Sigma is statistical only: a speed change biases v by far more than sigma.
  • What the distance window is.
  • The DEM matters in relief.
  • A new verification section covers what to do when no site is consistent: different places or a wrong file offset, an aircraft missing from ADS-B, a site outside the bbox, or a bad trace.

Measured on synthetic data

  • Six self-test seeds give the same errors, verdicts and regions as v1.1.1.
  • True two-event answers measure 0.3–1.8× the reference per event.
  • Wrong aircraft pairs exceed 15× on at least one event. The true pair at a site 2–6 km away reaches 2.7–15×, so the check rules out wrong aircraft, not every wrong site; the brightness score still ranks those below the true site.
  • With one event, wrong aircraft 30 km away can pass (1.3×). The check is reported there but does not reorder.
  • Event 2 shifted by +300 s gives NO CONSISTENT SITE with a diagnosis.
  • One event with a 2 s clock and t_tol = 20 s: all 492 candidates are scored, and the error is 23 m.
  • Real recordings were not part of these tests.

Downloads

File Language
audio-flyover-geolocation-en.skill English
audio-flyover-geolocation-zh-CN.skill 简体中文

Reinstall over v1.1.1 the same way you installed it.

v1.1.1

Choose a tag to compare

@AlanBacker AlanBacker released this 03 Nov 23:01

Fixes for merged fly-overs, rising tones and very short files. The six-seed self-test reproduces the v1.1.0 numbers exactly.

Fixed

  • Separate fly-overs were merged into one box. Overlapping 60 s windows chained neighbouring hits, so two passes a minute or two apart came out as one long candidate. Each group is now split again where its ridge loses support for more than 10 s of usable audio, and at turning points where the ridge rises again (one fly-over only falls in frequency). The flat approach and departure stay with their pass. Groups holding a single pass keep the box they had before.
  • A rising test tone counted as a fly-over. Every event now has a frequency trend (descending, flat) and drop_pct. Ridges that rise are not returned; boxes.diag.json lists them under rejected. Because a strong tone hides what lies under it, its ridge is masked and the stretch is searched again, so a pass beneath it is still reported. audit counts only falling ridges: a rising test tone is no longer medium, and a pass hidden under a tone makes the result undetermined rather than low.
  • Very short WAVs crashed audit. Clips with 1–4 spectrogram frames (about 1–1.8 s) failed in transient gating, and files shorter than the 1 s analysis window raised an error. detect now reports insufficient for them. audit returns undetermined, with no PNG when there is nothing to draw, and spectrogram exits with code 2 and a message.

Changed

  • Event fields trend, drop_pct, warnings (and note for passes found under a tone). warnings flags a box whose ridge rises again or falls by more than 45 %: one propeller pass falls by 2v/(c+v), which is 36 % at 75 m/s. Such a box probably joins two aircraft or two harmonics, and audit counts it as two.
  • boxes.diag.json gains rejected. The audit output gains notes and png.
  • detect logs each event's trend and every set-aside ridge.

Measured on synthetic data

  • Two passes 90–180 s apart: two events in 12 of 12 cases (v1.1.0: 4 of 12, only at 180 s).
  • Passes 40–60 s apart that are audible at the same time often stay one box (6 of 8), but each of those boxes carries a warning.
  • Single passes from 150 m to 2 km (144 clips) and 72 noise-only clips: the same detections as v1.1.0, with no warnings and no set-aside ridges.
  • Real wind and traffic noise were not part of these tests.

Downloads

File Language
audio-flyover-geolocation-en.skill English
audio-flyover-geolocation-zh-CN.skill 简体中文

Reinstall over v1.1.0 the same way you installed it.

v1.1.0

Choose a tag to compare

@AlanBacker AlanBacker released this 03 Nov 22:10

Fixes for short recordings and the privacy audit. Results on long recordings are unchanged: the six-seed self-test reproduces the v1.0.0 numbers exactly.

Fixed

  • Short clips were never detected. With fewer than six usable 60 s windows (clips under about two minutes), detect compared each window with a median of itself and could not flag anything, however clear the curve. Such clips, and recordings that are tonal almost throughout, are now compared with a calibrated noise model: the same pipeline run on synthetic Gaussian noise for the same window length. Whitening makes that level independent of the noise's loudness and colour; it matched the background of all six self-test recordings within ±0.06 dB.
  • audit reported low when it could not judge. An empty detection no longer means low risk. Insufficient evidence now gives undetermined, a noise-model check gives low, limited check, and the output includes the detection status, its reasons and what a low rating covers.
  • Harmonic stacking was silently off in detect. Over 40–400 Hz under a 600 Hz spectrogram only one harmonic fits for every candidate, so --harmonics 3 acted as 1. The count used is now reported by detect and trace, detect defaults to the honest value of 1, and a band above the spectrogram top keeps the fundamental instead of summing to zero. Per-candidate harmonic counts were tried and made both detection and tracing worse in the self-tests, so they were not adopted.
  • The last seconds of a recording were never examined. The final detection window now ends at the end of the recording.

Changed

  • detect writes diagnostics to <out>.diag.json (status ok / limited / insufficient, reasons, baseline, harmonics used, hits shorter than --min-dur). boxes.json keeps its format.
  • New detect options: --window, --step, --null-db.
  • New regression tests (tests/test_detection.py), run in CI.

Measured on synthetic data

  • 73 noise-only clips (12–300 s, with mains hum and speech bursts): no false events.
  • Passes within 1.5 km in 20–69 s clips: 48 of 48 detected; at 2 km, about half.
  • Real wind and traffic noise were not part of these tests.

Downloads

File Language
audio-flyover-geolocation-en.skill English
audio-flyover-geolocation-zh-CN.skill 简体中文

Reinstall over v1.0.0 the same way you installed it.

v1.0.0

Choose a tag to compare

@AlanBacker AlanBacker released this 03 Nov 19:53

First release of the audio fly-over geolocation skill: estimate where a recording was made from the Doppler curves of aircraft audible in the background, matched against ADS-B track history.

Downloads

File Language
audio-flyover-geolocation-en.skill English
audio-flyover-geolocation-zh-CN.skill 简体中文

Both packages share the same tested Python pipeline (scripts/); only the instructions and references are translated.

Claude Code: unzip into ~/.claude/skills/ (or copy skills/<lang>/audio-flyover-geolocation/ from this repo), then pip install numpy scipy matplotlib.
Claude apps: upload the .skill file in the Skills section of settings, with code execution enabled.

What's in it

  • Spectrogram → harmonic ridge tracing → Doppler fit (closest-approach time, speed, miss distance) with a split fit and clock-offset mask
  • ADS-B ingest for OpenSky / ADS-B Exchange / CSV exports, corridor → CPA → timing → silence search funnel
  • Joint scoring of two or more fly-overs with a shared clock offset, hill-climb refinement, a 0.9 confidence region and plateau / mirror / geometry warnings
  • Blind synthetic self-test (afg_cli.py selftest) and a report template

Self-test results (blind, synthetic)

Seed Two events ±2 s, turning pass ±2 s, straight pass
7 24 m 23 m mirror (flagged)
11 477 m (3° crossing, inside 849 m region) 35 m mirror (flagged)
23 46 m 79 m 162 m
42 31 m 86 m 206 m
101 53 m 48 m 213 m
2026 25 m 96 m 6 m

With ±30 s clock uncertainty and one pass, the result is a 2.1–4.8 km strip that contains the truth in every seed.

Real recordings are harder than synthetic ones: wind, reflections, engine-tone drift and incomplete ADS-B coverage all widen the region. See the README's limitations and responsible-use sections before relying on a result.