12 Commits

Author SHA1 Message Date
Alexandre 89270c01a3 feat(routes): export/import validated routes — make the 100% DB permanent
The oracle-validated route DB (the realistic path to 100% under prose-only)
lived only in the gitignored real.db, so a navdata rebuild would lose it.
Now it is portable and version-controlled:

- routes::export_validated -> TSV of every ifps_ok route (one per pair);
  import_seeds loads them back as reusable seeds (source=seed).
- CLI `export-routes` / `import-routes`.
- discover reuse now matches any ifps_ok route (not just source=ifps), so
  imported seeds are reused instantly (verified round-trip).
- data/validated_routes.tsv: 65 validated European city-pair routes, built
  by bulk-seeding discover over ~85 common pairs (the DB grew ~11 -> 65).

This is the PFPX-style route DB, committed: 65 pairs are now no-error &
instant on any machine; `learn` + more bulk-seed grow it further.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-28 21:45:29 +02:00
Alexandre 36ecaed267 feat(cli): learn — validate a known route and store it as a permanent seed
Completes the route-DB pipeline that is the realistic path to 100% under
the prose-only / independent constraint (pure RAD-prose synthesis can't
guarantee 100% on arbitrary pairs):

- discover: synthesize -> oracle-validate -> store if no-error -> reuse
  instantly next time (verified: 2nd LFPG-EDDF returns via "reused stored
  IFPS-valid route", 0 IFPUV calls).
- learn --from --to --route --fl: validate a user-supplied route against
  IFPUV and, if no-error, record it (source=ifps, ifps_ok=1) so discover
  reuses it forever. --force keeps a still-erroring route for review
  (source=manual, not reused). This is how a real ops route DB grows and
  how the pairs the generator can't yet crack reach 100% (seed once).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-27 15:33:52 +02:00
Alexandre 63e90bbedd feat(rad): preserve airways in mandatory transitions + anchor-on-entry
The transition parser dropped airways, so mandate splices filed all-DCT
(MOVUM DCT HAREM) — wrong routing, and the arrival splice self-looped
(ROUTE49) when the enroute already crossed the arrival area.

- rad: expand_via keeps airway tokens (is_airway_tok); a transition is now
  a point/airway token sequence (DCT where no airway), e.g.
  [MOVUM, T109, HAREM, T104, WLD].
- discover: transition_legs() builds legs with the interspersed airways.
  route_via_arrival_transition anchors on the transition ENTRY (first
  on-airway point) and flies the transition through once — the enroute leg
  stops before the arrival area, so no self-loop. route_via_transition and
  the est/near sorts use the last/first *point* (skip airways).

Files the mandate correctly now (… MOVUM T109 HAREM T104 WLD DCT EDDM).
Coverage 6/10 held, no regression (LFPG-EDDF still passes); LSZH-EGLL
6→4 PROF204; EGLL-EDDM's fake 1-err self-loop is replaced by its real
residuals (CPT dep forbidden, forbidden points on the mandated T104, WLD
STAR limit). 49 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-27 12:24:03 +02:00
Alexandre 29c3f1c185 feat(rad): FRA engine phase 2b — mandatory arrival transitions + top-2 repair
Parse Annex 2B mandatory arrival routings and repair the best few
candidates (not just the lowest-error one):

- core::rad: MandatoryArr + RadData.mandatory_arr +
  mandatory_arr_transitions(ades) -> allowed point-sequences.
- rad crate: parse_mandatory_arr() (VIA-clauses in NOT-AVBL-EXC rows,
  scoped to ARR airports). via_sequences() now scans all parenthesised
  transition groups (contain DCT/airway), not just contiguous "VIA (",
  because sub-option markers separate them ("VIA i) (KARDU DCT KALMO)").
  rad-tool `manarr [ADES]`. LIML now yields KARDU KALMO etc.
- discover: "manarr" candidates route to the transition's on-airway
  anchor then splice the (often off-airway) transition points in via DCT.
- attempt_once repairs the top-2 candidates and keeps the best result:
  the lowest-raw-error candidate isn't always the most repairable (a
  mandate-satisfying route may carry more mechanical errors than a route
  stuck on an unfixable mandatory).

Collapses the dense-FRA near-misses: EGLL-EDDM 7->1 err, EDDF-LIRF 3->1
(mandatory PROF205 resolved; residual is a single ROUTE49). Coverage 6/10,
no regression (passing pairs short-circuit on accept). 49 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-26 10:07:19 +02:00
Alexandre b4ef0107e9 feat(rad): FRA engine phase 2a — mandatory departure transitions
Parse Annex 3A DEP "COMPULSORY FOR TFC" rows into structured mandatory
departure routings and satisfy PROF205 "… ANNEX3A DEP … MANDATORY ROUTE"
by offering the allowed transitions as discovery candidates:

- core::rad: MandatoryDep + RadData.mandatory_dep +
  mandatory_dep_transitions(adep) -> allowed point-sequences.
- rad crate: parse_mandatory_dep() extracts the VIA(...) clauses, expands
  nested "(A, B)" alternatives into separate branches, and chains
  sub-fragments into full sequences from an entry point (46 rules; LFPG =
  20 transitions incl. NURMO CMB VEKIN ADUTO, OPALE KESAX DIMAL ALESO).
  rad-tool `mandep [ADEP]` to inspect.
- discover: for the departure airport, inject "mandep" candidates that fly
  the allowed transitions nearest the destination (additive — the oracle
  still picks best, so no regression risk).

Cracks LFPG-EDDF (Paris-Frankfurt): coverage 5/10 -> 6/10 on the batch,
no regression. 49 tests green. Phase 2b (mandatory arrivals) is next.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-23 15:58:58 +02:00
Alexandre 3fd714eeb2 feat(routing): off-airway + avoid-aware splice, surgical PROF204 seg-bans
Cracks EDDM-LOWW (coverage 4/10 -> 5/10) and generalises DCT-too-long
(ROUTE165) repair:

- airway_path_avoiding(): the splice now builds the avoid-aware graph, so
  re-routing a too-long DCT never re-introduces a RAD-forbidden airway.
- Off-airway absorb: when a broken segment's from-point isn't on any airway
  (a SID/terminal fix, e.g. ALUTU), splice from the previous on-airway
  point through to the target, dropping the DCT-only detour
  (EDDM …BIBAG DCT ALUTU DCT AGNAV -> BIBAG airway AGNAV).
- PROF204 repair now bans the *segment* X<airway>Y (seg_key) instead of
  excluding the points, so a forbidden airway leg out of a SID/STAR gateway
  (BIBAG) no longer nukes the gateway itself.

Batch: 5/10, no regression. 49 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-23 10:10:12 +02:00
Alexandre 49dc74ef20 feat(routing): low-regime fallback when the upper route is FRA-blocked
If discovery's upper-airspace route can't be made no-error because of RAD
restrictions we don't yet model (PROF204 forbidden / PROF205 mandatory
routing common in Free Route Airspace), retry the whole discovery in the
conventional airway regime below the FRA floor (~FL195), where those
restrictions are far sparser. Adopt the low route only if it is fully
valid — the bar is no-error, and flying needlessly low isn't the answer
when neither passes, so the natural-FL route is kept otherwise.

Measured on the batch: no regression, and the fallback collapses the
FRA-blocked pairs from ~7-9 errors to 1-2 (near-miss). It delivers a
no-error plan for any pair that has a valid low-level alternative; the
remaining near-misses need the phase-2 mandatory-routing engine to close.
49 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-22 16:49:54 +02:00
Alexandre 62cee15549 feat(rad): FRA engine phase 1 — Annex-2B forbidden-segment avoidance
Parse the 1135 forbidden airway segments in RAD Annex 2B (rows with
Airway/From/To + "NOT AVBL FOR TFC") into structured ForbiddenSeg records
(FL band + arrival/departure applicability), and route around the ones
that apply to a flight from the start:

- core::rad: ForbiddenSeg + RadData.forbidden_segs + forbidden_segments(
  adep, ades, fl) -> applicable (airway,from,to) triples.
- rad crate: parse_forbidden_segments() wired into parse(); rad-tool
  `forbidden [ADEP ADES FL]` to inspect.
- graph: seg_key() encodes a segment ban into the existing avoid channel;
  build_avoiding skips those segments. discover seeds the avoid set with
  the flight's Annex-2B bans before generating the airway candidate.

