mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-21 07:31:13 +00:00
docs: add Phase 1 ADRs and update PROJECT.md with completed decisions
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>
This commit is contained in:
@@ -17,3 +17,7 @@ build/
|
||||
# Env
|
||||
.env
|
||||
*.env
|
||||
|
||||
# Local planning/docs (not for the repo)
|
||||
docs-Lokal/
|
||||
.planning/
|
||||
|
||||
+138
-61
@@ -1,98 +1,175 @@
|
||||
# GPS-Denied Onboard Navigation System
|
||||
# GPS-Denied Onboard Navigation System — Stage 2
|
||||
|
||||
## What This Is
|
||||
|
||||
Real-time GPS-independent position estimation system for a fixed-wing UAV operating in GPS-denied/spoofed environments (flat terrain, Ukraine). Runs onboard a Jetson Orin Nano Super (8GB shared, 67 TOPS). Fuses visual odometry (cuVSLAM), satellite image matching (TensorRT FP16), and IMU via an ESKF to output MAVLink GPS_INPUT to an ArduPilot flight controller at 5-10Hz, while also streaming position and confidence over SSE to a ground station.
|
||||
|
||||
## Stage 2 Iteration
|
||||
|
||||
**Stage 2 is a self-contained iteration of the project.** It is NOT a continuation of Stage 1's phase numbering — it has its own roadmap (Phases 1–6), its own requirements list, and its own success criteria. Each stage is conceptually a new pass at the system: same problem, same end goal, fresh decisions about HOW.
|
||||
|
||||
**Stage 2 starting capital:**
|
||||
|
||||
- **From stage1 (own work):** The full v1 pipeline as MVP — ESKF (15-state), cuVSLAM/ORB VO, satellite matching + GPR, MAVLink GPS_INPUT, pipeline orchestration, SITL harness, accuracy benchmarks, 195 passing tests. Treated as **MVP, not production** — refactoring is allowed and expected.
|
||||
- **From try02 (parallel team):** Concept-level ideas only — Safety Anchor State Machine, Geometry-Gated Anchor Verifier, Flight Data Recorder, Conditional Multi-Scale VPR, dual-channel MAVLink design, formal Acceptance Criteria document with numeric thresholds, structured test taxonomy.
|
||||
- **From real-flight data:** Azaion 10.05.2026 dataset (tlog + 6min video + 9.5Hz GPS ground truth) as integration fixture.
|
||||
|
||||
**Stage 2 is free to:**
|
||||
|
||||
- Reorganize the codebase (hexagonal layout) — no production lock-in
|
||||
- Replace, swap, or rebuild components — only AC-driven test outcomes are sacred
|
||||
- Change the architecture wholesale if a better path emerges mid-stage
|
||||
- Diverge from try02's choices where the evidence supports it (e.g., reject BASALT in favor of cuVSLAM, reject Pydantic on hot path)
|
||||
|
||||
**Stage 2 archive:** `_planning/archive/v1.0/` preserves stage1's PROJECT.md, REQUIREMENTS.md, ROADMAP.md, and Phase 1 artifacts as historical record.
|
||||
|
||||
## 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.
|
||||
|
||||
## Requirements
|
||||
## Stage 2 Goal
|
||||
|
||||
### Validated
|
||||
Refactor the inherited stage1 MVP into a hexagonal/ports-and-adapters architecture with explicit DI composition root, integrate selected concept-level ideas from `try02`, formalize acceptance criteria with testable numerics, and add a real-flight integration fixture (Azaion 10.05.2026).
|
||||
|
||||
- ✓ FastAPI service scaffold with SSE streaming — existing
|
||||
- ✓ FlightProcessor orchestrator with NORMAL/LOST/RECOVERY state machine — existing
|
||||
- ✓ CoordinateTransformer (GPS↔ENU, pixel→camera→body→NED→WGS84) — existing
|
||||
- ✓ SatelliteDataManager (tile fetch, diskcache, GeoHash lookup) — existing
|
||||
- ✓ ImageInputPipeline (frame queue, batch validation, storage) — existing
|
||||
- ✓ SQLAlchemy async DB layer (flights, waypoints, frames, results) — existing
|
||||
- ✓ Pydantic schema contracts for all inter-component data — existing
|
||||
- ✓ ABC interfaces for all core components (VO, GPR, metric, graph) — existing
|
||||
## Stage 2 Target Features
|
||||
|
||||
### Active
|
||||
**Architecture:**
|
||||
- Hexagonal layout — `src/gps_denied/components/{vio, satellite_matcher, gpr, anchor_verifier, safety_state, flight_recorder, mavlink_io, coordinate_transforms}/` with `protocol.py` + concrete impls per component
|
||||
- Hot-path types as `@dataclass(slots=True, frozen=True)` for `FrameState`, `IMUSample`, `PositionEstimate`; Pydantic kept only at REST/config/DB boundaries
|
||||
- Composition root `pipeline/composition.py` with explicit DI for env-specific wiring (jetson/x86_dev/ci/sitl)
|
||||
- Per-environment config — `config/{jetson,x86_dev,ci,sitl}.yaml` driven by pydantic-settings
|
||||
- `core/` retained for concentrated math (ESKF, factor graph, RANSAC) — single-file pure functions
|
||||
|
||||
- [ ] ESKF implementation (15-state error-state Kalman filter: IMU prediction + VO update + satellite update + covariance propagation)
|
||||
- [ ] MAVLink GPS_INPUT output (pymavlink, UART/UDP, 5-10Hz loop, ESKF state→GPS_INPUT field mapping)
|
||||
- [ ] Real VO implementation (cuVSLAM on Jetson / OpenCV ORB stub on dev for CI)
|
||||
- [ ] Real TensorRT inference (SuperPoint+LightGlue for VO, XFeat for satellite matching — FP16 on Jetson)
|
||||
- [ ] Satellite feature matching pipeline (tile selection by ESKF uncertainty, RANSAC homography, WGS84 extraction)
|
||||
- [ ] GlobalPlaceRecognition implementation (AnyLoc/DINOv2 candidate retrieval, FAISS index, tile scoring)
|
||||
- [ ] FactorGraph implementation (pose graph with VO edges + satellite anchor nodes, optimization loop)
|
||||
- [ ] FailureRecoveryCoordinator (tracking loss detection, re-init protocol, operator re-localization hint)
|
||||
- [ ] End-to-end pipeline wiring (processor.process_frame → VO → ESKF → satellite → GPS_INPUT)
|
||||
- [ ] Docker SITL test harness (ArduPilot SITL, camera replay, tile server mock, CI integration)
|
||||
- [ ] Confidence scoring and GPS_INPUT fix_type mapping (HIGH/MEDIUM/LOW → fix_type 3/2/0)
|
||||
- [ ] Object GPS localization endpoint (POST /objects/locate with gimbal angle projection)
|
||||
**try02 concept integration:**
|
||||
- Acceptance Criteria document — formal AC-1.x…AC-NEW-x with numeric thresholds, validation methods, test linkage
|
||||
- Safety Anchor State Machine — separate layer over ESKF owning `source_label` (`satellite_anchored`/`vo_extrapolated`/`dead_reckoned`), monotonic covariance growth, anchor age, tile write eligibility
|
||||
- Geometry-gated Anchor Verifier — formal accept/reject gates (inliers, MRE, reprojection error) before anchor enters ESKF
|
||||
- Flight Data Recorder (FDR) — append-only event log with bounded segment storage and health states
|
||||
- Conditional VPR invocation — DINOv2 forward only on re-loc triggers; steady-state geometric prior
|
||||
- Multi-scale VPR chunks — 600-800m ground-footprint chunks at 40-50% overlap, decoupled from storage tiles, fine (z=20) + coarse (z=17) scales
|
||||
- Source label + anchor_age_ms emitted in every GPS_INPUT estimate
|
||||
- Visual blackout handling — switch to `dead_reckoned` ≤400ms, monotonic covariance growth, `VISUAL_BLACKOUT_IMU_ONLY` STATUSTEXT @ 1-2Hz
|
||||
- Spoofing-promotion latency monitor — promote own estimate to FC primary within <3s of detected real-GPS health drop
|
||||
- Test taxonomy — `tests/{unit,integration,blackbox,sitl,e2e}/`
|
||||
- Dual-channel MAVLink design — `GPS_INPUT` primary (v1 only), `ODOMETRY` auxiliary scaffolded behind feature flag for v1.1
|
||||
- Structured JSON logging with `correlation_id` (frame_id) per-frame
|
||||
- CLI tool `gps_denied replay --tlog ... --video ...`
|
||||
- Real-flight integration fixture — Azaion 10.05.2026 as `tests/integration/azaion_flight/`
|
||||
|
||||
### Out of Scope
|
||||
## Stage 2 Explicit Non-Goals
|
||||
|
||||
- TensorRT engine building tooling — engines are pre-built offline, system only loads them
|
||||
- Google Maps tile download tooling — tiles pre-cached before flight, not streamed live
|
||||
- Full ArduPilot integration testing on hardware — Jetson hardware validation is post-v1
|
||||
- Mobile/web ground station UI — SSE stream is consumed by external systems
|
||||
- BASALT VIO backend — cuVSLAM remains primary (aarch64) with ORB-SLAM3 as CI baseline
|
||||
- Pydantic on the per-frame hot path — dataclasses replace it
|
||||
- Mandatory PostgreSQL — SQLite remains the embedded default
|
||||
- Microservice processes / IPC — single-process architecture preserved
|
||||
- Folder-per-component split for `core/` math files — ESKF/factor graph stay concentrated
|
||||
- Mid-flight tile generation + write-back to Suite (AC-8.4) — deferred to Stage 3
|
||||
- Production hardware validation on Jetson — deferred to Stage 3
|
||||
|
||||
## Future Stages (parking lot)
|
||||
|
||||
- **Stage 3 candidates:** Jetson hardware validation, mid-flight tile generation + Suite write-back, ODOMETRY channel enabled, AC-NEW-1 cold-boot benchmark, BASALT evaluation if cuVSLAM blockers emerge
|
||||
|
||||
## Out of Scope (across all stages, unless re-opened)
|
||||
|
||||
- TensorRT engine building tooling — engines are pre-built offline
|
||||
- Google Maps tile download tooling — tiles pre-cached before flight
|
||||
- Mobile/web ground station UI — SSE consumed by external systems
|
||||
- Multi-UAV coordination — single UAV instance only
|
||||
|
||||
## Context
|
||||
|
||||
**Hardware target:** Jetson Orin Nano Super (8GB LPDDR5 shared, JetPack 6.2.2, CUDA 12.6, TRT 10.3.0). All development happens on x86 Linux; cuVSLAM and TRT are Jetson-only — dev machine uses OpenCV ORB stub and MockInferenceEngine.
|
||||
**Hardware target:** Jetson Orin Nano Super (8GB LPDDR5 shared, JetPack 6.2.2, CUDA 12.6, TRT 10.3.0). Development on x86 Linux; cuVSLAM and TRT are Jetson-only — dev/CI uses OpenCV ORB stub and MockInferenceEngine.
|
||||
|
||||
**Camera:** ADTI 20L V1 (5456×3632, APS-C, 16mm lens, nadir fixed, 0.7fps). AI detection camera: Viewpro A40 Pro (separate).
|
||||
**Camera (target):** ADTI 20L V1 (5456×3632, APS-C, 16mm lens, nadir fixed, 0.7fps). AI detection camera: Viewpro A40 Pro (separate).
|
||||
|
||||
**Flight controller:** ArduPilot via MAVLink UART. System sends GPS_INPUT; receives IMU (200Hz) and GLOBAL_POSITION_INT (1Hz) from FC.
|
||||
**Camera (Azaion fixture):** Multirotor gimbal EO+IR split-screen with HUD overlay, 1280×720 @ 30fps. Used for integration testing only — does not represent target deployment camera.
|
||||
|
||||
**Key latency budget:** <400ms end-to-end per frame (camera @ 0.7fps = 1430ms window).
|
||||
**Flight controller:** ArduPilot via MAVLink UART. System sends GPS_INPUT; receives IMU (200Hz target / 9.7Hz in Azaion fixture) and GLOBAL_POSITION_INT (1Hz) from FC.
|
||||
|
||||
**Existing scaffold:** ~2800 lines of Python code exist as a well-structured scaffold. All modules are present with ABC interfaces and schemas, but critical algorithmic kernels (ESKF, real VO, TRT inference, MAVLink) are missing or mocked.
|
||||
**Key latency budget:** <400ms end-to-end per frame.
|
||||
|
||||
**Test data:** 60 UAV frames (AD000001-AD000060.jpg), coordinates.csv with ground-truth GPS, expected_results/position_accuracy.csv. 43 documented test scenarios across 7 categories.
|
||||
**Stage 1 inheritance:** ~7,800 lines of working Python code with 195 passing tests. All algorithmic kernels (ESKF, VO, GPR, MAVLink, factor graph) implemented. Stage 2 starts from this codebase on branch `stage2` (HEAD = stage1).
|
||||
|
||||
**Reference branch:** `try02` is checked out as a worktree at `../gps-denied-onboard-try02/` for concept harvesting. We do NOT merge from try02 — we read it for ideas and re-implement what fits.
|
||||
|
||||
## Constraints
|
||||
|
||||
- **Performance**: <400ms/frame end-to-end, <8GB RAM+VRAM — non-negotiable for real-time flight
|
||||
- **Hardware**: cuVSLAM v15.0.0 (aarch64-only wheel) — stub interface required for CI
|
||||
- **Platform**: JetPack 6.2.2, Python 3.10+, TensorRT 10.3.0, CUDA 12.6
|
||||
- **Navigation accuracy**: 80% frames ≤50m, 60% frames ≤20m, max drift 100m between satellite corrections
|
||||
- **Resilience**: Handle sharp turns (disconnected VO segments), 3+ consecutive satellite match failures
|
||||
- **Performance:** <400ms/frame end-to-end p95, <8GB RAM+VRAM — non-negotiable
|
||||
- **Hardware:** cuVSLAM v15.0.0 (aarch64-only wheel) — Protocol with stub on x86
|
||||
- **Platform:** JetPack 6.2.2, Python 3.10+, TensorRT 10.3.0, CUDA 12.6
|
||||
- **Navigation accuracy:** 80% frames ≤50m, 60% frames ≤20m, max drift 100m between satellite corrections
|
||||
- **Resilience:** Handle sharp turns (disconnected VO segments), 3+ consecutive satellite match failures, visual blackout, GPS spoofing promotion <3s
|
||||
- **Regression floor:** All 195 stage1 passing tests must continue to pass after refactor
|
||||
|
||||
## Key Decisions
|
||||
## Stage 2 Key Decisions
|
||||
|
||||
| Decision | Rationale | Outcome |
|
||||
|----------|-----------|---------|
|
||||
| ESKF over EKF/UKF | 15-state error-state formulation avoids quaternion singularities, standard for INS | — Pending |
|
||||
| XFeat over LiteSAM for satellite matching | LiteSAM may exceed 400ms budget on Jetson; XFeat is faster | — Pending (benchmark required) |
|
||||
| OpenCV ORB stub for dev/CI | cuVSLAM is aarch64-only; CI must run on x86 | — Pending |
|
||||
| AnyLoc/DINOv2 for GPR | Validated on UAV-VisLoc benchmark (17.86m RMSE) | — Pending |
|
||||
| diskcache + GeoHash for tiles | O(1) tile lookup, no DB overhead, LRU eviction | ✓ Good |
|
||||
| AsyncSQLAlchemy + aiosqlite | Non-blocking DB for async FastAPI service | ✓ Good |
|
||||
| Hexagonal layout with `components/` folders | Clear ownership per swappable backend, native bridges colocate with adapter | ✓ Phase 1 |
|
||||
| `@dataclass(slots=True, frozen=True)` on hot path, Pydantic at boundaries only | Avoid try02's per-frame Pydantic latency cost; validate where it catches bugs (REST input, config) | ✓ Phase 1 (hot_types/ scaffolded; full migration Phase 2) |
|
||||
| Explicit DI composition root | One file wires environment-specific implementations; tests pass mock dependencies | ✓ Phase 1 (`pipeline/composition.py:build_pipeline`) |
|
||||
| Adopt try02 concept ideas, reject try02 layout details | Take Safety Anchor / Anchor Verifier / FDR / Conditional VPR; reject Pydantic-on-hot-path, BASALT | ✓ Adopted — Phases 3–5 |
|
||||
| Take try02 acceptance criteria with numeric thresholds | Their AC-1.x…AC-NEW-x is more rigorous than stage1's drafts; bind every AC to ≥1 test | ✓ Adopted — Phase 2 |
|
||||
| Test taxonomy `unit/integration/blackbox/sitl/e2e` | Clarifies CI-on-push vs PR vs nightly vs hardware-only test runs | ✓ Phase 2 |
|
||||
| Stage as iteration, not phase continuation | Each stage = own roadmap, own phase numbering, own success criteria | ✓ Adopted |
|
||||
|
||||
## Phase 1 Outcome (2026-05-11, completed)
|
||||
|
||||
**ARCH-01..07 all satisfied.** 216 tests pass (baseline 195+21 new = 216), 0 failures, accuracy benchmarks unchanged.
|
||||
|
||||
### What was built
|
||||
|
||||
**Components scaffold** (`src/gps_denied/components/`):
|
||||
- `vio/` — `protocol.py` + `orbslam_backend.py` + `cuvslam_backend.py` + `factory.py`; `core/vo.py` is a shim
|
||||
- `gpr/` — `protocol.py` + `faiss_gpr.py` (inline numpy fallback preserved); `core/gpr.py` is a shim
|
||||
- `satellite_matcher/` — `protocol.py` + `local_tile_loader.py` + `metric_refinement.py`; `core/satellite.py`, `core/metric.py` are shims
|
||||
- `mavlink_io/` — `protocol.py` + `pymavlink_bridge.py` + `mock_mavlink.py`; `core/mavlink.py` is a shim (re-exports private helpers `_confidence_to_fix_type`, `_eskf_to_gps_input`, `_unix_to_gps_time`)
|
||||
- `anchor_verifier/`, `safety_state/`, `flight_recorder/`, `coordinate_transforms/` — Protocol stubs only (Phases 3–5)
|
||||
|
||||
**Hot-path types** (`src/gps_denied/hot_types/`): `FrameState`, `IMUSample`, `PositionEstimate`, `VOEstimate`, `SatelliteAnchor` as `@dataclass(slots=True, frozen=True)`. Schemas shimmed to re-export. `Pose` stays Pydantic (mutation sites in `factor_graph.py` lines 182–297); `GPSPoint` stays Pydantic. Full hot-path migration deferred to Phase 2.
|
||||
|
||||
**Pipeline package** (`src/gps_denied/pipeline/`):
|
||||
- `orchestrator.py` — `FlightProcessor` (moved from `core/processor.py`)
|
||||
- `image_input.py`, `result_manager.py`, `sse_streamer.py` (moved from `core/`)
|
||||
- `composition.py` — `build_pipeline(env: Literal["jetson","x86_dev","ci","sitl"]) -> FlightProcessor`
|
||||
|
||||
**Composition root**: wires 10 components; lazy imports inside function body to avoid circular imports; Jetson env → `prefer_cuvslam=True`, `prefer_mono_depth=True`; other envs → mocks.
|
||||
|
||||
**Config**: `AppSettings.env` Literal field + `RuntimeConfig = AppSettings` alias. `pydantic-settings YamlConfigSettingsSource` loads `config/{env}.yaml`. `pyyaml>=6.0` declared.
|
||||
|
||||
**ABC→Protocol sweep**: 6 interfaces converted to `typing.Protocol` with `@runtime_checkable`:
|
||||
`IFactorGraphOptimizer`, `IRouteChunkManager`, `IFailureRecoveryCoordinator`, `IModelManager`, `IImageMatcher`, + all 8 component Protocols from `components/*/protocol.py`.
|
||||
|
||||
**`core/` retained** for concentrated math: `eskf.py`, `factor_graph.py`, `coordinates.py`, `chunk_manager.py`, `recovery.py`, `rotation.py`, `models.py`.
|
||||
|
||||
**Shim policy**: every moved file leaves a re-export shim at its old path. Tests import from old paths — shims keep them green. Shim removal is Phase 2 work.
|
||||
|
||||
### Deferred to Phase 2
|
||||
|
||||
- Full hot-path type migration (`Pose`, `GPSPoint`, remaining Pydantic models on frame path)
|
||||
- Test reorganization to `tests/{unit,integration,blackbox,sitl,e2e}/`
|
||||
- Shim removal from `core/`
|
||||
- YAML config enrichment with env-specific overrides (MAVLink connection strings, tile dirs)
|
||||
|
||||
## Stage 1 Decisions Inherited (validated, kept)
|
||||
|
||||
| Decision | Outcome |
|
||||
|----------|---------|
|
||||
| ESKF over EKF/UKF | ✓ Stage 1 |
|
||||
| XFeat over LiteSAM for satellite matching | ✓ Stage 1 |
|
||||
| OpenCV ORB stub for dev/CI; cuVSLAM on Jetson | ✓ Stage 1 |
|
||||
| AnyLoc/DINOv2 for GPR | ✓ Stage 1 |
|
||||
| diskcache + GeoHash for tiles | ✓ Stage 1 |
|
||||
| AsyncSQLAlchemy + aiosqlite | ✓ Stage 1 |
|
||||
|
||||
## Evolution
|
||||
|
||||
This document evolves at phase transitions and milestone boundaries.
|
||||
Each stage is its own iteration with its own PROJECT.md, REQUIREMENTS.md, ROADMAP.md. At stage completion:
|
||||
|
||||
**After each phase transition** (via `/gsd:transition`):
|
||||
1. Requirements invalidated? → Move to Out of Scope with reason
|
||||
2. Requirements validated? → Move to Validated with phase reference
|
||||
3. New requirements emerged? → Add to Active
|
||||
4. Decisions to log? → Add to Key Decisions
|
||||
5. "What This Is" still accurate? → Update if drifted
|
||||
|
||||
**After each milestone** (via `/gsd:complete-milestone`):
|
||||
1. Full review of all sections
|
||||
2. Core Value check — still the right priority?
|
||||
3. Audit Out of Scope — reasons still valid?
|
||||
4. Update Context with current state
|
||||
1. Snapshot current PROJECT.md / REQUIREMENTS.md / ROADMAP.md / phases/ → `.planning/archive/v[X.Y]/`
|
||||
2. Open new stage with fresh roadmap (Phase 1 of the new stage)
|
||||
3. Carry forward only validated decisions and unresolved Future-stages items
|
||||
|
||||
---
|
||||
*Last updated: 2026-04-01 after initialization*
|
||||
*Stage 2 opened: 2026-05-10*
|
||||
|
||||
+142
-123
@@ -1,157 +1,176 @@
|
||||
# Requirements: GPS-Denied Onboard Navigation System
|
||||
# Requirements: GPS-Denied Onboard Navigation System — Stage 2
|
||||
|
||||
**Defined:** 2026-04-01
|
||||
**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.
|
||||
|
||||
## v1 Requirements
|
||||
---
|
||||
|
||||
Requirements for this milestone. The scaffold (~2800 lines) exists; all algorithmic kernels are missing or mocked. Every requirement below maps to one phase of implementation work.
|
||||
## Stage 2 Requirements
|
||||
|
||||
### ESKF — Error-State Kalman Filter
|
||||
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.
|
||||
|
||||
- [ ] **ESKF-01**: 15-state ESKF implemented (δp, δv, δθ, δb_a, δb_g) with IMU prediction step (F, Q matrices, bias propagation)
|
||||
- [ ] **ESKF-02**: VO measurement update implemented (relative pose ΔR/Δt from cuVSLAM, H_vo, R_vo covariance, Kalman gain)
|
||||
- [ ] **ESKF-03**: Satellite measurement update implemented (absolute WGS84 position from matching, H_sat, R_sat from RANSAC inlier ratio)
|
||||
- [ ] **ESKF-04**: ESKF state initializes from GLOBAL_POSITION_INT at startup and on mid-flight reboot with high-uncertainty covariance
|
||||
- [ ] **ESKF-05**: Confidence tier computation outputs HIGH/MEDIUM/LOW based on covariance magnitude and last satellite correction age
|
||||
- [ ] **ESKF-06**: Coordinate transform chain implemented: pixel→camera ray (K matrix), camera→body (T_cam_body), body→NED (ESKF quaternion), NED→WGS84 — replacing all FAKE Math stubs
|
||||
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).
|
||||
|
||||
### VO — Visual Odometry
|
||||
### ARCH — Hexagonal architecture & composition
|
||||
|
||||
- [ ] **VO-01**: cuVSLAM wrapper implemented for Jetson target (Inertial mode, camera + IMU inputs, relative pose output with metric scale)
|
||||
- [ ] **VO-02**: OpenCV ORB stub conforms to the same `ISequentialVisualOdometry` interface as cuVSLAM wrapper, used on dev/CI (x86)
|
||||
- [ ] **VO-03**: TensorRT FP16 inference engine loader implemented for SuperPoint and LightGlue on Jetson; MockInferenceEngine used on dev/CI
|
||||
- [ ] **VO-04**: Scale ambiguity resolved — `scale_ambiguous` is False when ESKF provides metric scale reference; VO relative pose is metric in NED
|
||||
- [ ] **VO-05**: ImageInputPipeline batch validation minimum lowered to 1 image (not 10); `get_image_by_sequence` uses exact filename matching
|
||||
- [ ] **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
|
||||
|
||||
### SAT — Satellite Matching
|
||||
### AC — Formal acceptance criteria document
|
||||
|
||||
- [ ] **SAT-01**: XFeat TRT FP16 inference engine implemented for satellite feature matching on Jetson; MockInferenceEngine used on dev/CI
|
||||
- [ ] **SAT-02**: Satellite tile selection uses ESKF position ± 3σ_horizontal to define search area; tiles assembled into mosaic at matcher resolution
|
||||
- [ ] **SAT-03**: GSD normalization implemented — camera frame downsampled to match satellite GSD (0.3–0.6 m/px) before matching
|
||||
- [ ] **SAT-04**: RANSAC homography estimation produces WGS84 absolute position with confidence score from inlier ratio
|
||||
- [ ] **SAT-05**: SatelliteDataManager reads from pre-loaded GeoHash-indexed local directory (read-only, no live HTTP fetches during flight)
|
||||
- [ ] **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)
|
||||
|
||||
### GPR — Global Place Recognition
|
||||
### SAFE — Safety anchor state machine
|
||||
|
||||
- [ ] **GPR-01**: Real Faiss index loaded at runtime from file path (not synthetic random vectors); index built from DINOv2 descriptors of actual satellite tiles during offline pre-processing
|
||||
- [ ] **GPR-02**: DINOv2/AnyLoc TRT FP16 inference engine implemented on Jetson; MockInferenceEngine used on dev/CI
|
||||
- [ ] **GPR-03**: GPR candidate retrieval returns real tile matches ranked by descriptor similarity, used for re-localization after tracking loss
|
||||
- [ ] **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
|
||||
|
||||
### MAV — MAVLink Output
|
||||
### VERIFY — Geometry-gated anchor verification
|
||||
|
||||
- [ ] **MAV-01**: pymavlink added to dependencies; MAVLink output component implemented sending GPS_INPUT over UART at 5-10Hz
|
||||
- [ ] **MAV-02**: ESKF state and covariance mapped to GPS_INPUT fields (lat/lon/alt from position, velocity from v-state, accuracy from covariance diagonal, fix_type from confidence tier, synthesized hdop/vdop, GPS time from system clock)
|
||||
- [ ] **MAV-03**: IMU input path implemented — MAVLink listener receives ATTITUDE/RAW_IMU from flight controller at 5-10Hz and feeds ESKF prediction step
|
||||
- [ ] **MAV-04**: Consecutive-failure counter detects 3 frames without any position estimate; sends MAVLink NAMED_VALUE_FLOAT re-localization request to ground station operator
|
||||
- [ ] **MAV-05**: Telemetry output at 1Hz sends confidence score and drift estimate to ground station via MAVLink NAMED_VALUE_FLOAT
|
||||
- [ ] **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
|
||||
|
||||
### PIPE — Pipeline Wiring
|
||||
### FDR — Flight data recorder
|
||||
|
||||
- [ ] **PIPE-01**: FlightProcessor.process_frame wired end-to-end: image in → cuVSLAM VO → ESKF VO update → (keyframe) satellite match → ESKF satellite update → GPS_INPUT output
|
||||
- [ ] **PIPE-02**: SatelliteDataManager and CoordinateTransformer instantiated and wired into processor pipeline (currently standalone, not connected)
|
||||
- [ ] **PIPE-03**: FactorGraph replaced or backed by real GTSAM ISAM2 incremental smoothing with BetweenFactorPose3 (VO) and GPSFactor (satellite anchors)
|
||||
- [ ] **PIPE-04**: FailureRecoveryCoordinator connected to ESKF — on tracking loss, ESKF continues IMU-only prediction with growing uncertainty; on recovery success, ESKF is reset with satellite position
|
||||
- [ ] **PIPE-05**: ImageRotationManager integrated into process_frame — heading sweep on first frame; `calculate_precise_angle` implemented with real VO-based refinement
|
||||
- [ ] **PIPE-06**: Object GPS localization endpoint (POST /objects/locate) uses full pixel→ray→ground→WGS84 chain with ESKF attitude; hardcoded stub removed
|
||||
- [ ] **PIPE-07**: Confidence scoring and fix_type mapping wired end-to-end: ESKF confidence tier → GPS_INPUT fix_type (3/2/0), accuracy fields
|
||||
- [ ] **PIPE-08**: ImageRotationManager constructor signature fixed (accepts optional ModelManager); startup TypeError resolved
|
||||
- [ ] **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
|
||||
|
||||
### TEST — Test Harness and Validation
|
||||
### VPR — Conditional + multi-scale visual place recognition
|
||||
|
||||
- [ ] **TEST-01**: Docker SITL test harness implemented: ArduPilot SITL container, camera-replay service, satellite tile server mock, MAVLink capture
|
||||
- [ ] **TEST-02**: CI pipeline runs on x86 using OpenCV ORB stub and MockInferenceEngine; all unit tests pass
|
||||
- [ ] **TEST-03**: Accuracy validation test runs against 60-frame dataset (AD000001–AD000060.jpg) with coordinates.csv ground truth; reports 80%/50m and 60%/20m hit rates
|
||||
- [ ] **TEST-04**: Performance benchmark test validates <400ms end-to-end per frame on Jetson (or reports estimated latency breakdown on dev)
|
||||
- [ ] **TEST-05**: All 21 blackbox test scenarios (FT-P-01 to FT-P-14, FT-N-01 to FT-N-07) implemented as runnable pytest tests using SITL harness
|
||||
- [ ] **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
|
||||
|
||||
## v2 Requirements
|
||||
### MAVOUT — MAVLink output: source labels, dual-channel scaffold
|
||||
|
||||
Deferred to future release. Tracked but not in current roadmap.
|
||||
- [ ] **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
|
||||
|
||||
### Security
|
||||
### FIXTURE — Real-flight integration fixture (Azaion 10.05.2026)
|
||||
|
||||
- **SEC-01**: JWT bearer token authentication on all API endpoints
|
||||
- **SEC-02**: TLS 1.3 on all HTTPS connections
|
||||
- **SEC-03**: Satellite tile manifest SHA-256 integrity verification
|
||||
- **SEC-04**: Mahalanobis distance outlier rejection in ESKF measurement updates
|
||||
- **SEC-05**: CORS origins locked down (remove wildcard default)
|
||||
- [ ] **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)
|
||||
|
||||
### Operational
|
||||
### TEST — Test taxonomy & infrastructure
|
||||
|
||||
- **OPS-01**: Uvicorn `reload` flag defaults to False in production config
|
||||
- **OPS-02**: Structured logging with configurable log levels per module
|
||||
- **OPS-03**: Pre-flight health check validates TRT engines loaded, tiles present, IMU receiving
|
||||
- **OPS-04**: ResultManager.publish_waypoint_update implemented for waypoint SSE emission
|
||||
- [ ] **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`
|
||||
|
||||
### Performance
|
||||
### OBS — Observability & tooling
|
||||
|
||||
- **PERF-01**: Dual CUDA stream execution (Stream A: VO, Stream B: satellite matching) for pipeline parallelism
|
||||
- **PERF-02**: Satellite tile RAM preload (±2km corridor) at startup for sub-millisecond tile access
|
||||
- [ ] **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
|
||||
|
||||
## Out of Scope
|
||||
---
|
||||
|
||||
Explicitly excluded. Documented to prevent scope creep.
|
||||
## Stage 3 candidates (parking lot)
|
||||
|
||||
| Feature | Reason |
|
||||
|---------|--------|
|
||||
| TRT engine building tooling | Engines are pre-built offline via trtexec; system only loads them |
|
||||
| Google Maps tile download tooling | Tiles pre-cached before flight; no live internet during flight |
|
||||
| Full ArduPilot hardware validation on Jetson | Post-v1; Jetson hardware testing is not in scope for this milestone |
|
||||
| Mobile/web ground station UI | SSE stream consumed by external systems; UI is out of scope |
|
||||
| Multi-UAV coordination | Single UAV instance only |
|
||||
| GTSAM ARM64 source build tooling | GTSAM on Jetson requires source compilation; CI uses mock; Jetson build is ops concern |
|
||||
| tech_stack.md synchronization | Documented inconsistency (3fps vs 0.7fps, etc.); separate documentation task |
|
||||
- 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
|
||||
|
||||
Which phases cover which requirements. Populated from ROADMAP.md phase assignments.
|
||||
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.
|
||||
|
||||
| Requirement | Phase | Status |
|
||||
|-------------|-------|--------|
|
||||
| ESKF-01 | Phase 1 | Pending |
|
||||
| ESKF-02 | Phase 1 | Pending |
|
||||
| ESKF-03 | Phase 1 | Pending |
|
||||
| ESKF-04 | Phase 1 | Pending |
|
||||
| ESKF-05 | Phase 1 | Pending |
|
||||
| ESKF-06 | Phase 1 | Pending |
|
||||
| VO-01 | Phase 2 | Pending |
|
||||
| VO-02 | Phase 2 | Pending |
|
||||
| VO-03 | Phase 2 | Pending |
|
||||
| VO-04 | Phase 2 | Pending |
|
||||
| VO-05 | Phase 2 | Pending |
|
||||
| SAT-01 | Phase 3 | Pending |
|
||||
| SAT-02 | Phase 3 | Pending |
|
||||
| SAT-03 | Phase 3 | Pending |
|
||||
| SAT-04 | Phase 3 | Pending |
|
||||
| SAT-05 | Phase 3 | Pending |
|
||||
| GPR-01 | Phase 3 | Pending |
|
||||
| GPR-02 | Phase 3 | Pending |
|
||||
| GPR-03 | Phase 3 | Pending |
|
||||
| MAV-01 | Phase 4 | Pending |
|
||||
| MAV-02 | Phase 4 | Pending |
|
||||
| MAV-03 | Phase 4 | Pending |
|
||||
| MAV-04 | Phase 4 | Pending |
|
||||
| MAV-05 | Phase 4 | Pending |
|
||||
| PIPE-01 | Phase 5 | Pending |
|
||||
| PIPE-02 | Phase 5 | Pending |
|
||||
| PIPE-03 | Phase 5 | Pending |
|
||||
| PIPE-04 | Phase 5 | Pending |
|
||||
| PIPE-05 | Phase 5 | Pending |
|
||||
| PIPE-06 | Phase 5 | Pending |
|
||||
| PIPE-07 | Phase 5 | Pending |
|
||||
| PIPE-08 | Phase 5 | Pending |
|
||||
| TEST-01 | Phase 6 | Pending |
|
||||
| TEST-02 | Phase 6 | Pending |
|
||||
| TEST-03 | Phase 7 | Pending |
|
||||
| TEST-04 | Phase 7 | Pending |
|
||||
| TEST-05 | Phase 7 | Pending |
|
||||
| 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:**
|
||||
- v1 requirements: 36 total
|
||||
- Mapped to phases: 36
|
||||
- Unmapped: 0
|
||||
|
||||
---
|
||||
*Requirements defined: 2026-04-01*
|
||||
*Last updated: 2026-04-01 after initial definition*
|
||||
**Coverage:** 52/52 requirements mapped. No orphans, no duplicates.
|
||||
|
||||
+120
-82
@@ -1,114 +1,152 @@
|
||||
# Roadmap: GPS-Denied Onboard Navigation System
|
||||
# Roadmap: GPS-Denied Onboard Navigation System — Stage 2
|
||||
|
||||
**Stage:** 2 (independent iteration)
|
||||
**Created:** 2026-05-10
|
||||
**Branch:** `stage2` (HEAD = stage1; v1.0 archived under `.planning/archive/v1.0/`)
|
||||
**Granularity:** standard
|
||||
**Total phases:** 6
|
||||
**Total requirements mapped:** 52 / 52 (100% coverage)
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The scaffold exists (~2800 lines): FastAPI service, all component ABCs, Pydantic schemas, database layer, and SSE streaming are in place. What is missing is every algorithmic kernel. This roadmap implements them in dependency order: the ESKF math core first (everything else feeds into it), then the two sensor inputs (VO and satellite/GPR), then the MAVLink output that closes the loop to the flight controller, then end-to-end pipeline wiring, then a Docker SITL test harness, and finally accuracy validation against real flight data.
|
||||
Stage 2 is a **self-contained iteration** with its own phase numbering (1–6). It is NOT a continuation of Stage 1's seven phases — those are archived under `.planning/archive/v1.0/` and treated as MVP starting capital (the working ESKF + cuVSLAM/ORB VO + GPR + MAVLink + 195 passing tests).
|
||||
|
||||
The Stage 2 mission: refactor the inherited MVP into a hexagonal/ports-and-adapters architecture, re-implement (not merge) selected concept-level ideas from the parallel `try02` branch, formalize acceptance criteria with testable numerics, and add the Azaion 10.05.2026 real-flight integration fixture — all without regressing any of the 195 stage1 tests.
|
||||
|
||||
Phases are derived from the ten Stage 2 requirement categories (ARCH, AC, SAFE, VERIFY, FDR, VPR, MAVOUT, FIXTURE, TEST, OBS) and ordered so each phase stabilizes the Protocol surfaces and test infrastructure that the next phase depends on.
|
||||
|
||||
## Phase Dependency Order
|
||||
|
||||
```
|
||||
Phase 1 (ARCH — hexagonal refactor + composition root; Protocols stabilized)
|
||||
↓
|
||||
Phase 2 (AC + TEST taxonomy + structlog spine — measurement scaffolding)
|
||||
↓
|
||||
Phase 3 (SAFE state machine + VERIFY anchor gates — authoritative source labels)
|
||||
↓
|
||||
Phase 4 (Conditional Multi-Scale VPR + FDR — uses SAFE triggers, FDR for audit)
|
||||
↓
|
||||
Phase 5 (MAVOUT — source-aware GPS_INPUT + spoofing + blackout — needs SAFE labels)
|
||||
↓
|
||||
Phase 6 (FIXTURE — Azaion replay + CLI + per-env Docker — exercises everything e2e)
|
||||
```
|
||||
|
||||
## Phases
|
||||
|
||||
- [ ] **Phase 1: ESKF Core** - 15-state error-state Kalman filter, coordinate transforms, confidence scoring
|
||||
- [ ] **Phase 2: Visual Odometry** - cuVSLAM wrapper (Jetson) + OpenCV ORB stub (dev/CI) + TRT SuperPoint/LightGlue
|
||||
- [ ] **Phase 3: Satellite Matching + GPR** - XFeat TRT matching, offline tile pipeline, real Faiss GPR index
|
||||
- [ ] **Phase 4: MAVLink I/O** - pymavlink GPS_INPUT output loop, IMU input listener, telemetry, re-localization request
|
||||
- [ ] **Phase 5: End-to-End Pipeline Wiring** - processor integration, GTSAM factor graph, recovery coordinator, object localization
|
||||
- [ ] **Phase 6: Docker SITL Harness + CI** - ArduPilot SITL, camera replay, tile server mock, CI integration
|
||||
- [ ] **Phase 7: Accuracy Validation** - 60-frame dataset validation, latency profiling, blackbox test suite
|
||||
- [ ] **Phase 1: Hexagonal Refactor & Composition Root** — Reorganize stage1 MVP into `components/` hexagonal layout with Protocol-typed DI composition root; no regressions.
|
||||
- [ ] **Phase 2: Acceptance Criteria + Test Taxonomy + Observability Spine** — Formal AC document with numeric thresholds, `tests/{unit,integration,blackbox,sitl,e2e}/` taxonomy, structlog correlation_id spine.
|
||||
- [ ] **Phase 3: Safety Anchor State Machine & Geometry-Gated Verifier** — Authoritative `source_label` ownership + accept/reject gates for satellite anchors before they reach ESKF.
|
||||
- [ ] **Phase 4: Conditional Multi-Scale VPR + Flight Data Recorder** — Trigger-driven DINOv2 forward, multi-scale FAISS chunks, append-only event log with bounded storage.
|
||||
- [ ] **Phase 5: MAVLink Source-Aware Output & Spoofing/Blackout Handling** — Source labels + anchor age in GPS_INPUT, spoofing-promotion <3s, visual-blackout dead-reckoning ≤400ms, ODOMETRY scaffold behind feature flag.
|
||||
- [ ] **Phase 6: Real-Flight Fixture (Azaion 10.05.2026) + CLI + Per-Env Docker** — End-to-end integration test on real flight data, `gps_denied` typer CLI, split Jetson/x86 Dockerfiles.
|
||||
|
||||
## Phase Details
|
||||
|
||||
### Phase 1: ESKF Core
|
||||
**Goal**: A correct, standalone ESKF implementation exists that fuses IMU, VO, and satellite measurements and outputs confidence-tiered position estimates in WGS84
|
||||
**Depends on**: Nothing (first phase — no other algorithmic component depends on this being absent)
|
||||
**Requirements**: ESKF-01, ESKF-02, ESKF-03, ESKF-04, ESKF-05, ESKF-06
|
||||
**Success Criteria** (what must be TRUE):
|
||||
1. ESKF propagates nominal state (position, velocity, quaternion, biases) from synthetic IMU inputs and covariance grows correctly between measurement updates
|
||||
2. VO measurement update reduces position uncertainty and innovation is within expected bounds for a simulated relative pose input
|
||||
3. Satellite measurement update corrects absolute position and covariance tightens to satellite noise level
|
||||
4. Confidence tier outputs HIGH when last satellite correction is recent and covariance is small, MEDIUM on VO-only, LOW on IMU-only — verified by unit tests
|
||||
5. Full coordinate chain (pixel → camera ray → body → NED → WGS84) produces correct GPS coordinates for a known geometry test case; all FAKE Math stubs replaced
|
||||
**Plans**: 3 plans
|
||||
Plans:
|
||||
- [x] 01-01-PLAN.md — ESKF core algorithm (schemas, 15-state filter, IMU prediction, VO/satellite updates, confidence tiers)
|
||||
- [x] 01-02-PLAN.md — Coordinate chain fix (replace fake math with real K matrix projection, ray-ground intersection)
|
||||
- [x] 01-03-PLAN.md — Unit tests for ESKF and coordinate chain (18+ ESKF tests, 10+ coordinate tests)
|
||||
### Phase 1: Hexagonal Refactor & Composition Root
|
||||
|
||||
### Phase 2: Visual Odometry
|
||||
**Goal**: VO produces metric relative poses via cuVSLAM on Jetson and via OpenCV ORB on dev/CI, both satisfying the same interface — no more scale-ambiguous unit vectors
|
||||
**Depends on**: Phase 1 (ESKF provides metric scale reference and coordinate transforms for VO measurement update)
|
||||
**Requirements**: VO-01, VO-02, VO-03, VO-04, VO-05
|
||||
**Goal**: Stage1 MVP reorganized into hexagonal/ports-and-adapters layout with explicit DI composition root; all 195 stage1 tests still pass.
|
||||
**Depends on**: Nothing (first phase; consumes stage1 archived code as input).
|
||||
**Requirements**: ARCH-01, ARCH-02, ARCH-03, ARCH-04, ARCH-05, ARCH-06, ARCH-07
|
||||
**Success Criteria** (what must be TRUE):
|
||||
1. cuVSLAM wrapper initializes in Inertial mode with camera intrinsics and IMU parameters, and returns RelativePose with `scale_ambiguous=False` and metric translation in NED
|
||||
2. OpenCV ORB stub satisfies the same ISequentialVisualOdometry interface and passes the same interface contract tests as the cuVSLAM wrapper
|
||||
3. TRT SuperPoint/LightGlue engines load and run inference on Jetson; MockInferenceEngine is selected automatically on dev/x86
|
||||
4. ImageInputPipeline accepts single-image batches without error; sequence lookup returns the correct frame with no substring collision
|
||||
1. Every swappable component (vio, satellite_matcher, gpr, anchor_verifier, safety_state, flight_recorder, mavlink_io, coordinate_transforms) lives under `src/gps_denied/components/<name>/` with its own `protocol.py` + concrete impls + (where needed) `native/` bridge.
|
||||
2. Hot-path types (`FrameState`, `IMUSample`, `PositionEstimate`, `VOEstimate`, `SatelliteAnchor`) are `@dataclass(slots=True, frozen=True)` and Pydantic no longer touches the per-frame path.
|
||||
3. Calling `build_pipeline(env="x86_dev")` / `"jetson"` / `"ci"` / `"sitl"` from `pipeline/composition.py` returns a fully-wired `Pipeline` with environment-correct adapters and no concrete imports leaking into pipeline orchestration.
|
||||
4. Per-environment YAML configs (`config/{jetson,x86_dev,ci,sitl}.yaml`) load via `pydantic-settings` into a typed `RuntimeConfig` that drives composition.
|
||||
5. `pytest` runs all 195 stage1 tests (+ 8 SITL skipped) green and accuracy benchmarks show no regression vs the archived stage1 baseline.
|
||||
**Plans**: TBD
|
||||
|
||||
### Phase 3: Satellite Matching + GPR
|
||||
**Goal**: The system can correct absolute position from pre-loaded satellite tiles and re-localize after tracking loss using a real Faiss descriptor index
|
||||
**Depends on**: Phase 1 (ESKF position uncertainty drives tile selection radius and measurement update), Phase 2 (VO provides keyframe selection timing)
|
||||
**Requirements**: SAT-01, SAT-02, SAT-03, SAT-04, SAT-05, GPR-01, GPR-02, GPR-03
|
||||
### Phase 2: Acceptance Criteria + Test Taxonomy + Observability Spine
|
||||
|
||||
**Goal**: Project gains a formal, testable acceptance-criteria contract, a structured test taxonomy, and a structured-logging spine — the measurement scaffolding every later phase needs to prove its claims.
|
||||
**Depends on**: Phase 1 (Protocol surfaces and components/ layout must exist before tests/AC can reference them).
|
||||
**Requirements**: AC-01, AC-02, AC-03, AC-04, AC-05, AC-06, TEST-01, TEST-02, TEST-03, OBS-01
|
||||
**Success Criteria** (what must be TRUE):
|
||||
1. Satellite tile selection queries the local GeoHash-indexed directory using ESKF position ± 3σ and returns correct tiles without any HTTP requests
|
||||
2. Camera frame is GSD-normalized to satellite resolution before matching; XFeat TRT inference runs on Jetson and MockInferenceEngine on dev/CI
|
||||
3. RANSAC homography produces a WGS84 position estimate with a confidence score derived from inlier ratio, accepted by ESKF satellite measurement update
|
||||
4. GPR loads a real Faiss index from disk and returns tile candidates ranked by DINOv2 descriptor similarity (not random vectors)
|
||||
5. After simulated tracking loss, GPR candidate + MetricRefinement produces an ESKF re-localization within expected accuracy bounds
|
||||
1. `_docs/00_problem/acceptance_criteria.md` lists every AC-1.x…AC-NEW-x with numeric threshold + validation method + linked test ID(s); no AC entry is unbound.
|
||||
2. `tests/` is reorganized into `unit/integration/blackbox/sitl/e2e/`, every existing test is reclassified, and `pytest -m unit|integration|blackbox|sitl|e2e` selects the right subset for CI.
|
||||
3. Running `scripts/gen_ac_traceability.py` produces `.planning/AC-TRACEABILITY.md` linking every AC ID → test ID(s) → component(s); CI fails if any AC is orphaned.
|
||||
4. Position-accuracy, failure-mode, and real-time-performance ACs are wired to `tests/integration/accuracy/`, `tests/blackbox/failure_modes/`, and a benchmark harness that emits CI-tracked metrics.
|
||||
5. Pipeline emits structured JSON via `structlog` with `correlation_id` (frame_id) on every per-frame log line, and Pydantic logging schemas guard the boundary records.
|
||||
**Plans**: TBD
|
||||
|
||||
### Phase 4: MAVLink I/O
|
||||
**Goal**: The flight controller receives GPS_INPUT at 5-10Hz and the system receives IMU data from the flight controller — the primary acceptance criterion is met end-to-end for the communication layer
|
||||
**Depends on**: Phase 1 (ESKF state is the source for GPS_INPUT field population; IMU data drives ESKF prediction)
|
||||
**Requirements**: MAV-01, MAV-02, MAV-03, MAV-04, MAV-05
|
||||
### Phase 3: Safety Anchor State Machine & Geometry-Gated Verifier
|
||||
|
||||
**Goal**: A separate safety layer — not the ESKF — owns the authoritative `source_label`, enforces monotonic covariance growth in non-anchored modes, and only accepts satellite anchors that pass formal geometric gates.
|
||||
**Depends on**: Phase 2 (needs AC document + test taxonomy + structlog so state-machine behavior is testable and observable).
|
||||
**Requirements**: SAFE-01, SAFE-02, SAFE-03, SAFE-04, SAFE-05, SAFE-06, VERIFY-01, VERIFY-02, VERIFY-03, VERIFY-04, VERIFY-05
|
||||
**Success Criteria** (what must be TRUE):
|
||||
1. pymavlink sends GPS_INPUT messages to a MAVLink endpoint at 5-10Hz; all required fields populated (lat, lon, alt, velocity, accuracy, fix_type, hdop, vdop, GPS time)
|
||||
2. fix_type maps correctly from ESKF confidence tier: HIGH → 3 (3D fix), MEDIUM → 2 (2D fix), LOW → 0 (no fix)
|
||||
3. IMU listener receives ATTITUDE/RAW_IMU from flight controller at 5-10Hz and ESKF prediction step runs at that rate between camera frames
|
||||
4. After 3 consecutive frames with no position estimate, a MAVLink NAMED_VALUE_FLOAT message with last known position is sent (verifiable in SITL logs)
|
||||
5. Telemetry at 1Hz emits confidence score and drift estimate to ground station via NAMED_VALUE_FLOAT
|
||||
1. Every emitted `PositionEstimate` carries one of `satellite_anchored / vo_extrapolated / dead_reckoned` set by `SafetyAnchorStateMachine`, plus an `anchor_age_ms` field that increases until the next accepted anchor.
|
||||
2. Property-based tests prove covariance never decreases without an accepted anchor, and a unit-test matrix exercises all 9 declared state transitions.
|
||||
3. `GeometryGatedAnchorVerifier` accepts/rejects each candidate using configurable gates (min inliers, max mean reprojection error, max homography condition number, freshness window) and emits a machine-readable rejection reason on every reject.
|
||||
4. Tile-write eligibility (`can_persist_tile`) is exposed by the state machine and is `false` whenever the system is in `dead_reckoned`, so the tile cache cannot be poisoned during blind flight.
|
||||
5. The state machine never sees raw VPR top-K candidates — `AnchorVerifier` is the only path that can hand it an accepted anchor — and benchmark mode lets matcher profiles be compared offline on a fixed frame.
|
||||
**Plans**: TBD
|
||||
|
||||
### Phase 5: End-to-End Pipeline Wiring
|
||||
**Goal**: A single uploaded camera frame travels through the full pipeline — VO, ESKF update, satellite correction (on keyframes), GPS_INPUT output — with no hardcoded stubs in the path
|
||||
**Depends on**: Phase 1, Phase 2, Phase 3, Phase 4 (all algorithmic components must exist to be wired)
|
||||
**Requirements**: PIPE-01, PIPE-02, PIPE-03, PIPE-04, PIPE-05, PIPE-06, PIPE-07, PIPE-08
|
||||
### Phase 4: Conditional Multi-Scale VPR + Flight Data Recorder
|
||||
|
||||
**Goal**: DINOv2 retrieval runs only when re-localization is actually needed; chunks are decoupled from storage tiles with multi-scale coverage; every state transition / anchor decision / MAVLink emission is captured in an append-only flight recorder with bounded storage and explicit health states.
|
||||
**Depends on**: Phase 3 (VPR triggers and FDR events ride on SAFE state-transitions and VERIFY accept/reject decisions).
|
||||
**Requirements**: VPR-01, VPR-02, VPR-03, VPR-04, VPR-05, FDR-01, FDR-02, FDR-03, FDR-04, FDR-05, FDR-06
|
||||
**Success Criteria** (what must be TRUE):
|
||||
1. process_frame executes the full chain without error: VO relative pose → ESKF VO update → (every 5-10 frames) satellite match → ESKF satellite update → GPS_INPUT sent to flight controller
|
||||
2. SatelliteDataManager and CoordinateTransformer are instantiated in app.py lifespan and injected into the processor; no component is standalone
|
||||
3. FactorGraphOptimizer calls real GTSAM ISAM2 update when GTSAM is available; mock path remains for CI
|
||||
4. Object GPS localization (POST /objects/locate) returns a WGS84 position using the real pixel→ray→ground chain; hardcoded (48.0, 37.0) stub is gone
|
||||
5. Application starts without TypeError; ImageRotationManager constructor accepts the model manager argument
|
||||
1. In steady state the pipeline ranks chunks by IMU+VO geometric prior and skips the DINOv2 forward; DINOv2 runs only on declared re-loc triggers (cold start, sharp turn, σ_xy > 50m, VO failure ≥2 frames, disconnected segment).
|
||||
2. VPR chunks cover the operating area with 600–800m ground footprint and 40–50% overlap so any frame footprint falls fully inside ≥1 chunk; FAISS holds both fine-scale (z=20) and coarse-scale (z=17/18) descriptor sets.
|
||||
3. Top-K is dynamic — K=5 stable, K=20 active-conflict, K=50 expanding-window — and the integration uses the existing `chunk_manager.py` / `gpr.py` API surface without breaking stage1 GPR contracts.
|
||||
4. `FlightRecorder` writes append-only JSONL segments to `data/fdr/{flight_id}/segment-NNNN.jsonl`, enforces configurable segment + total storage byte limits, and exposes `health ∈ {ok, degraded, critical}`.
|
||||
5. State transitions, anchor accept/reject decisions, MAVLink sends, and pipeline errors are all recorded as FDR events; AC-NEW-3 forensic thumbnails fire at ≤0.1Hz on tile-generation failures within the FDR size budget.
|
||||
**Plans**: TBD
|
||||
|
||||
### Phase 6: Docker SITL Harness + CI
|
||||
**Goal**: The full pipeline can be tested in a reproducible Docker environment with ArduPilot SITL, camera replay, and a tile server mock — and CI runs this on every commit
|
||||
**Depends on**: Phase 5 (all components must be wired before integration testing is meaningful)
|
||||
**Requirements**: TEST-01, TEST-02
|
||||
### Phase 5: MAVLink Source-Aware Output & Spoofing/Blackout Handling
|
||||
|
||||
**Goal**: The MAVLink output the flight controller actually sees carries source provenance and reacts correctly to GPS spoofing and visual blackout, with the dual-channel ODOMETRY path scaffolded but disabled.
|
||||
**Depends on**: Phase 4 (needs SAFE source labels, FDR audit channel, and VPR triggers to drive blackout/promotion semantics).
|
||||
**Requirements**: MAVOUT-01, MAVOUT-02, MAVOUT-03, MAVOUT-04
|
||||
**Success Criteria** (what must be TRUE):
|
||||
1. `docker compose up` starts ArduPilot SITL, the GPS-denied service, a camera-replay container, and a satellite tile server mock — all communicate over MAVLink and HTTP
|
||||
2. CI pipeline runs on x86 using OpenCV ORB stub and MockInferenceEngine; all 85+ unit tests pass with no manual steps
|
||||
3. MAVLink GPS_INPUT messages are captured in SITL logs and show 5-10Hz output rate during camera replay
|
||||
4. Tracking loss scenario (simulated by replaying frames with no overlap) triggers RECOVERY state and sends re-localization request
|
||||
1. Every `GPS_INPUT` message carries `source_label`, `anchor_age_ms`, and `covariance_semimajor_m` propagated from the corresponding `PositionEstimate` (mapped into `horiz_accuracy` and a custom STATUSTEXT for label/age).
|
||||
2. When real-GPS health rolling average drops below threshold, the system promotes its own estimate to FC primary within <3s and emits a `STATUSTEXT` on every promotion/demotion.
|
||||
3. When the camera produces no usable signal, the pipeline switches to `dead_reckoned` within ≤1 processed frame OR ≤400ms and emits `VISUAL_BLACKOUT_IMU_ONLY` STATUSTEXT at 1–2Hz until imagery returns.
|
||||
4. The `ODOMETRY` emitter exists in code but is disabled by `config.mavlink.odometry_enabled=false` in stage 2, and an integration test asserts ODOMETRY is intentionally absent on the wire.
|
||||
**Plans**: TBD
|
||||
|
||||
### Phase 7: Accuracy Validation
|
||||
**Goal**: The system demonstrably meets the navigation accuracy acceptance criteria on the 60-frame test dataset, and all 21 blackbox test scenarios are implemented as runnable tests
|
||||
**Depends on**: Phase 6 (SITL harness is required for the blackbox test scenarios)
|
||||
**Requirements**: TEST-03, TEST-04, TEST-05
|
||||
### Phase 6: Real-Flight Fixture (Azaion 10.05.2026) + CLI + Per-Env Docker
|
||||
|
||||
**Goal**: The whole stack is exercised end-to-end against real flight data, an operator-facing CLI replays flights and runs AC benchmarks, and per-environment Docker images close the deployment loop.
|
||||
**Depends on**: Phase 5 (final phase — exercises ARCH + AC + SAFE + VERIFY + VPR + FDR + MAVOUT against the Azaion fixture).
|
||||
**Requirements**: FIXTURE-01, FIXTURE-02, FIXTURE-03, FIXTURE-04, FIXTURE-05, FIXTURE-06, FIXTURE-07, OBS-02, OBS-03
|
||||
**Success Criteria** (what must be TRUE):
|
||||
1. Running against AD000001–AD000060.jpg with coordinates.csv ground truth: 80% of frames within 50m error and 60% of frames within 20m error
|
||||
2. Maximum cumulative VO drift between satellite corrections is less than 100m across any segment in the test dataset
|
||||
3. End-to-end latency per frame (camera capture to GPS_INPUT) is under 400ms on Jetson, with a breakdown report per pipeline stage
|
||||
4. All 21 blackbox test scenarios (FT-P-01 to FT-P-14, FT-N-01 to FT-N-07) run as pytest tests against the SITL harness and produce a pass/fail report
|
||||
1. `tests/integration/azaion_flight/` runs against `Data/Azaion/10.05.2026/` (tlog + cropped EO video + MAVLink CSV) and is documented in `_docs/00_problem/fixtures.md` with ground-truth references and known limitations.
|
||||
2. `scripts/prep_azaion_fixture.py` produces HUD-stripped EO frames at 0.7 fps, an IMU/GPS/ATTITUDE CSV from the tlog, and a timestamp-aligned manifest.
|
||||
3. MAVLink replay decodes every `GLOBAL_POSITION_INT` / `RAW_IMU` / `ATTITUDE` message without error; ESKF replay on the real IMU samples produces no NaN/Inf and shows bounded covariance growth; ORB-SLAM3 VO smoke test achieves ≥30% frame registration on the cropped EO frames.
|
||||
4. The GPS-denial simulation masks `GPS_RAW_INT` for t∈[180s, 280s] and the pipeline correctly switches to `vo_extrapolated` and back to `satellite_anchored` when GPS returns.
|
||||
5. `gps_denied` typer CLI exposes `replay --tlog ... --video ...`, `benchmark --scenario ...`, and `bench-ac AC-1.1`; `Dockerfile.x86_dev` and `Dockerfile.jetson` (multi-stage with TRT engine prebuild step) build green and run the replay end-to-end on their respective platforms.
|
||||
**Plans**: TBD
|
||||
|
||||
## Progress
|
||||
|
||||
| Phase | Plans Complete | Status | Completed |
|
||||
|-------|----------------|--------|-----------|
|
||||
| 1. ESKF Core | 0/3 | Planned | - |
|
||||
| 2. Visual Odometry | 0/TBD | Not started | - |
|
||||
| 3. Satellite Matching + GPR | 0/TBD | Not started | - |
|
||||
| 4. MAVLink I/O | 0/TBD | Not started | - |
|
||||
| 5. End-to-End Pipeline Wiring | 0/TBD | Not started | - |
|
||||
| 6. Docker SITL Harness + CI | 0/TBD | Not started | - |
|
||||
| 7. Accuracy Validation | 0/TBD | Not started | - |
|
||||
| 1. Hexagonal Refactor & Composition Root | 0/0 | Not started | - |
|
||||
| 2. Acceptance Criteria + Test Taxonomy + Observability Spine | 0/0 | Not started | - |
|
||||
| 3. Safety Anchor State Machine & Geometry-Gated Verifier | 0/0 | Not started | - |
|
||||
| 4. Conditional Multi-Scale VPR + Flight Data Recorder | 0/0 | Not started | - |
|
||||
| 5. MAVLink Source-Aware Output & Spoofing/Blackout Handling | 0/0 | Not started | - |
|
||||
| 6. Real-Flight Fixture (Azaion 10.05.2026) + CLI + Per-Env Docker | 0/0 | Not started | - |
|
||||
|
||||
## Coverage Summary
|
||||
|
||||
| Category | Count | Phase |
|
||||
|----------|-------|-------|
|
||||
| ARCH | 7 | Phase 1 |
|
||||
| AC | 6 | Phase 2 |
|
||||
| TEST | 3 | Phase 2 |
|
||||
| OBS-01 | 1 | Phase 2 |
|
||||
| SAFE | 6 | Phase 3 |
|
||||
| VERIFY | 5 | Phase 3 |
|
||||
| VPR | 5 | Phase 4 |
|
||||
| FDR | 6 | Phase 4 |
|
||||
| MAVOUT | 4 | Phase 5 |
|
||||
| FIXTURE | 7 | Phase 6 |
|
||||
| OBS-02, OBS-03 | 2 | Phase 6 |
|
||||
| **Total** | **52** | **6 phases** |
|
||||
|
||||
100% of Stage 2 requirements mapped; no orphans; no duplicates.
|
||||
|
||||
+64
-37
@@ -1,71 +1,98 @@
|
||||
---
|
||||
gsd_state_version: 1.0
|
||||
milestone: v1.0
|
||||
milestone_name: milestone
|
||||
status: Phase 1 complete
|
||||
last_updated: "2026-04-01T21:05:00Z"
|
||||
milestone: v2.0
|
||||
milestone_name: Stage 2 — Hexagonal architecture + try02 idea integration + real-flight fixture
|
||||
status: phase_1_complete
|
||||
last_updated: "2026-05-11T00:00:00Z"
|
||||
last_activity: 2026-05-11 — Phase 1 complete; 01-08 composition root + YAML config shipped; 216/216 tests green
|
||||
progress:
|
||||
total_phases: 7
|
||||
total_phases: 6
|
||||
completed_phases: 1
|
||||
total_plans: 3
|
||||
completed_plans: 3
|
||||
total_plans: 8
|
||||
completed_plans: 8
|
||||
percent: 100
|
||||
---
|
||||
|
||||
# Project State
|
||||
|
||||
## Project Reference
|
||||
|
||||
See: .planning/PROJECT.md (updated 2026-04-01)
|
||||
See: .planning/PROJECT.md (updated 2026-05-10)
|
||||
|
||||
**Core value:** 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.
|
||||
**Current focus:** Phase 2 — Visual Odometry Pipeline
|
||||
**Current focus:** Stage 2 / Phase 1 — Hexagonal Refactor & Composition Root
|
||||
|
||||
## Current Phase
|
||||
|
||||
**Phase:** 1 — ESKF Core (✓ Complete)
|
||||
**Next action:** Run `/gsd:plan-phase 2` to plan Phase 2 (Visual Odometry Pipeline)
|
||||
**Phase:** 1 — Hexagonal Refactor & Composition Root (COMPLETE)
|
||||
**Next phase:** Phase 2 — Acceptance Criteria + Test Taxonomy + Observability Spine
|
||||
|
||||
## Roadmap Summary
|
||||
|
||||
### Stage 1 (v1.0 — archived under `.planning/archive/v1.0/`)
|
||||
|
||||
Treated as MVP starting capital, not active backlog. ESKF + cuVSLAM/ORB VO + GPR + MAVLink + 195 passing tests + 8 SITL skipped. Refactoring is allowed and expected.
|
||||
|
||||
### Stage 2 (v2.0 — current iteration, Phases 1–6)
|
||||
|
||||
| Phase | Name | Status |
|
||||
|-------|------|--------|
|
||||
| 1 | ESKF Core | ✓ Complete |
|
||||
| 2 | Visual Odometry Pipeline | Pending |
|
||||
| 3 | Satellite Matching + GPR | Pending |
|
||||
| 4 | MAVLink I/O | Pending |
|
||||
| 5 | End-to-End Pipeline Wiring | Pending |
|
||||
| 6 | Docker SITL Harness + CI | Pending |
|
||||
| 7 | Accuracy Validation | Pending |
|
||||
| 1 | Hexagonal Refactor & Composition Root | **Complete** (8/8 plans, 216 tests green) |
|
||||
| 2 | Acceptance Criteria + Test Taxonomy + Observability Spine | Pending |
|
||||
| 3 | Safety Anchor State Machine & Geometry-Gated Verifier | Pending |
|
||||
| 4 | Conditional Multi-Scale VPR + Flight Data Recorder | Pending |
|
||||
| 5 | MAVLink Source-Aware Output & Spoofing/Blackout Handling | Pending |
|
||||
| 6 | Real-Flight Fixture (Azaion 10.05.2026) + CLI + Per-Env Docker | Pending |
|
||||
|
||||
## Key Files
|
||||
|
||||
- `.planning/PROJECT.md` — project context and requirements
|
||||
- `.planning/REQUIREMENTS.md` — 36 v1 requirements with traceability
|
||||
- `.planning/ROADMAP.md` — 7-phase execution plan
|
||||
- `.planning/PROJECT.md` — Stage 2 project context
|
||||
- `.planning/REQUIREMENTS.md` — 52 Stage 2 requirements with traceability
|
||||
- `.planning/ROADMAP.md` — Stage 2 roadmap, 6 phases
|
||||
- `.planning/archive/v1.0/` — Stage 1 historical record (PROJECT/REQUIREMENTS/ROADMAP/phases)
|
||||
- `.planning/codebase/` — codebase map (ARCHITECTURE, CONCERNS, STACK, etc.)
|
||||
- `_docs/01_solution/solution.md` — authoritative architecture spec
|
||||
- `_docs/00_problem/acceptance_criteria.md` — 43 test scenarios
|
||||
- `_docs/00_problem/acceptance_criteria.md` — to be rewritten with formal AC-1.x (Phase 2)
|
||||
|
||||
## Session Notes
|
||||
|
||||
- Initialized 2026-04-01
|
||||
- Brownfield: scaffold exists (~2800 lines), critical algorithms missing (ESKF, MAVLink, real TRT/cuVSLAM)
|
||||
- cuVSLAM + TRT available only on Jetson; dev/CI uses OpenCV ORB stub + MockInferenceEngine
|
||||
- Pipeline direction: top-down (API → ESKF → VO → satellite → GPS_INPUT)
|
||||
- 2026-04-01 — Project initialized; Stage 1 brownfield scaffold (~2800 lines)
|
||||
- Stage 1 complete — 195 passing + 8 SITL skipped tests, all 7 phases shipped, archived to `.planning/archive/v1.0/`
|
||||
- 2026-05-10 — Stage 2 opened as independent iteration with own phase numbering (1–6); 52 requirements drafted
|
||||
- 2026-05-10 — Stage 2 ROADMAP.md created; 100% requirement coverage; traceability populated in REQUIREMENTS.md
|
||||
- Stage 2 strategy: refactor stage1 working code to hexagonal layout, re-implement try02 concepts (NOT layout details), formalize AC, add Azaion real-flight fixture
|
||||
- Code from stage1 is MVP — refactoring is allowed and expected; no regression in 195 stage1 tests is the floor
|
||||
- 2026-05-11 — Phase 1 complete: Plans 01-01 through 01-08 executed; 216/216 tests passing; ARCH-01..07 all satisfied
|
||||
|
||||
## Phase 1 Execution Summary (2026-04-01)
|
||||
## Stage 2 Phase Dependency Order
|
||||
|
||||
**Status:** ✓ Complete — All 3 plans executed, 35 tests passing
|
||||
```
|
||||
Phase 1 (ARCH refactor — Protocol surfaces stabilize first)
|
||||
↓
|
||||
Phase 2 (AC + TEST taxonomy + structlog spine)
|
||||
↓
|
||||
Phase 3 (SAFE state machine + VERIFY anchor gates)
|
||||
↓
|
||||
Phase 4 (Conditional VPR + FDR — needs trigger semantics from SAFE)
|
||||
↓
|
||||
Phase 5 (MAVOUT — source labels + spoofing + blackout — needs SAFE labels + FDR audit)
|
||||
↓
|
||||
Phase 6 (FIXTURE — Azaion real-flight + CLI + per-env Docker — exercises everything end-to-end)
|
||||
```
|
||||
|
||||
**Deliverables:**
|
||||
- `src/gps_denied/schemas/eskf.py` (68 lines) — ESKF data contracts (ConfidenceTier, ESKFState, ESKFConfig, IMUMeasurement)
|
||||
- `src/gps_denied/core/eskf.py` (359 lines) — 15-state ESKF with IMU prediction, VO/satellite updates, confidence tiers
|
||||
- `src/gps_denied/core/coordinates.py` (176 lines added) — Real K-matrix projection, ray-ground intersection, gps_to_pixel inverse
|
||||
- `tests/test_eskf.py` (290 lines) — 18 ESKF unit tests
|
||||
- `tests/test_coordinates.py` (+200 lines) — 17 coordinate chain tests
|
||||
## Current Position
|
||||
|
||||
**Requirements Covered:** ESKF-01 through ESKF-06 (all 6 Phase 1 requirements)
|
||||
Phase: 1 — Hexagonal Refactor & Composition Root
|
||||
Plan: 08 (COMPLETE — final plan of phase)
|
||||
Status: Phase 1 complete — all 8 plans executed, 216/216 tests green, ARCH-01..07 satisfied
|
||||
Last activity: 2026-05-11 — Plan 01-08 complete; build_pipeline factory + RuntimeConfig + YAML config shipped
|
||||
|
||||
**Commits:** 4 total (schemas, core ESKF, coordinates, tests, summaries)
|
||||
## Key Decisions (Phase 1)
|
||||
|
||||
**Verification:** pytest 35/35 passing (100% success)
|
||||
- ARCH-01: components/{vio,satellite_matcher,gpr,mavlink_io,anchor_verifier,safety_state,flight_recorder,coordinate_transforms}/ created with protocol.py + impls
|
||||
- ARCH-02: hot_types migration deferred to Phase 2 (Pydantic retained for 216-test stability)
|
||||
- ARCH-03: pipeline/composition.py build_pipeline(env) as explicit DI root
|
||||
- ARCH-04: core/ math files retained as single files (eskf, factor_graph, coordinates, chunk_manager, recovery, rotation)
|
||||
- ARCH-05: typing.Protocol throughout; orchestrator.py has zero concrete adapter imports
|
||||
- ARCH-06: config/{jetson,x86_dev,ci,sitl}.yaml + RuntimeConfig.env + YamlConfigSettingsSource
|
||||
- ARCH-07: 216 passed / 8 skipped / 0 failed (baseline was 216+8 skipped)
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
# ADR 0002 — Hexagonal / Ports-and-Adapters Architecture for Stage 2
|
||||
|
||||
**Date:** 2026-05-11
|
||||
**Status:** Accepted
|
||||
**Supersedes:** —
|
||||
**Implemented in:** Phase 1 (2026-05-11)
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
Stage 1 used a flat `src/gps_denied/core/` layout where all implementations lived as peers — `vo.py`, `gpr.py`, `mavlink.py`, `satellite.py`, `metric.py`, `graph.py`, `processor.py`, etc. ABCs were scattered across files. The pipeline was wired inline inside `app.py:lifespan`.
|
||||
|
||||
For Stage 2 we evaluated three architectural options:
|
||||
|
||||
**Option A — Continue flat monolith**: keep `core/` as-is, add new code alongside existing files. Lowest friction, but backends are not swappable without editing the orchestrator; no clear seam for Jetson vs dev implementations.
|
||||
|
||||
**Option B — Hexagonal / ports-and-adapters**: one folder per swappable component under `components/`, each with a `protocol.py` (the port) and concrete adapter files (the adapters). Math stays concentrated in `core/`. Explicit DI composition root `pipeline/composition.py` wires env-specific adapters. Test the orchestrator against Protocols — concrete adapters only appear in composition.py.
|
||||
|
||||
**Option C — Microservices with IPC**: separate processes per component. Rejected immediately — adds network latency on a <400ms budget, no hardware justification.
|
||||
|
||||
The parallel `try02` branch chose a similar hexagonal layout but used Pydantic models on the per-frame hot path. We observed in benchmarks that per-frame Pydantic validation has measurable overhead at 0.7fps on Jetson's 8GB shared pool. We chose Option B but diverged from try02 on the hot-path type decision (see ADR 0003).
|
||||
|
||||
## Decision
|
||||
|
||||
Adopt **Option B — hexagonal / ports-and-adapters** with the following rules:
|
||||
|
||||
1. Every swappable backend gets its own folder: `src/gps_denied/components/{vio, satellite_matcher, gpr, mavlink_io, anchor_verifier, safety_state, flight_recorder, coordinate_transforms}/`
|
||||
2. Each component folder contains `protocol.py` (a `typing.Protocol` port) + one or more concrete adapter files.
|
||||
3. `core/` is retained for concentrated math (ESKF, factor graph, RANSAC, coordinates) — these are pure-function single files, NOT split into `interfaces.py + impl.py`.
|
||||
4. The orchestrator (`pipeline/orchestrator.py`) imports only Protocols — no concrete adapters. Only `pipeline/composition.py` imports concrete adapters.
|
||||
5. Per-environment wiring via `build_pipeline(env: Literal["jetson","x86_dev","ci","sitl"]) -> FlightProcessor`.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive:**
|
||||
- cuVSLAM backend (Jetson) vs ORB-SLAM3 stub (dev/CI) are swapped by changing a single `create_vo_backend()` call in `composition.py` — no orchestrator edits.
|
||||
- New component (e.g., Safety Anchor State Machine in Phase 3) gets its own folder with a Protocol first; the orchestrator only sees the Protocol.
|
||||
- Tests inject mock adapters directly via `attach_components()` — no monkey-patching needed.
|
||||
- `orchestrator.py` passes ARCH-05 check: zero concrete adapter imports verified via grep.
|
||||
|
||||
**Negative / Trade-offs:**
|
||||
- Every moved file leaves a re-export shim at the old path to keep 216 existing tests green. Shims accumulate tech debt until Phase 2 removes them.
|
||||
- `Pose` (Pydantic) inside `factor_graph.py` has mutable `.position` assignments at lines 182–297. Converting it to a frozen dataclass requires rewriting those mutation sites — deferred to Phase 2 to avoid breaking the regression floor.
|
||||
- 8 component folders with stub Protocols for Phase 3/4 components (anchor_verifier, safety_state, flight_recorder, coordinate_transforms) add file count without code yet — this is intentional scaffolding.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- Phase 1 (Plans 01-08) implemented this decision end-to-end. 216/216 tests pass.
|
||||
- Private helpers `_confidence_to_fix_type`, `_eskf_to_gps_input`, `_unix_to_gps_time` are tested directly by `tests/test_mavlink.py`. The `core/mavlink.py` shim re-exports them verbatim. When shims are removed in Phase 2, those tests must be updated to import from `components/mavlink_io/pymavlink_bridge`.
|
||||
- Faiss numpy fallback stays inline in `components/gpr/faiss_gpr.py:load_index()` — splitting into a sibling `numpy_gpr.py` is Phase 4 (VPR-03) work.
|
||||
@@ -0,0 +1,52 @@
|
||||
# ADR 0003 — `@dataclass(slots=True, frozen=True)` on Hot Path; Pydantic at Boundaries Only
|
||||
|
||||
**Date:** 2026-05-11
|
||||
**Status:** Accepted (partially implemented — Phase 1 scaffolded; full migration Phase 2)
|
||||
**Supersedes:** —
|
||||
**Implemented in:** Phase 1 scaffold; Phase 2 full migration
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
Stage 1 and the parallel `try02` branch both used Pydantic models (`BaseModel`) for per-frame data types: `FrameState`, `IMUSample`, `PositionEstimate`, `VOEstimate`, `SatelliteAnchor`. Pydantic v2 is fast, but on the per-frame path at 0.7fps with Jetson's shared 8GB CPU/GPU pool, every `model_validate()` or `__init__` triggers field validation, type coercion, and `__dict__` allocation — none of which we need for internal pipeline types whose values come from trusted numpy operations.
|
||||
|
||||
try02's design doc noted this overhead but kept Pydantic for "consistency." We rejected this trade-off.
|
||||
|
||||
Pydantic remains genuinely valuable at system boundaries: REST API request/response parsing (FastAPI), config loading (pydantic-settings), and DB schema validation (SQLAlchemy models). At those boundaries, external input is untrusted and validation catches bugs early. On the per-frame path, input comes from our own numpy operations — validation is redundant overhead.
|
||||
|
||||
## Decision
|
||||
|
||||
**Hot-path data types** use `@dataclass(slots=True, frozen=True)` from Python 3.10+:
|
||||
- `FrameState` — per-frame snapshot passed through the pipeline
|
||||
- `IMUSample` — raw IMU measurement from MAVLink
|
||||
- `PositionEstimate` — output of ESKF, input to GPS_INPUT encoding
|
||||
- `VOEstimate` — output of visual odometry backend
|
||||
- `SatelliteAnchor` — accepted satellite match result
|
||||
|
||||
These live in `src/gps_denied/hot_types/`. Old schema paths (`gps_denied.schemas.eskf`, `gps_denied.schemas.vo`, etc.) are shimmed to re-export from `hot_types` for test compatibility.
|
||||
|
||||
**Boundary types** keep Pydantic:
|
||||
- FastAPI request/response schemas (`src/gps_denied/schemas/`)
|
||||
- `AppSettings` / `RuntimeConfig` (pydantic-settings)
|
||||
- `AsyncSQLAlchemy` models
|
||||
- `Pose` — special case (see below)
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive:**
|
||||
- `slots=True` eliminates `__dict__` per instance — reduces per-frame allocations on a memory-constrained target.
|
||||
- `frozen=True` prevents accidental mutation deep in the pipeline — catches bugs at assignment time rather than as silent state corruption.
|
||||
- `dataclasses.replace()` for modified copies is explicit and cheap.
|
||||
- No validation overhead on trusted internal data.
|
||||
|
||||
**Negative / Exceptions:**
|
||||
- **`Pose` stays Pydantic** in Phase 1. `core/factor_graph.py` mutates `pose.position` at lines 182, 207, 230, 297 using `pose.position[0] = x` style assignment. Converting `Pose` to a frozen dataclass requires rewriting 4 mutation sites to use `dataclasses.replace()`. Deferred to Phase 2 to avoid breaking the regression floor during the Phase 1 rename wave.
|
||||
- **`GPSPoint` stays Pydantic** — it appears in REST responses and is already at a boundary. No change needed.
|
||||
- `dataclasses.replace()` is more verbose than Pydantic's `.model_copy(update={...})`. Acceptable trade-off.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- `src/gps_denied/hot_types/` scaffolded in Plan 01-01 with 5 types + `__init__.py`.
|
||||
- Old schema files (`schemas/eskf.py`, `schemas/vo.py`, `schemas/satellite.py`, `schemas/metric.py`, `schemas/rotation.py`) converted to re-export shims pointing to `hot_types`.
|
||||
- Phase 2 work: migrate all `Pose` mutation sites to `dataclasses.replace()`; remove schema shims; update tests to import from `hot_types` directly.
|
||||
@@ -0,0 +1,52 @@
|
||||
# ADR 0004 — Stage 2 as Independent Iteration (Own Phases 1–6)
|
||||
|
||||
**Date:** 2026-05-10
|
||||
**Status:** Accepted
|
||||
**Supersedes:** —
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
After Stage 1 delivered a working MVP (195 tests, ESKF + cuVSLAM + satellite matching + MAVLink pipeline), the question was how to structure the next development cycle. Two options:
|
||||
|
||||
**Option A — Continue Stage 1 phase numbering**: treat Stage 2 as Phases 8–13 (continuing from Stage 1's Phase 7). The roadmap grows linearly. Decisions from Stage 1 are "inherited constraints."
|
||||
|
||||
**Option B — Fresh iteration**: Stage 2 is a self-contained iteration with its own Phases 1–6, its own requirements document, its own success criteria. Stage 1 code is treated as MVP starting capital — refactoring is expected and allowed. Only AC-driven test outcomes are sacred.
|
||||
|
||||
The problem with Option A: treating Stage 1 phases as immutable history means we cannot refactor the architecture without numbering collisions, and it creates psychological friction against rewriting decisions that turned out to be suboptimal. The GSD workflow (milestone → phases → plans) works cleanest when each milestone has its own numbered phase space.
|
||||
|
||||
The parallel `try02` branch from a different team demonstrated a completely different architectural take on the same problem in the same time window. We wanted to be able to incorporate their concept-level ideas freely, not be constrained by compatibility with Stage 1's exact module boundaries.
|
||||
|
||||
## Decision
|
||||
|
||||
Each development stage is an independent iteration:
|
||||
- Own `REQUIREMENTS.md` (52 v2 requirements vs 36 v1 requirements)
|
||||
- Own `ROADMAP.md` (Phases 1–6)
|
||||
- Own phase numbering starting from 1
|
||||
- Stage 1 artifacts archived in `.planning/archive/v1.0/` as historical record, not active backlog
|
||||
- Stage 1 code treated as MVP — any file can be refactored, moved, or replaced if the tests still pass
|
||||
|
||||
**Stage 2 sources of starting capital:**
|
||||
- Stage 1 codebase (own work): ESKF, VO, GPR, MAVLink, pipeline, 216 passing tests
|
||||
- try02 branch (parallel team): concept-level ideas harvested and re-implemented — Safety Anchor State Machine, Geometry-Gated Anchor Verifier, FDR, Conditional VPR, formal AC document, test taxonomy
|
||||
- Azaion 10.05.2026 real-flight dataset: tlog + 6min video used as integration fixture
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive:**
|
||||
- Clean phase numbering — Phase 1 of Stage 2 is the hexagonal refactor, unambiguous
|
||||
- Freedom to refactor Stage 1 code without "breaking" numbered phases
|
||||
- try02 ideas integrated by re-implementation (not git merge) — avoids namespace collisions and allows selective adoption
|
||||
- `stage2` branch starts at HEAD = stage1, with Stage 2 work built on top
|
||||
|
||||
**Negative / Trade-offs:**
|
||||
- Stage 1 tests that tested specific module paths (e.g., `from gps_denied.core.vo import`) become shim-dependent after Phase 1 moves code. Shim cleanup is Phase 2 work — tests are not edited during Phase 1 to preserve the regression floor.
|
||||
- The `try02` branch is checked out as a worktree at `../gps-denied-onboard-try02/` for reading. We do NOT merge from it — ideas are read and re-implemented from scratch.
|
||||
|
||||
## Stage Boundary Convention
|
||||
|
||||
At stage completion:
|
||||
1. Snapshot `PROJECT.md` / `REQUIREMENTS.md` / `ROADMAP.md` / `phases/` → `.planning/archive/v[X.Y]/`
|
||||
2. Open Stage N+1 with a fresh roadmap starting at Phase 1
|
||||
3. Carry forward only validated decisions and unresolved parking-lot items
|
||||
Reference in New Issue
Block a user