Skip to content

feat: lintle verify --orbit — sampled sgp4 orbit-consistency (#144 core)#162

Merged
elfensky merged 2 commits into
developfrom
feature/verify-orbit
Jul 11, 2026
Merged

feat: lintle verify --orbit — sampled sgp4 orbit-consistency (#144 core)#162
elfensky merged 2 commits into
developfrom
feature/verify-orbit

Conversation

@elfensky

Copy link
Copy Markdown
Owner

lintle verify --orbit — sampled sgp4 orbit-consistency (#144, core slice)

The opt-in physics pass (verify Increment 2, goal 2). For each sampled satellite's epoch-sorted
track, propagate every cleaned TLE forward to its neighbour's epoch with sgp4 and measure the
position residual (km).

Advances #144 — this is the core-first slice the owner chose; the deferred refinements
are tracked in a follow-up (see below). Does not close #144.

Verdict model (safe by construction)

  • soft VRFY-ORBIT-OUTLIER (inconclusive) — a residual over the robust per-satellite
    threshold max(100 km, median + 10·MAD) (flat 100 km floor until ≥10 epochs). Never
    convicts: a real manoeuvre and a corruption are indistinguishable from a single pair, so an
    outlier is telemetry, not a conviction — it does not raise the exit code.
  • hard VRFY-ORBIT-ERRORsgp4 rejecting an element set as physically unphysical
    (error codes 1–5). A safety net: on cleaned (validate-passing) data this is ~0. Decay
    (error 6) is a real end-of-life state, never convicted; it just breaks the propagation chain.

Determinism & correctness

  • Residuals rounded to a 0.1 km quantum before thresholding + a one-quantum guardband →
    byte-reproducible suspect set + exit code across platforms. Golden Vanguard fixture locks
    the exact residuals [8.4, 0.9, 0.6, 0.3, 0.4].
  • Epochs from Satrec.jdsatepoch + jdsatepochF (sgp4's own parse, never re-derived). 3-day
    adjacent-gap gate skips pairs too far apart.
  • Constant memory w.r.t. the corpus (streams through verify's ExternalSorter, one track at
    a time). Deterministic, evenly-spaced satellite sample; default --sample 3000 / --all.

Packaging

sgp4 promoted from dev-only to a verify-scoped runtime dependency. orbit.py is the package's
sole sgp4 importer, lazily imported by run_verify only under --orbit; the
clean/validate/repair path stays walled off (import-graph test still green).

Deferred to the follow-up (core-first)

Leave-one-out spike-vs-manoeuvre culprit isolation, stratified oversampling of repaired/dup-epoch
satellites, the --sensitivity dial, and LEO/MEO/GEO gap-gate tuning.

Verification

  • 782 passed (15 new orbit tests); ruff check + ruff format --check clean.
  • Real bytes: 100k-record tle2006 slice → 8577 satellites, 88025 pairs, 0 hard,
    125 soft outliers
    (exit 0). Example: catalog 879, a 2229.9 km residual flagged inconclusive.

🤖 Generated with Claude Code

elfensky and others added 2 commits July 11, 2026 02:48
, core)

Add the opt-in `lintle verify --orbit` physics pass (Increment 2, goal 2). For
each sampled satellite's epoch-sorted track, propagate every TLE forward to its
neighbour's epoch with sgp4 and measure the position residual (km):

- **soft VRFY-ORBIT-OUTLIER** (inconclusive) — a residual over a robust
  per-satellite threshold `max(100 km, median + 10·MAD)` (flat 100 km floor until
  >=10 epochs). Never convicts: a real manoeuvre looks identical to a corruption
  from a single pair, so a lone outlier is telemetry, not a conviction.
- **hard VRFY-ORBIT-ERROR** — sgp4 rejecting an element set as unphysical
  (error codes 1-5). A safety net: on cleaned (validate-passing) data this is ~0.
  Decay (error 6) is a real end-of-life state, never convicted; it just breaks
  the propagation chain.

Determinism: residuals are rounded to a 0.1 km quantum before thresholding and an
outlier must clear the threshold by a full quantum, so the suspect set + exit code
are byte-reproducible across platforms (golden Vanguard fixture locks the exact
residuals). Epochs come from Satrec.jdsatepoch+jdsatepochF (sgp4's own parse,
never re-derived). A 3-day adjacent-gap gate skips pairs too far apart to compare.

Sampling unit is the satellite (continuity needs a contiguous track); the sample
is deterministic (evenly-spaced over sorted catalogs), default `--sample 3000`,
`--all` for the full sweep. Constant memory w.r.t. the corpus: records stream
through verify's ExternalSorter, one track held at a time.

Packaging: sgp4 promoted from dev-only to a verify-scoped runtime dependency.
orbit.py is the sole sgp4 importer, lazily imported by run_verify only when
--orbit is set; the clean/validate/repair path stays walled off (import-graph
test still green).

Deferred to a follow-up (core-first slice): leave-one-out spike-vs-manoeuvre
culprit isolation, stratified oversampling, the --sensitivity dial, and
LEO/MEO/GEO gap-gate tuning.

Verified on a 100k-record real cleaned slice: 8577 satellites, 88025 pairs,
0 hard, 125 soft outliers (exit 0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
README: a physics-pass paragraph in the verify section + the --orbit/--sample/
--all flags and the soft-outlier exit-code note.

ARCHITECTURE: orbit.py in the verify/ tree + table row (the sole, lazy sgp4
importer); §7 moves sgp4 into the runtime-deps intro and out of the dev-only
exempt list — the promotion has landed.

CHANGELOG: an unreleased `verify --orbit` bullet. CLAUDE.md: the runtime-deps
line and the sgp4-promotion sentence updated to past tense.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant