mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-21 21:41:12 +00:00
a11ed15187
ADR 0002: hexagonal/ports-and-adapters architecture — components/ layout, protocol.py per component, composition root, core/ for concentrated math. ADR 0003: @dataclass(slots=True, frozen=True) on hot path; Pydantic retained only at REST/config/DB boundaries. Pose/GPSPoint migration deferred to Phase 2. ADR 0004: Stage 2 as independent iteration — own phases 1-6, own requirements, stage1 code treated as MVP starting capital. PROJECT.md: Stage 2 Key Decisions updated from Pending → Accepted with Phase 1 implementation notes, deferred work list, and final architecture summary. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
177 lines
13 KiB
Markdown
177 lines
13 KiB
Markdown
# Requirements: GPS-Denied Onboard Navigation System — Stage 2
|
||
|
||
**Defined:** 2026-05-10
|
||
**Stage:** 2 (independent iteration)
|
||
**Branch:** `stage2` (HEAD = stage1; v1.0 archived)
|
||
**Core Value:** The flight controller must receive valid MAVLink GPS_INPUT at 5-10Hz with position accuracy ≤50m for 80% of frames — without this, the UAV cannot navigate in GPS-denied airspace.
|
||
|
||
---
|
||
|
||
## Stage 2 Requirements
|
||
|
||
Stage 2 is a self-contained iteration. Phases are numbered 1–6 within this stage. Stage 1 work (its 36 v1 requirements + 7 phases) is archived in `.planning/archive/v1.0/` as starting capital, not as active backlog.
|
||
|
||
The stage 1 codebase (ESKF + cuVSLAM + GPR + MAVLink + pipeline + 195 passing tests) is treated as MVP — refactoring is allowed and expected. Concept-level ideas from the parallel `try02` branch are re-implemented (not merged).
|
||
|
||
### ARCH — Hexagonal architecture & composition
|
||
|
||
- [ ] **ARCH-01**: Codebase reorganized to `src/gps_denied/components/{vio, satellite_matcher, gpr, anchor_verifier, safety_state, flight_recorder, mavlink_io, coordinate_transforms}/`, each containing `protocol.py` + concrete implementations + (where applicable) `native/` for backend bridges
|
||
- [ ] **ARCH-02**: Hot-path data types (`FrameState`, `IMUSample`, `PositionEstimate`, `VOEstimate`, `SatelliteAnchor`) implemented as `@dataclass(slots=True, frozen=True)` in `src/gps_denied/hot_types/`; Pydantic retained only for REST/config/DB boundary schemas
|
||
- [ ] **ARCH-03**: Explicit DI composition root `src/gps_denied/pipeline/composition.py` exposes `build_pipeline(env: Literal["jetson", "x86_dev", "ci", "sitl"]) -> Pipeline` that wires environment-specific implementations
|
||
- [ ] **ARCH-04**: `core/` retained for concentrated math (ESKF, factor graph, RANSAC, coordinate transforms) — these stay as pure-function single files, NOT split into `interfaces.py + types.py + impl.py`
|
||
- [ ] **ARCH-05**: All component Protocols defined with `typing.Protocol`; concrete adapters implement them; `Pipeline` constructor takes Protocol-typed dependencies (no concrete imports inside pipeline orchestration)
|
||
- [ ] **ARCH-06**: Per-environment YAML configuration in `config/{jetson,x86_dev,ci,sitl}.yaml`, loaded via `pydantic-settings` into a typed `RuntimeConfig` model passed to `build_pipeline`
|
||
- [ ] **ARCH-07**: All 195 stage1 tests + 8 SITL skipped continue to pass after refactor; no regression in accuracy benchmarks
|
||
|
||
### AC — Formal acceptance criteria document
|
||
|
||
- [ ] **AC-01**: `_docs/00_problem/acceptance_criteria.md` rewritten with formal AC-1.x…AC-NEW-x list adapted from `try02` and validated against this project's actual constraints
|
||
- [ ] **AC-02**: Each AC entry includes (a) numeric thresholds, (b) validation method, (c) at least one test ID linking to `tests/`
|
||
- [ ] **AC-03**: Position accuracy AC (50m@80%, 20m@50%, anchor age tracking, drift bounds) bound to `tests/integration/accuracy/` and `tests/e2e/`
|
||
- [ ] **AC-04**: Failure-mode AC (visual blackout, spoofing promotion, dead reckoning, ≥3 disconnected segments) bound to `tests/blackbox/failure_modes/`
|
||
- [ ] **AC-05**: Real-time performance AC (<400ms p95 e2e, <8GB RAM, ≥5Hz GPS_INPUT output) bound to a benchmark harness producing CI-tracked metrics
|
||
- [ ] **AC-06**: Traceability matrix `.planning/AC-TRACEABILITY.md` generated linking every AC ID → test ID(s) → implementing component(s)
|
||
|
||
### SAFE — Safety anchor state machine
|
||
|
||
- [ ] **SAFE-01**: `components/safety_state/SafetyAnchorStateMachine` owns authoritative `source_label ∈ {satellite_anchored, vo_extrapolated, dead_reckoned}` for every emitted `PositionEstimate`
|
||
- [ ] **SAFE-02**: Covariance growth is monotonic in non-anchored modes; resets only on accepted satellite anchor
|
||
- [ ] **SAFE-03**: `anchor_age_ms` recorded on every estimate; transitions to `vo_extrapolated` after configurable max-age threshold
|
||
- [ ] **SAFE-04**: State machine receives anchor decisions from `AnchorVerifier`, never raw VPR top-K — bad candidates cannot poison the state
|
||
- [ ] **SAFE-05**: Tile write eligibility flag exposed (`can_persist_tile: bool`) — false in `dead_reckoned` mode to prevent corrupt tile cache writes
|
||
- [ ] **SAFE-06**: Unit tests cover all 9 state transitions; property-based test asserts covariance never decreases without an accepted anchor
|
||
|
||
### VERIFY — Geometry-gated anchor verification
|
||
|
||
- [ ] **VERIFY-01**: `components/anchor_verifier/GeometryGatedAnchorVerifier` accepts/rejects satellite candidate matches based on configurable gates: min inliers, max mean reprojection error (px), max homography condition number
|
||
- [ ] **VERIFY-02**: Rejection reason string emitted on every reject (`"too_few_inliers"`, `"mre_above_threshold"`, `"degenerate_homography"`, `"freshness_expired"`)
|
||
- [ ] **VERIFY-03**: Freshness check integrates with sector classification (active-conflict <6mo, stable-rear <12mo) — expired tiles produce `freshness_expired` reject
|
||
- [ ] **VERIFY-04**: Verifier benchmark mode evaluates multiple matcher profiles on the same frame for offline comparison
|
||
- [ ] **VERIFY-05**: Unit tests cover each gate independently; integration test with real Azaion frame verifies end-to-end accept/reject
|
||
|
||
### FDR — Flight data recorder
|
||
|
||
- [ ] **FDR-01**: `components/flight_recorder/FlightRecorder` Protocol with `append_event(event)` and `export() -> FdrExportResult`
|
||
- [ ] **FDR-02**: `InMemoryFlightRecorder` impl with bounded segments and configurable segment+storage byte limits
|
||
- [ ] **FDR-03**: `DiskFlightRecorder` impl writing append-only JSONL segments under `data/fdr/{flight_id}/segment-NNNN.jsonl`
|
||
- [ ] **FDR-04**: Health states `ok / degraded (≥90% storage) / critical (limit reached)` exposed via `health` property
|
||
- [ ] **FDR-05**: Pipeline emits FDR events at every state transition, anchor decision, MAVLink send, and pipeline error
|
||
- [ ] **FDR-06**: AC-NEW-3 forensic-thumbnail rate (≤0.1Hz on tile-generation failures) wired through FDR with size budget enforcement
|
||
|
||
### VPR — Conditional + multi-scale visual place recognition
|
||
|
||
- [ ] **VPR-01**: VPR retrieval triggered conditionally — DINOv2 forward runs only on re-loc triggers (cold start, sharp turn AC-3.2, σ_xy > 50m, VO failure ≥2 frames, disconnected segment AC-3.3); steady-state uses geometric prior (IMU+VO predicted position) ranking by distance
|
||
- [ ] **VPR-02**: VPR chunks decoupled from storage tiles — chunks sized to ground footprint (600-800m at deployment altitude band) with 40-50% overlap; any frame footprint falls fully inside ≥1 chunk
|
||
- [ ] **VPR-03**: Multi-scale FAISS index — fine-scale (z=20-derived) + coarse-scale (z=17 or z=18) descriptor sets; coarse used in active-conflict sectors for change-robust retrieval
|
||
- [ ] **VPR-04**: Dynamic top-K — K=5 in stable sectors with σ_xy ≤ 20m, K=20 in active-conflict, K=50 on expanding-window fallback
|
||
- [ ] **VPR-05**: Chunking and indexing integrated into existing `chunk_manager.py`/`gpr.py` without breaking stage1 GPR API contracts
|
||
|
||
### MAVOUT — MAVLink output: source labels, dual-channel scaffold
|
||
|
||
- [ ] **MAVOUT-01**: Every emitted `GPS_INPUT` includes `source_label`, `anchor_age_ms`, `covariance_semimajor_m` propagated from `PositionEstimate` (mapped into `horiz_accuracy` and a custom STATUSTEXT for label/age)
|
||
- [ ] **MAVOUT-02**: `ODOMETRY` emitter scaffolded behind feature flag (`config.mavlink.odometry_enabled`); flag is false in stage 2; integration test asserts ODOMETRY is intentionally absent on the wire
|
||
- [ ] **MAVOUT-03**: Spoofing-promotion latency monitor — listens to `GPS_RAW_INT`/`EKF_STATUS_REPORT`/`SYS_STATUS`; promotes own estimate to FC primary within <3s when real-GPS health rolling avg < threshold; emits `STATUSTEXT` on every promotion/demotion
|
||
- [ ] **MAVOUT-04**: Visual blackout handling — pipeline switches to `dead_reckoned` within ≤1 processed frame OR ≤400ms when camera produces no usable signal; emits `VISUAL_BLACKOUT_IMU_ONLY` STATUSTEXT @ 1-2Hz
|
||
|
||
### FIXTURE — Real-flight integration fixture (Azaion 10.05.2026)
|
||
|
||
- [ ] **FIXTURE-01**: `tests/integration/azaion_flight/` integration test suite consuming `Data/Azaion/10.05.2026/` (tlog + cropped EO video + MAVLink CSV)
|
||
- [ ] **FIXTURE-02**: Preprocessing script `scripts/prep_azaion_fixture.py` producing — (a) HUD-stripped EO frames at 0.7 fps, (b) IMU/GPS/ATTITUDE CSV from tlog, (c) timestamp-aligned manifest
|
||
- [ ] **FIXTURE-03**: MAVLink replay test — feed tlog through `MAVLinkBridge` parser, assert all `GLOBAL_POSITION_INT`/`RAW_IMU`/`ATTITUDE` messages decoded without error
|
||
- [ ] **FIXTURE-04**: ESKF real-IMU smoke test — replay IMU samples through `ESKFCore.predict`, assert no NaN/Inf, bounded covariance growth
|
||
- [ ] **FIXTURE-05**: VO smoke test on cropped EO frames using ORB-SLAM3 backend — assert ≥30% frame registration success
|
||
- [ ] **FIXTURE-06**: GPS-denial simulation — mask `GPS_RAW_INT` for t∈[180s, 280s], replay rest of stream, assert pipeline switches to `vo_extrapolated` and back to `satellite_anchored` correctly
|
||
- [ ] **FIXTURE-07**: Azaion fixture documented in `_docs/00_problem/fixtures.md` with ground-truth references and known limitations (low altitude, multirotor dynamics, HUD overlay)
|
||
|
||
### TEST — Test taxonomy & infrastructure
|
||
|
||
- [ ] **TEST-01**: `tests/` reorganized to `tests/{unit,integration,blackbox,sitl,e2e}/`; existing tests redistributed by category
|
||
- [ ] **TEST-02**: `pyproject.toml` test markers updated — `pytest -m unit` / `-m integration` / etc.; CI runs unit+integration on every push, blackbox on PR, sitl+e2e nightly
|
||
- [ ] **TEST-03**: AC traceability auto-generated — pytest plugin tags each test with `@pytest.mark.ac("AC-1.1")`; `scripts/gen_ac_traceability.py` produces the matrix in `.planning/AC-TRACEABILITY.md`
|
||
|
||
### OBS — Observability & tooling
|
||
|
||
- [ ] **OBS-01**: Structured JSON logging via `structlog` with `correlation_id` (frame_id) propagated through pipeline; Pydantic logging schemas at boundaries
|
||
- [ ] **OBS-02**: CLI tool `gps_denied` (typer-based) with subcommands — `replay --tlog ... --video ...`, `benchmark --scenario ...`, `bench-ac AC-1.1` for AC-driven benchmark runs
|
||
- [ ] **OBS-03**: Per-environment Docker images split — `Dockerfile.x86_dev` for CI/dev, `Dockerfile.jetson` (multi-stage with TRT engine prebuild step) for hardware
|
||
|
||
---
|
||
|
||
## Stage 3 candidates (parking lot)
|
||
|
||
- Mid-flight tile generation + write-back to Azaion Satellite Service (AC-8.4)
|
||
- On-device hardware validation on Jetson Orin Nano Super
|
||
- Dual-channel MAVLink ODOMETRY enabled (depends on ArduPilot fixes for EKF3 source switching)
|
||
- AC-NEW-1 cold-boot time-to-first-fix bench (<30s, 50× cold reboot)
|
||
- BASALT VIO backend evaluation (only if cuVSLAM hits a blocker)
|
||
|
||
## Out of Scope (Stage 2)
|
||
|
||
- Migration to PostgreSQL (SQLite remains embedded default; Postgres optional for ground station only)
|
||
- Folder-per-component layout for `core/` math files (ESKF/factor graph stay concentrated)
|
||
- Real microservices with separate processes / IPC
|
||
- Pydantic on per-frame hot path (dataclasses replace it)
|
||
- Mobile/web ground station UI
|
||
- Multi-UAV coordination
|
||
|
||
## Traceability
|
||
|
||
Populated by roadmapper on 2026-05-10. Test IDs will be filled in by `/gsd:plan-phase` and `/gsd:implement` as each phase produces concrete tests.
|
||
|
||
| REQ | Phase | Tests |
|
||
|-----|-------|-------|
|
||
| ARCH-01 | Phase 1 | _pending plan-phase_ |
|
||
| ARCH-02 | Phase 1 | _pending plan-phase_ |
|
||
| ARCH-03 | Phase 1 | _pending plan-phase_ |
|
||
| ARCH-04 | Phase 1 | _pending plan-phase_ |
|
||
| ARCH-05 | Phase 1 | _pending plan-phase_ |
|
||
| ARCH-06 | Phase 1 | _pending plan-phase_ |
|
||
| ARCH-07 | Phase 1 | _pending plan-phase_ |
|
||
| AC-01 | Phase 2 | _pending plan-phase_ |
|
||
| AC-02 | Phase 2 | _pending plan-phase_ |
|
||
| AC-03 | Phase 2 | _pending plan-phase_ |
|
||
| AC-04 | Phase 2 | _pending plan-phase_ |
|
||
| AC-05 | Phase 2 | _pending plan-phase_ |
|
||
| AC-06 | Phase 2 | _pending plan-phase_ |
|
||
| TEST-01 | Phase 2 | _pending plan-phase_ |
|
||
| TEST-02 | Phase 2 | _pending plan-phase_ |
|
||
| TEST-03 | Phase 2 | _pending plan-phase_ |
|
||
| OBS-01 | Phase 2 | _pending plan-phase_ |
|
||
| SAFE-01 | Phase 3 | _pending plan-phase_ |
|
||
| SAFE-02 | Phase 3 | _pending plan-phase_ |
|
||
| SAFE-03 | Phase 3 | _pending plan-phase_ |
|
||
| SAFE-04 | Phase 3 | _pending plan-phase_ |
|
||
| SAFE-05 | Phase 3 | _pending plan-phase_ |
|
||
| SAFE-06 | Phase 3 | _pending plan-phase_ |
|
||
| VERIFY-01 | Phase 3 | _pending plan-phase_ |
|
||
| VERIFY-02 | Phase 3 | _pending plan-phase_ |
|
||
| VERIFY-03 | Phase 3 | _pending plan-phase_ |
|
||
| VERIFY-04 | Phase 3 | _pending plan-phase_ |
|
||
| VERIFY-05 | Phase 3 | _pending plan-phase_ |
|
||
| VPR-01 | Phase 4 | _pending plan-phase_ |
|
||
| VPR-02 | Phase 4 | _pending plan-phase_ |
|
||
| VPR-03 | Phase 4 | _pending plan-phase_ |
|
||
| VPR-04 | Phase 4 | _pending plan-phase_ |
|
||
| VPR-05 | Phase 4 | _pending plan-phase_ |
|
||
| FDR-01 | Phase 4 | _pending plan-phase_ |
|
||
| FDR-02 | Phase 4 | _pending plan-phase_ |
|
||
| FDR-03 | Phase 4 | _pending plan-phase_ |
|
||
| FDR-04 | Phase 4 | _pending plan-phase_ |
|
||
| FDR-05 | Phase 4 | _pending plan-phase_ |
|
||
| FDR-06 | Phase 4 | _pending plan-phase_ |
|
||
| MAVOUT-01 | Phase 5 | _pending plan-phase_ |
|
||
| MAVOUT-02 | Phase 5 | _pending plan-phase_ |
|
||
| MAVOUT-03 | Phase 5 | _pending plan-phase_ |
|
||
| MAVOUT-04 | Phase 5 | _pending plan-phase_ |
|
||
| FIXTURE-01 | Phase 6 | _pending plan-phase_ |
|
||
| FIXTURE-02 | Phase 6 | _pending plan-phase_ |
|
||
| FIXTURE-03 | Phase 6 | _pending plan-phase_ |
|
||
| FIXTURE-04 | Phase 6 | _pending plan-phase_ |
|
||
| FIXTURE-05 | Phase 6 | _pending plan-phase_ |
|
||
| FIXTURE-06 | Phase 6 | _pending plan-phase_ |
|
||
| FIXTURE-07 | Phase 6 | _pending plan-phase_ |
|
||
| OBS-02 | Phase 6 | _pending plan-phase_ |
|
||
| OBS-03 | Phase 6 | _pending plan-phase_ |
|
||
|
||
**Coverage:** 52/52 requirements mapped. No orphans, no duplicates.
|