# Test Specification — C1 Visual / Visual-Inertial Odometry

This file is component-scoped. AC-level coverage is in the suite-level test docs (`_docs/02_document/tests/*.md`) and the canonical traceability map is `_docs/02_document/tests/traceability-matrix.md`. The tables below cite test IDs from those files; the `Component-Internal Tests` section adds C1-specific unit/contract tests that the suite-level scenarios do not cover.

## Acceptance Criteria Traceability

| AC ID | Acceptance Criterion (one-line) | Test IDs (suite-level + this file) | Coverage |
|-------|---------------------------------|-----------------------------------|----------|
| AC-1.3 | Cumulative drift between satellite-anchored fixes <100 m visual / <50 m IMU-fused | FT-P-02, **C1-IT-01** | Covered |
| AC-1.4 | Estimate reports 95% covariance + source label | FT-P-03, **C1-IT-02** | Covered |
| AC-2.1a | Frame-to-frame registration ≥95% on normal segments | FT-P-04, **C1-IT-03** | Covered |
| AC-2.2 (frame-to-frame portion) | MRE <1 px frame-to-frame | FT-P-05, **C1-IT-04** | Covered |
| AC-3.2 | Tolerate sharp turns; recovery via satellite re-loc | FT-P-07, FT-N-02 | Covered (C1 contributes track-loss detection) |
| AC-4.1 | E2E latency <400 ms p95 | NFT-PERF-01 (Tier-2), **C1-PT-01** | Covered |
| AC-5.1 | Init from FC EKF's last valid GPS + IMU-extrapolated | FT-P-11, **C1-IT-05** | Covered |
| AC-5.3 | On reboot, re-init from FC IMU-extrapolated pose | NFT-RES-02, **C1-IT-06** | Covered |

---

## Component-Internal Tests

### C1-IT-01: VioStrategy contract — `process_frame` honest covariance under degradation

**Summary**: every concrete `VioStrategy` implementation (Okvis2, VinsMono, KltRansac) must produce a 6×6 covariance whose norm grows monotonically when feature tracking degrades.

**Traces to**: AC-1.3, AC-1.4

**Description**: feed each strategy a synthetic 60 s nav-camera + IMU sequence with a controlled feature-loss event at t=30 s (50% feature drop). Assert that `pose_covariance_6x6` Frobenius norm before t=30 s is below the steady-state threshold and rises monotonically for ≥3 s after the event. No strategy may emit a tightened covariance during a degradation event (catches honest-covariance-violation regressions).

**Input data**: `tests/fixtures/synthetic_vio/normal_then_feature_drop_60s/` (nav-cam frames at 3 Hz + IMU at 200 Hz; feature-loss event injected via image masking).

**Expected result**: `||cov||_F` curve has a rising shoulder ≥1.5× steady-state norm within 3 s of the event for all three strategies; `VioHealth.state` transitions `TRACKING → DEGRADED` within the same window.

**Max execution time**: 30 s per strategy on Tier-1.
**Dependencies**: helper `ImuPreintegrator` (shared with C5).

---

### C1-IT-02: `VioOutput` schema invariants

**Summary**: every `VioOutput` carries a 6×6 SPD covariance and a non-empty `feature_quality`.

**Traces to**: AC-1.4

**Description**: drive each strategy through 100 frames of a synthetic loop; for each emitted `VioOutput`, assert (a) `pose_covariance_6x6` is symmetric and positive-definite, (b) `feature_quality.tracked + new ≥ 0`, (c) `frame_id` matches the input `NavCameraFrame.frame_id`. Any single violation fails the test.

**Input data**: `tests/fixtures/synthetic_vio/loop_100f/`.

**Expected result**: 100/100 frames pass all invariants per strategy.

**Max execution time**: 10 s.

---

### C1-IT-03: KltRansac mandatory simple-baseline registration ≥95%

**Summary**: the simple-baseline strategy must hit AC-2.1a's 95% threshold on the Derkachi normal segment so the engine rule's mandatory baseline is met.

**Traces to**: AC-2.1a (engine rule)

**Description**: replay the Derkachi normal-segment fixture (the same 60 stills C8 fixture that FT-P-04 uses) through the KltRansac strategy only; count frames with `VioHealth.state == TRACKING` at emission. Pass if ≥95%.

**Input data**: `tests/fixtures/flight_derkachi/normal_segment_60_stills/`.

**Expected result**: tracked frame ratio ≥ 0.95.

**Max execution time**: 60 s.

---

### C1-IT-04: Frame-to-frame MRE bound

**Summary**: each strategy's per-frame mean reprojection error stays under 1 px on normal segments.

**Traces to**: AC-2.2 (frame-to-frame portion)

**Description**: same Derkachi normal-segment fixture as C1-IT-03. Compute MRE per frame from the strategy's internal residual; assert MRE p95 < 1 px per AC-2.2.

**Input data**: as above.

**Expected result**: MRE p95 < 1 px for Okvis2 + KltRansac (production-default + simple-baseline). VinsMono is research-only and exempt from MRE bound (only IT-12 comparative-study coverage).

**Max execution time**: 60 s.

---

### C1-IT-05: Warm-start from `WarmStartPose` converges within configured budget

**Summary**: `reset_to_warm_start` followed by 5 frames of input must converge to `state == TRACKING` for every strategy.

**Traces to**: AC-5.1

**Description**: prepare a `WarmStartPose` derived from the FC EKF's last-valid-GPS fixture; call `reset_to_warm_start`, then push 5 frames of normal-segment input; assert health transitions `INIT → TRACKING` within 5 frames.

**Input data**: `tests/fixtures/flight_derkachi/takeoff_warmstart/`.

**Expected result**: TRACKING by frame 5 for all three strategies.

**Max execution time**: 5 s.

---

### C1-IT-06: F8 reboot recovery via warm-start hint

**Summary**: simulate a mid-flight reboot — the strategy must re-init from the warm-start hint without crashing or producing covariance < pre-reboot.

**Traces to**: AC-5.3

**Description**: run normal-segment input for 30 s; capture last `VioOutput`; reset the strategy with that pose as `WarmStartPose`; resume input; assert next 5 emitted `VioOutput` have `pose_covariance_6x6` Frobenius norm ≥ the pre-reboot value (no fake confidence after reboot).

**Input data**: as C1-IT-03.

**Expected result**: pass per assertion above for all three strategies.

**Max execution time**: 60 s.

---

## Performance Tests

### C1-PT-01: per-frame latency budget on Tier-2

**Summary**: `process_frame` p95 latency on Jetson under nominal thermal.

**Traces to**: AC-4.1 (component-level partition; suite-level NFT-PERF-01 owns the e2e budget).

**Load scenario**:
- Single ingest thread, 3 Hz frame rate, 10 min replay of Derkachi normal segment.
- Concurrent C2 backbone forward pass running on the same Jetson (realistic load contention).

**Expected results**:

| Metric | Target | Failure Threshold |
|--------|--------|-------------------|
| `process_frame` latency p50 | ≤ 25 ms (Okvis2 production-default) | 60 ms |
| `process_frame` latency p95 | ≤ 80 ms | 120 ms |
| Throughput | ≥ 3 Hz sustained | < 2.5 Hz |

**Resource limits**:
- CPU: ≤ 30% of one core (Okvis2 is multi-threaded internally; bound at 30% per ADR-002 budget partition).
- Memory: ≤ 1.5 GB resident.

---

## Security Tests

C1 has no externally-reachable surface (internal-only component); suite-level NFT-SEC-02 (no in-flight egress) and NFT-SEC-05 (DNS blackholing) cover the airborne process broadly. No C1-specific security tests.

---

## Acceptance Tests

C1 contributes to AC-1.3 / 1.4 / 2.1a / 5.1 / 5.3 via the suite-level FT scenarios cited in the traceability table. No additional C1-only acceptance tests are needed.

---

## Test Data Management

**Required test data**:

| Data Set | Description | Source | Size |
|----------|-------------|--------|------|
| `synthetic_vio/normal_then_feature_drop_60s/` | 60 s nav-cam + IMU with controlled feature-loss event at t=30 s | generated by `scripts/gen_synthetic_vio.py` (deterministic seed) | ~50 MB |
| `synthetic_vio/loop_100f/` | 100-frame synthetic closed loop for invariant checks | generated, deterministic | ~30 MB |
| `flight_derkachi/normal_segment_60_stills/` | the project's canonical normal-segment fixture | curated subset of Derkachi raw drop | ~80 MB |
| `flight_derkachi/takeoff_warmstart/` | last-valid-GPS + IMU window from FC EKF for warm-start tests | recorded once, replayed | ~5 MB |

**Setup procedure**:
1. Run `scripts/gen_synthetic_vio.py` once per fixture to populate `tests/fixtures/synthetic_vio/`.
2. Mount `tests/fixtures/flight_derkachi/` from the project's data archive (read-only).

**Teardown procedure**:
1. Synthetic fixtures persist between runs (deterministic; no per-run mutation).
2. The Derkachi fixture is read-only; nothing to clean up.

**Data isolation strategy**: every test runs in its own temp directory under `tests/tmp/c1/<test-id>/`; per-strategy state is constructed fresh in each test (no shared state across tests).
