mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-21 10:21:13 +00:00
[AZ-263] [AZ-264] [AZ-265] Decompose: layout, helpers epic, replay epic
Decompose Step 1 + Step 1.5 + new cycle-1 epics: - Step 1 (Bootstrap): AZ-263 spec at _docs/02_tasks/todo/. Single top-level Python package src/gps_denied_onboard/ + nested components/ subpackage per user feedback (replaces earlier src/gps_denied/ + sibling src/components/ split). - Step 1.5 (Module Layout): _docs/02_document/module-layout.md is the file-ownership map consumed by /implement Step 4. Covers all 14 components + cross-cuttings (_types, config, logging, fdr_client, helpers x8, frame_source, clock, runtime_root, cli/replay, healthcheck), 5-layer layering, and the Build-Time Exclusion Map for all 4 binaries (airborne, research, operator-tooling, replay-cli). - New epic AZ-264 (E-CC-HELPERS): re-homes the 8 shared helpers from per-component child-issues into a single cross-cutting epic per the decompose skill cross-cutting rule. R14 (LightGlue circular dep) is structurally prevented because both C2.5 and C3 import gps_denied_onboard.helpers.lightglue_runtime. - New epic AZ-265 (E-DEMO-REPLAY): offline replay mode (video + tlog -> per-tick coordinate stream). 8 child tasks, 27-32 pts. Reuses C8 FcAdapter via TlogReplayFcAdapter strategy + new VideoFileFrameSource + JsonlReplaySink + compose_replay composition root + gps-denied-replay CLI + auto-sync via IMU take-off detection (per how_to_test.md). NO ROS dependency. - Plan Final report at FINAL_report.md. - _autodev_state.md updated with handoff notes for Step 2 execution in a fresh chat (~290 MCP calls expected; epic ordering documented). Step 2 task PLAN approved (97 implementation tasks across 18 epics) but EXECUTION deferred per user choice to a fresh chat. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+214
-39
@@ -1,6 +1,6 @@
|
||||
# Work-Item Epics — gps-denied-onboard Plan cycle 1
|
||||
|
||||
This file is the local epic draft for Plan Step 6. Tracker IDs (`AZ-XXX`) are populated when each epic is created in Jira (project `AZ`). Until then, every entry carries `Tracker: pending`.
|
||||
This file is the local epic draft for Plan Step 6. Tracker IDs (`AZ-XXX`) are now populated for every epic — they live in Jira project `AZ`. The canonical `E-*` ↔ `AZ-NN` mapping below is the source of truth referenced from each Jira epic's description.
|
||||
|
||||
## Conventions
|
||||
|
||||
@@ -10,29 +10,35 @@ This file is the local epic draft for Plan Step 6. Tracker IDs (`AZ-XXX`) are po
|
||||
- **Cross-cutting epics** parent exactly one shared implementation task; component epics consuming the concern declare a dependency, never re-implement locally.
|
||||
- **Dependency rule**: no epic depends on a later one in this index.
|
||||
|
||||
## Decompose-time amendment (cycle 1, dated 2026-05-10)
|
||||
|
||||
Row 20 (E-CC-HELPERS / AZ-264) was added during Decompose Step 2 to comply with the cross-cutting rule. The 8 shared helpers (`ImuPreintegrator`, `SE3Utils`, `LightGlueRuntime`, `WgsConverter`, `Sha256Sidecar`, `EngineFilenameSchema`, `RansacFilter`, `DescriptorNormaliser`) were originally listed as child issues inside their largest-consumer component epics (e.g., `ImuPreintegrator` under E-C1 child #5, `LightGlueRuntime` under E-C2.5 child #2). Those child-issue listings are now superseded — helper ownership moves to E-CC-HELPERS, and component epics consume helpers as dependencies. The original component epic descriptions in Jira still reference the helpers in their child-issue tables; those will be reconciled at the next epic-edit pass (or at Step 4 cross-verification).
|
||||
|
||||
## Index
|
||||
|
||||
| # | Epic ID | Title | Type | Tracker | T-shirt | Story Pts | Depends on |
|
||||
|---|---------|-------|------|---------|---------|-----------|------------|
|
||||
| 1 | E-BOOT | Bootstrap & Initial Structure | bootstrap | Tracker: pending | M | 13–21 | — |
|
||||
| 2 | E-CC-LOG | Cross-Cutting: Structured JSON Logging | cross-cutting | Tracker: pending | S | 5–8 | E-BOOT |
|
||||
| 3 | E-CC-CONF | Cross-Cutting: Configuration & Composition Root | cross-cutting | Tracker: pending | S | 5–8 | E-BOOT |
|
||||
| 4 | E-CC-FDR-CLIENT | Cross-Cutting: FDR Producer Client (lock-free queue + record schema) | cross-cutting | Tracker: pending | M | 8–13 | E-BOOT, E-CC-LOG |
|
||||
| 5 | E-C13 | C13 Flight Data Recorder (writer thread + segments + cap) | component | Tracker: pending | L | 21–34 | E-BOOT, E-CC-LOG, E-CC-CONF, E-CC-FDR-CLIENT |
|
||||
| 6 | E-C7 | C7 On-Jetson Inference Runtime | component | Tracker: pending | L | 21–34 | E-BOOT, E-CC-CONF, E-CC-FDR-CLIENT |
|
||||
| 7 | E-C6 | C6 Tile Cache + Spatial Index | component | Tracker: pending | M | 13–21 | E-BOOT, E-CC-LOG, E-CC-CONF |
|
||||
| 8 | E-C11 | C11 Tile Manager (TileDownloader + TileUploader) | component | Tracker: pending | M | 13–21 | E-C6, E-CC-CONF, E-CC-LOG |
|
||||
| 9 | E-C10 | C10 Pre-flight Cache Provisioning | component | Tracker: pending | M | 13–21 | E-C6, E-C7, E-CC-LOG |
|
||||
| 10 | E-C12 | C12 Operator Pre-flight Tooling | component | Tracker: pending | M | 13–21 | E-C10, E-C11, E-CC-LOG |
|
||||
| 11 | E-C1 | C1 Visual / Visual-Inertial Odometry | component | Tracker: pending | XL | 34–55 | E-BOOT, E-CC-FDR-CLIENT, E-C7 |
|
||||
| 12 | E-C2 | C2 Visual Place Recognition | component | Tracker: pending | L | 21–34 | E-C6, E-C7, E-CC-FDR-CLIENT |
|
||||
| 13 | E-C2.5 | C2.5 Inlier-based Re-rank | component | Tracker: pending | S | 5–8 | E-C2, E-C7, E-C6 (LightGlue helper shared with C3) |
|
||||
| 14 | E-C3 | C3 Cross-Domain Matcher | component | Tracker: pending | L | 21–34 | E-C2.5, E-C7 |
|
||||
| 15 | E-C3.5 | C3.5 AdHoP-Conditional Refinement | component | Tracker: pending | M | 8–13 | E-C3, E-C7 |
|
||||
| 16 | E-C4 | C4 Pose Estimator | component | Tracker: pending | M | 13–21 | E-C3.5, E-C5 (shared GTSAM substrate; co-developed) |
|
||||
| 17 | E-C5 | C5 State Estimator | component | Tracker: pending | XL | 34–55 | E-C1, E-C4 (shared graph), E-CC-FDR-CLIENT |
|
||||
| 18 | E-C8 | C8 FC + GCS Adapter | component | Tracker: pending | L | 21–34 | E-C5, E-CC-CONF, E-CC-LOG |
|
||||
| 19 | E-BBT | Blackbox Tests (FT/NFT scenarios) | tests | Tracker: pending | M | 13–21 | every component epic ships its component-internal tests under its own epic; this one parents the suite-level FT/NFT scenarios in `_docs/02_document/tests/*.md` |
|
||||
| 1 | E-BOOT | Bootstrap & Initial Structure | bootstrap | AZ-244 | M | 13–21 | — |
|
||||
| 2 | E-CC-LOG | Cross-Cutting: Structured JSON Logging | cross-cutting | AZ-245 | S | 5–8 | E-BOOT |
|
||||
| 3 | E-CC-CONF | Cross-Cutting: Configuration & Composition Root | cross-cutting | AZ-246 | S | 5–8 | E-BOOT |
|
||||
| 4 | E-CC-FDR-CLIENT | Cross-Cutting: FDR Producer Client (lock-free queue + record schema) | cross-cutting | AZ-247 | M | 8–13 | E-BOOT, E-CC-LOG |
|
||||
| 5 | E-C13 | C13 Flight Data Recorder (writer thread + segments + cap) | component | AZ-248 | L | 21–34 | E-BOOT, E-CC-LOG, E-CC-CONF, E-CC-FDR-CLIENT |
|
||||
| 6 | E-C7 | C7 On-Jetson Inference Runtime | component | AZ-249 | L | 21–34 | E-BOOT, E-CC-CONF, E-CC-FDR-CLIENT |
|
||||
| 7 | E-C6 | C6 Tile Cache + Spatial Index | component | AZ-250 | M | 13–21 | E-BOOT, E-CC-LOG, E-CC-CONF |
|
||||
| 8 | E-C11 | C11 Tile Manager (TileDownloader + TileUploader) | component | AZ-251 | M | 13–21 | E-C6, E-CC-CONF, E-CC-LOG |
|
||||
| 9 | E-C10 | C10 Pre-flight Cache Provisioning | component | AZ-252 | M | 13–21 | E-C6, E-C7, E-CC-LOG |
|
||||
| 10 | E-C12 | C12 Operator Pre-flight Tooling | component | AZ-253 | M | 13–21 | E-C10, E-C11, E-CC-LOG |
|
||||
| 11 | E-C1 | C1 Visual / Visual-Inertial Odometry | component | AZ-254 | XL | 34–55 | E-BOOT, E-CC-FDR-CLIENT, E-C7 |
|
||||
| 12 | E-C2 | C2 Visual Place Recognition | component | AZ-255 | L | 21–34 | E-C6, E-C7, E-CC-FDR-CLIENT |
|
||||
| 13 | E-C2.5 | C2.5 Inlier-based Re-rank | component | AZ-256 | S | 5–8 | E-C2, E-C7, E-C6 (LightGlue helper shared with C3) |
|
||||
| 14 | E-C3 | C3 Cross-Domain Matcher | component | AZ-257 | L | 21–34 | E-C2.5, E-C7 |
|
||||
| 15 | E-C3.5 | C3.5 AdHoP-Conditional Refinement | component | AZ-258 | M | 8–13 | E-C3, E-C7 |
|
||||
| 16 | E-C4 | C4 Pose Estimator | component | AZ-259 | M | 13–21 | E-C3.5, E-C5 (shared GTSAM substrate; co-developed) |
|
||||
| 17 | E-C5 | C5 State Estimator | component | AZ-260 | XL | 34–55 | E-C1, E-C4 (shared graph), E-CC-FDR-CLIENT |
|
||||
| 18 | E-C8 | C8 FC + GCS Adapter | component | AZ-261 | L | 21–34 | E-C5, E-CC-CONF, E-CC-LOG |
|
||||
| 19 | E-BBT | Blackbox Tests (FT/NFT scenarios) | tests | AZ-262 | M | 13–21 | every component epic ships its component-internal tests under its own epic; this one parents the suite-level FT/NFT scenarios in `_docs/02_document/tests/*.md` |
|
||||
| 20 | E-CC-HELPERS | Cross-Cutting: Common Helpers (8 shared utilities) | cross-cutting | AZ-264 | M | 13–21 | E-BOOT, E-CC-LOG (added in Decompose Step 2 — supersedes per-component helper child-issues from cycle 1) |
|
||||
| 21 | E-DEMO-REPLAY | Offline replay mode (video + tlog → per-tick coordinate stream) | feature | AZ-265 | M | 22–27 | E-C1, E-C2, E-C2.5, E-C3, E-C3.5, E-C4, E-C5, E-C8, E-CC-CONF (added in Decompose Step 2 — enables parent-suite UI demo via subprocess + JSONL streaming) |
|
||||
|
||||
## High-level component dependency diagram
|
||||
|
||||
@@ -57,10 +63,13 @@ flowchart TB
|
||||
C5[E-C5 State]
|
||||
C8[E-C8 FC Adapter]
|
||||
BBT[E-BBT Blackbox Tests]
|
||||
HELP[E-CC-HELPERS Common Helpers]
|
||||
DEMO[E-DEMO-REPLAY Offline Replay Mode]
|
||||
|
||||
BOOT --> LOG --> FDRC --> C13
|
||||
BOOT --> CONF --> C13
|
||||
BOOT --> CONF --> C7
|
||||
BOOT --> LOG --> HELP
|
||||
C13 -.-> C7
|
||||
CONF --> C6 --> C11
|
||||
C6 --> C10
|
||||
@@ -77,13 +86,30 @@ flowchart TB
|
||||
FDRC --> C5
|
||||
C8 --> BBT
|
||||
C12 --> BBT
|
||||
HELP -.-> C1
|
||||
HELP -.-> C2
|
||||
HELP -.-> C25
|
||||
HELP -.-> C3
|
||||
HELP -.-> C35
|
||||
HELP -.-> C4
|
||||
HELP -.-> C5
|
||||
HELP -.-> C6
|
||||
HELP -.-> C7
|
||||
HELP -.-> C8
|
||||
HELP -.-> C10
|
||||
HELP -.-> C11
|
||||
HELP -.-> C12
|
||||
C1 --> DEMO
|
||||
C5 --> DEMO
|
||||
C8 --> DEMO
|
||||
CONF --> DEMO
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## E-BOOT — Bootstrap & Initial Structure
|
||||
|
||||
**Tracker**: pending
|
||||
**Tracker**: AZ-244
|
||||
**Type**: bootstrap
|
||||
**T-shirt**: M | **Story points**: 13–21
|
||||
**Owner**: onboard team
|
||||
@@ -188,7 +214,7 @@ T-shirt M; 13–21 story points across child PBIs (each ≤ 5 points).
|
||||
|
||||
## E-CC-LOG — Cross-Cutting: Structured JSON Logging
|
||||
|
||||
**Tracker**: pending
|
||||
**Tracker**: AZ-245
|
||||
**Type**: cross-cutting
|
||||
**T-shirt**: S | **Story points**: 5–8
|
||||
|
||||
@@ -294,7 +320,7 @@ T-shirt S; 5–8 points.
|
||||
|
||||
## E-CC-CONF — Cross-Cutting: Configuration & Composition Root
|
||||
|
||||
**Tracker**: pending
|
||||
**Tracker**: AZ-246
|
||||
**Type**: cross-cutting
|
||||
**T-shirt**: S | **Story points**: 5–8
|
||||
|
||||
@@ -390,7 +416,7 @@ T-shirt S; 5–8 points.
|
||||
|
||||
## E-CC-FDR-CLIENT — Cross-Cutting: FDR Producer Client
|
||||
|
||||
**Tracker**: pending
|
||||
**Tracker**: AZ-247
|
||||
**Type**: cross-cutting
|
||||
**T-shirt**: M | **Story points**: 8–13
|
||||
|
||||
@@ -494,7 +520,7 @@ T-shirt M; 8–13 points.
|
||||
|
||||
## E-C13 — C13 Flight Data Recorder
|
||||
|
||||
**Tracker**: pending | **Type**: component | **T-shirt**: L | **Story points**: 21–34
|
||||
**Tracker**: AZ-248 | **Type**: component | **T-shirt**: L | **Story points**: 21–34
|
||||
|
||||
### System context
|
||||
|
||||
@@ -598,7 +624,7 @@ T-shirt L; 21–34 points.
|
||||
|
||||
## E-C7 — C7 On-Jetson Inference Runtime
|
||||
|
||||
**Tracker**: pending | **Type**: component | **T-shirt**: L | **Story points**: 21–34
|
||||
**Tracker**: AZ-249 | **Type**: component | **T-shirt**: L | **Story points**: 21–34
|
||||
|
||||
### System context
|
||||
|
||||
@@ -706,7 +732,7 @@ T-shirt L; 21–34 points.
|
||||
|
||||
## E-C6 — C6 Tile Cache + Spatial Index
|
||||
|
||||
**Tracker**: pending | **Type**: component | **T-shirt**: M | **Story points**: 13–21
|
||||
**Tracker**: AZ-250 | **Type**: component | **T-shirt**: M | **Story points**: 13–21
|
||||
|
||||
### System context
|
||||
|
||||
@@ -810,7 +836,7 @@ Per `components/08_c6_tile_cache/tests.md`.
|
||||
|
||||
## E-C11 — C11 Tile Manager (TileDownloader + TileUploader)
|
||||
|
||||
**Tracker**: pending | **Type**: component | **T-shirt**: M | **Story points**: 13–21
|
||||
**Tracker**: AZ-251 | **Type**: component | **T-shirt**: M | **Story points**: 13–21
|
||||
|
||||
### System context
|
||||
|
||||
@@ -921,7 +947,7 @@ Per `components/12_c11_tilemanager/tests.md`.
|
||||
|
||||
## E-C10 — C10 Pre-flight Cache Provisioning
|
||||
|
||||
**Tracker**: pending | **Type**: component | **T-shirt**: M | **Story points**: 13–21
|
||||
**Tracker**: AZ-252 | **Type**: component | **T-shirt**: M | **Story points**: 13–21
|
||||
|
||||
### System context
|
||||
|
||||
@@ -1023,7 +1049,7 @@ Per `components/11_c10_provisioning/tests.md`.
|
||||
|
||||
## E-C12 — C12 Operator Pre-flight Tooling
|
||||
|
||||
**Tracker**: pending | **Type**: component | **T-shirt**: M | **Story points**: 13–21
|
||||
**Tracker**: AZ-253 | **Type**: component | **T-shirt**: M | **Story points**: 13–21
|
||||
|
||||
### System context
|
||||
|
||||
@@ -1124,7 +1150,7 @@ Per `components/13_c12_operator_tooling/tests.md`.
|
||||
|
||||
## E-C1 — C1 Visual / Visual-Inertial Odometry
|
||||
|
||||
**Tracker**: pending | **Type**: component | **T-shirt**: XL | **Story points**: 34–55
|
||||
**Tracker**: AZ-254 | **Type**: component | **T-shirt**: XL | **Story points**: 34–55
|
||||
|
||||
### System context
|
||||
|
||||
@@ -1230,7 +1256,7 @@ Per `components/01_c1_vio/tests.md` + suite-level FT-P-02 / FT-P-04 / FT-P-05.
|
||||
|
||||
## E-C2 — C2 Visual Place Recognition
|
||||
|
||||
**Tracker**: pending | **Type**: component | **T-shirt**: L | **Story points**: 21–34
|
||||
**Tracker**: AZ-255 | **Type**: component | **T-shirt**: L | **Story points**: 21–34
|
||||
|
||||
### System context
|
||||
|
||||
@@ -1331,7 +1357,7 @@ Per `components/02_c2_vpr/tests.md`.
|
||||
|
||||
## E-C2.5 — C2.5 Inlier-based Re-rank
|
||||
|
||||
**Tracker**: pending | **Type**: component | **T-shirt**: S | **Story points**: 5–8
|
||||
**Tracker**: AZ-256 | **Type**: component | **T-shirt**: S | **Story points**: 5–8
|
||||
|
||||
### System context
|
||||
|
||||
@@ -1425,7 +1451,7 @@ Per `components/03_c2_5_rerank/tests.md`.
|
||||
|
||||
## E-C3 — C3 Cross-Domain Matcher
|
||||
|
||||
**Tracker**: pending | **Type**: component | **T-shirt**: L | **Story points**: 21–34
|
||||
**Tracker**: AZ-257 | **Type**: component | **T-shirt**: L | **Story points**: 21–34
|
||||
|
||||
### System context
|
||||
|
||||
@@ -1523,7 +1549,7 @@ Per `components/04_c3_matcher/tests.md`.
|
||||
|
||||
## E-C3.5 — C3.5 AdHoP-Conditional Refinement
|
||||
|
||||
**Tracker**: pending | **Type**: component | **T-shirt**: M | **Story points**: 8–13
|
||||
**Tracker**: AZ-258 | **Type**: component | **T-shirt**: M | **Story points**: 8–13
|
||||
|
||||
### System context
|
||||
|
||||
@@ -1617,7 +1643,7 @@ Per `components/05_c3_5_adhop/tests.md`.
|
||||
|
||||
## E-C4 — C4 Pose Estimator
|
||||
|
||||
**Tracker**: pending | **Type**: component | **T-shirt**: M | **Story points**: 13–21
|
||||
**Tracker**: AZ-259 | **Type**: component | **T-shirt**: M | **Story points**: 13–21
|
||||
|
||||
### System context
|
||||
|
||||
@@ -1720,7 +1746,7 @@ Per `components/06_c4_pose/tests.md`.
|
||||
|
||||
## E-C5 — C5 State Estimator
|
||||
|
||||
**Tracker**: pending | **Type**: component | **T-shirt**: XL | **Story points**: 34–55
|
||||
**Tracker**: AZ-260 | **Type**: component | **T-shirt**: XL | **Story points**: 34–55
|
||||
|
||||
### System context
|
||||
|
||||
@@ -1835,7 +1861,7 @@ Per `components/07_c5_state/tests.md`.
|
||||
|
||||
## E-C8 — C8 FC + GCS Adapter
|
||||
|
||||
**Tracker**: pending | **Type**: component | **T-shirt**: L | **Story points**: 21–34
|
||||
**Tracker**: AZ-261 | **Type**: component | **T-shirt**: L | **Story points**: 21–34
|
||||
|
||||
### System context
|
||||
|
||||
@@ -1948,7 +1974,7 @@ Per `components/10_c8_fc_adapter/tests.md`.
|
||||
|
||||
## E-BBT — Blackbox Tests (FT/NFT scenarios)
|
||||
|
||||
**Tracker**: pending | **Type**: tests | **T-shirt**: M | **Story points**: 13–21
|
||||
**Tracker**: AZ-262 | **Type**: tests | **T-shirt**: M | **Story points**: 13–21
|
||||
|
||||
### System context
|
||||
|
||||
@@ -2042,6 +2068,155 @@ This epic IS the testing strategy for system-level scenarios. Per-component test
|
||||
|
||||
---
|
||||
|
||||
## E-DEMO-REPLAY — Offline replay mode (video + tlog → per-tick coordinate stream)
|
||||
|
||||
**Tracker**: AZ-265
|
||||
**Type**: feature (deployment-adjacent)
|
||||
**T-shirt**: M | **Story points**: 27–32
|
||||
**Added**: Decompose Step 2 (cycle 1, 2026-05-10)
|
||||
**Source notes**: `_docs/how_to_test.md` (user-written demo requirements — auto-sync incorporated as child task #8)
|
||||
|
||||
### System context
|
||||
|
||||
Demonstrate the GPS-denied positioning pipeline against historical flight data: a video file from the nav camera + a `.tlog` file from the FC. The replay mode runs the **same C1–C5 inference pipeline** the airborne binary runs; only the input transport (live camera → video file; live MAVLink → tlog) and output sink (FC MAVLink emit → JSONL) differ. NO ROS dependency is added — replay reuses the existing C8 `FcAdapter` interface via the strategy pattern.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph LIVE[Airborne mode — unchanged]
|
||||
CAM[Live camera] --> C1L[C1 VIO]
|
||||
FCL[Live FC MAVLink] --> C8L[C8 inbound]
|
||||
C8L --> C1L
|
||||
C1L --> C2L[C2..C5]
|
||||
C2L --> C8OL[C8 outbound] --> FCL
|
||||
end
|
||||
subgraph REPLAY[Replay mode — this epic]
|
||||
VID[Video file .mp4/.h264] --> VFFS[VideoFileFrameSource] --> C1R[C1 VIO]
|
||||
TLOG[tlog file] --> TLR[TlogReplayFcAdapter] --> C1R
|
||||
C1R --> C2R[C2..C5]
|
||||
C2R --> RSINK[JsonlReplaySink] --> JSONL[results.jsonl - one EstimatorOutput per tick]
|
||||
end
|
||||
```
|
||||
|
||||
### Problem / Context
|
||||
|
||||
The parent-suite UI (in `ui/` workspace, out of scope for this repo) needs to demo the GPS-denied positioning end-to-end. Per-component fixtures or simulators would not give the demo end-to-end fidelity. Instead, replay mode runs the production pipeline against historical inputs — demo confidence equals field test confidence on the same footage.
|
||||
|
||||
ROS as the input transport was considered and rejected: the system is MAVLink-native; introducing ROS would (a) add a major new dependency, (b) split production vs. demo code paths, and (c) duplicate code. Reusing the existing C8 `FcAdapter` interface with a tlog-replay strategy is strictly better.
|
||||
|
||||
### Scope
|
||||
|
||||
**In scope**:
|
||||
- `FrameSource` interface (formalised cross-cutting; previously implicit "camera ingest thread") + `VideoFileFrameSource` strategy + `LiveCameraFrameSource` retrofit (no-op restructure of existing camera plumbing).
|
||||
- `TlogReplayFcAdapter` strategy (new C8 `FcAdapter` impl) parsing pymavlink `.tlog` files and emitting `ImuWindow` / `AttitudeWindow` / `GpsHealth` / `FlightStateSignal` at tlog timestamp cadence.
|
||||
- `ReplaySink` interface + `JsonlReplaySink` impl (one `EstimatorOutput` per line).
|
||||
- `compose_replay(config) -> ReplayRoot` composition root extending E-CC-CONF (AZ-246).
|
||||
- `Clock` injection (per R-DEMO-4) so timer-driven logic in C1–C5 works in both wall-clock (live) and tlog-simulated (replay) modes.
|
||||
- `gps-denied-replay` CLI: `--video PATH --tlog PATH --output results.jsonl --camera-calibration calib.json --config config.yaml --pace {realtime,asap} [--time-offset-ms N]`.
|
||||
- Fourth Docker image `gps-denied-replay-cli` (Python + C1–C5 + cpp/* + replay strategies; NO C6/C10/C11/C12; NO HTTP server).
|
||||
- E2E replay test on a 1–2 min Derkachi clip + matching tlog asserting estimated track within ≤ 100 m of ground-truth GPS for ≥ 80 % of ticks.
|
||||
|
||||
**Out of scope**:
|
||||
- ROS / ROS2 dependency.
|
||||
- HTTP wrapper microservice (parent-suite UI backend shells out to the CLI; defer until subprocess-shape is proven insufficient).
|
||||
- Modifying any C1–C5 component to be replay-aware — they MUST remain mode-agnostic.
|
||||
- C6 mid-flight write path (replay reads a pre-built tile cache; doesn't write).
|
||||
|
||||
### Architecture notes
|
||||
|
||||
- ADR-001 / ADR-002 / ADR-009 all apply unchanged.
|
||||
- New `BUILD_*` flags: `BUILD_VIDEO_FILE_FRAME_SOURCE`, `BUILD_TLOG_REPLAY_ADAPTER`, `BUILD_REPLAY_SINK_JSONL`. Default ON for the new replay-cli binary; OFF for airborne, research, and operator-tooling.
|
||||
- New cross-cutting `FrameSource` interface lives at `src/gps_denied_onboard/frame_source/` (Layer 1 Foundation per `module-layout.md` § layering).
|
||||
- `compose_replay` lives in `runtime_root.py` alongside `compose_root` and `compose_operator`.
|
||||
|
||||
### Interface specification
|
||||
|
||||
```python
|
||||
class FrameSource(Protocol):
|
||||
def next_frame(self) -> NavCameraFrame | None: ...
|
||||
def close(self) -> None: ...
|
||||
|
||||
class VideoFileFrameSource(FrameSource):
|
||||
def __init__(self, video_path: Path, frame_rate_hz: float, camera_id: str): ...
|
||||
|
||||
class TlogReplayFcAdapter(FcAdapter): # FcAdapter from AZ-261 / E-C8
|
||||
def __init__(self, tlog_path: Path, target_fc_dialect: enum {ARDUPILOT, INAV}): ...
|
||||
|
||||
class ReplaySink(Protocol):
|
||||
def emit(self, output: EstimatorOutput) -> None: ...
|
||||
def close(self) -> None: ...
|
||||
|
||||
class JsonlReplaySink(ReplaySink):
|
||||
def __init__(self, output_path: Path): ...
|
||||
|
||||
def compose_replay(config: Config) -> ReplayRoot: ...
|
||||
```
|
||||
|
||||
### Data flow
|
||||
|
||||
Startup → load config / calibration → process tlog + video timestamp-aligned → for each frame: camera-ingest → C1 → C2 → C2.5 → C3 → C3.5 → C4 → C5 → emit `EstimatorOutput` to `JsonlReplaySink`. End of input → close sink → exit.
|
||||
|
||||
`--pace realtime` paces frames at wall-clock; `--pace asap` runs uncapped (default). The injected `Clock` is wall-clock-derived in `realtime` mode and tlog-timestamp-derived in `asap` mode so component fallback timers (e.g., AC-5.2 3 s no-estimate fallback) trigger consistently in both.
|
||||
|
||||
### Dependencies
|
||||
|
||||
- E-C1, E-C2, E-C2.5, E-C3, E-C3.5, E-C4, E-C5, E-C8 (every per-frame component).
|
||||
- E-CC-CONF (AZ-246) for `compose_root` extension.
|
||||
- E-CC-HELPERS (AZ-264) for `WgsConverter` (tlog GPS → local-tangent-plane).
|
||||
- Does NOT depend on E-C6 / E-C10 / E-C11 / E-C12 (replay reads pre-built cache; no operator-side workflows).
|
||||
|
||||
### Acceptance criteria
|
||||
|
||||
- AC-1: CLI exits 0 on a valid 1-min fixture and produces JSONL with one `EstimatorOutput` line per tlog tick (within ±5 % of `GLOBAL_POSITION_INT` count).
|
||||
- AC-2: Each line is a valid JSON object matching the `EstimatorOutput` schema.
|
||||
- AC-3: For a fixture with known ground-truth GPS, the L2 horizontal distance ≤ 100 m for ≥ 80 % of ticks (matches AC-1.3 cumulative-drift bound).
|
||||
- AC-4: Replay binary contains C1–C5 + replay strategies; SBOM diff CI step verifies absence of C6/C10/C11/C12.
|
||||
- AC-5: Same input → same output (deterministic) within ≤ 1e-6 float drift in position fields.
|
||||
- AC-6: `--pace realtime` runs the 1-min fixture in 60 ± 5 s; `--pace asap` in ≤ 30 s on Tier-1 hardware.
|
||||
- AC-7: Without `--time-offset-ms`, the CLI auto-detects the video ↔ tlog offset by correlating video motion-onset (or first-frame timestamp) with the tlog IMU take-off pattern (sustained vertical accel > 0.5 g + change in attitude rate > 1 rad/s lasting ≥ 0.5 s, matching the typical quadcopter take-off signature). On a fixture with known correct offset, the auto-detected offset is within ± 200 ms of ground truth. If auto-detect confidence is < 80 % the CLI logs a WARN and proceeds with the best-guess offset; `--time-offset-ms N` always overrides the auto-detect.
|
||||
- AC-8: If neither auto-detect nor manual offset can produce > 95 % of frames with at least one matching IMU window within ± 100 ms, the CLI exits with code 2 and prints both the auto-detected offset (if any) and the percentage of frames-with-IMU-window so the operator can debug.
|
||||
|
||||
### Non-functional requirements
|
||||
|
||||
- Cold-start ≤ 5 s (not subject to AC-NEW-1's 30 s budget — that's airborne-only).
|
||||
- Throughput ≥ 5 × real time on Jetson AGX Orin for `--pace asap`.
|
||||
- Memory ≤ 4 GB resident (lean image; no FAISS index unless tile lookup is needed).
|
||||
|
||||
### Risks & mitigations
|
||||
|
||||
- **R-DEMO-1**: Tlog ↔ video timestamp drift across long flights, AND the more-common case that recordings on the operator workstation are not synchronised at all (camera and FC start independently, often minutes apart). **Mitigation**: auto-sync via IMU take-off detection (AC-7) is the default; `--time-offset-ms N` is the manual override. If take-off pattern is ambiguous (e.g., fixed-wing hand-launch instead of quadcopter, or tlog includes pre-arm motion), CLI WARNs and falls back to the manual override.
|
||||
- **R-DEMO-2**: Pymavlink slow on multi-GB tlogs. **Mitigation**: stream-parse, never materialise; benchmark + document throughput floor.
|
||||
- **R-DEMO-3**: Demo footage missing required FC messages (HIL mode etc.). **Mitigation**: CLI fails fast at startup listing missing message types and the components that need them.
|
||||
- **R-DEMO-4**: Production C1–C5 paths bake real-time-cadence assumptions (e.g., 5 s fallback timer). **Mitigation**: `Clock` injection (wall-clock for live, tlog-derived for replay); documented as ADR amendment in next architecture-doc cycle.
|
||||
|
||||
### Effort
|
||||
|
||||
T-shirt M; 27–32 points across 8 child tasks.
|
||||
|
||||
### Child issues
|
||||
|
||||
| # | Title | Pts |
|
||||
|---|-------|-----|
|
||||
| 1 | `FrameSource` interface (cross-cutting) + `VideoFileFrameSource` strategy + `LiveCameraFrameSource` retrofit | 3 |
|
||||
| 2 | `TlogReplayFcAdapter` strategy (pymavlink stream parser → inbound DTOs) | 5 |
|
||||
| 3 | `ReplaySink` interface + `JsonlReplaySink` impl | 3 |
|
||||
| 4 | `compose_replay(config)` + `Clock` injection (per R-DEMO-4) | 3 |
|
||||
| 5 | `gps-denied-replay` CLI entrypoint + arg parser + camera-calibration loader | 3 |
|
||||
| 6 | `gps-denied-replay-cli` Dockerfile + GitHub Actions matrix entry + SBOM diff (excludes C6/C10/C11/C12) | 3 |
|
||||
| 7 | E2E replay fixture test (Derkachi 1–2 min clip + tlog; AC-3 ≤100 m ≥ 80 % assertion) | 5 |
|
||||
| 8 | Auto-sync of video ↔ tlog via IMU take-off detection (AC-7 / AC-8; `--time-offset-ms` remains the manual override) | 5 |
|
||||
|
||||
### Key constraints
|
||||
|
||||
- ADR-001 / ADR-002 / ADR-009.
|
||||
- C1–C5 components MUST remain mode-agnostic; replay-aware logic lives only in the composition root, the new strategies, and the CLI.
|
||||
- No HTTP server in any companion binary (airborne or replay); HTTP wrapper, if added later, lives in operator-tooling per `module-layout.md` Layer-4 placement.
|
||||
|
||||
### Testing strategy
|
||||
|
||||
Unit tests under `tests/unit/frame_source/`, `tests/unit/c8_fc_adapter/test_tlog_replay_adapter.py`, `tests/unit/c8_fc_adapter/test_replay_sink.py`, `tests/unit/cli/test_replay_cli.py`. E2E under `tests/e2e/replay/` running the CLI against the Derkachi fixture (Tier-1 capable; gated by `RUN_REPLAY_E2E=1` in CI). No FT/NFT scenarios at this epic — those live in E-BBT.
|
||||
|
||||
---
|
||||
|
||||
## Lessons applied (Step 6 step-0 retrospective)
|
||||
|
||||
`_docs/LESSONS.md` does not yet exist (this is the project's first cycle), so no prior estimation/architecture/dependencies lessons were folded into the sizing above. When this cycle ends, the Final step's quality checklist should propose a lessons file capturing:
|
||||
|
||||
Reference in New Issue
Block a user