Measured: no regression (coverage 4/10 unchanged on the batch) — this is
the *forbidden* half. The coverage mover is mandatory routing (PROF205:
"must route via X"), which pure avoidance can't provide; that's phase 2.
49 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-22 11:13:54 +02:00
Alexandre 927b7eeea7 fix(routing): kill globe-spanning splices + strip computer-nav fixes
Two correctness fixes exposed by a coverage batch where "1 ROUTE130"
near-misses turned out to be catastrophically broken routes (Paris->
Frankfurt routed via Alaska) hidden by IFPS error-masking:

- airway_path now rejects a spliced sub-path that balloons past
  SPLICE_MAX_RATIO x the direct distance. At a fragmented FL the graph
  could route two nearby fixes the long way around the whole network; that
  garbage then showed only 1 (masked) error and beat sane routes in the
  monotonic best-tracking.
- route_item15 strips computer-navigation fixes (unnamed ARINC waypoints
  whose ident carries a digit, e.g. GT27A) proactively, collapsing
  same-airway legs, so IFPS never sees a ROUTE130 designator it would
  reject and stop evaluating at.

Net: coverage numbers are now honest (masked garbage no longer counts as a
near-miss); the real blocker is RAD PROF204/205 on upper/FRA routes.
49 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-22 09:54:55 +02:00
Alexandre 481ac241a8 feat(routing): RAD-forbidden-segment avoidance + stall-tolerant repair
- Graph gains build_avoiding(): drop segments whose endpoint ident or
  airway is in an avoid set. plan_route_conn_avoiding /
  plan_route_best_avoiding thread it through (also from SID/STAR conns).
- New PROF204 repair: parse "TRAFFIC VIA <pts> IS ON FORBIDDEN ROUTE",
  exclude the cited enroute points (never airports), re-plan around them.
  Runs first (a hard "cannot use" beats splice/level moves) and the avoid
  set accumulates across iterations. Other re-plans are now avoid-aware.
- Repair loop tolerates MAX_STALL non-improving steps before reverting, so
  it can cross a valley where excluding one forbidden point forces a
  temporarily worse route en route to a fix. best-so-far still guards the
  result: this can only find an equal-or-better route, never a worse one.

LSZH-LFBZ: 13 -> 2 errors (FRA-border problem gone; remainder is one
Annex-2B-forbidden Swiss departure segment needing real SID modelling).
LFRS-LFST / LFRN-LFMN / LSGG-LFPO still pass, 0 iterations. 49 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-21 12:38:29 +02:00
Alexandre 6a3a885c47 fix(routing): make low-level pairs pass IFPS (LFRS-LFST)
Three router bugs surfaced by testing real filed pairs against the
Eurocontrol IFPUV oracle:

- ROUTE130 repair now collapses the leg chain when dropping an unknown
  designator (a computer-nav fix like GT27A on an airway), instead of
  leaving a dangling `AWY AWY`. Same airway on both sides -> merge; else DCT.
- FL detour ratio 1.6 -> 1.25: the FL-filtered graph fragments the
  conventional (<=FL195) network and forced an absurd London detour for a
  domestic French hop; the short full route now wins.
- FL ceiling-cap: file a short low-level pair at its airways' natural
  ceiling instead of an optimistic FL360 (which yields PROF195 on every
  leg). Gated on upper-network fragmentation so genuine high-level
  long-haul (EGLL-LSZH) is never wrongly lowered.
- New PROF195 repair lowers the cruise FL to where the cited airway exists.

LFRS-LFST now no-error at FL080 (0 iterations); LFRN-LFMN and LSGG-LFPO
still pass and now converge in 0 iterations. 4 new unit tests; 49 green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-21 11:59:41 +02:00
Alexandre 2cb633e99d Initial commit: offline flight planner (Rust, PFPX-class)
Clean-room reproduction of PFPX: route generation + IFPS validation + OFP.
Independent design — official ICAO/EUROCONTROL data only (RAD, FRA points,
IFPUV oracle); no community FPL sources.

Workspace crates: core (routing/discover/rad/navdata/perf/export),
cli, server, rad (Annex parser), gui (Tauri v2 + React + MapLibre).
Route discovery: oracle-in-the-loop repair against Eurocontrol IFPUV.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-21 11:10:35 +02:00