# C3.5 — AdHoP-conditional Refinement

## 1. High-Level Overview

**Purpose**: conditionally refine `MatchResult` via OrthoLoC AdHoP (method-agnostic perspective preconditioning) when the initial reprojection residual exceeds a configurable threshold; bypass otherwise. The conditional invocation preserves the AC-4.1 latency budget on the steady-state path while keeping the refinement option for hard frames.

**Architectural Pattern**: Strategy with two concrete implementations: `AdHoPRefiner` (real refinement) and `PassthroughRefiner` (no-op for the non-conditional baseline / smoke tests). Selection at startup by config (ADR-001); both implementations linked into the deployment binary by default (refinement is conditionally invoked at runtime, not gated at build time).

**Upstream dependencies**:
- C3 → `MatchResult`.
- C7 InferenceRuntime — AdHoP backbone forward pass when invoked.

**Downstream consumers**:
- C4 PoseEstimator (consumes the possibly-refined `MatchResult`).

## 2. Internal Interfaces

### Interface: `ConditionalRefiner`

| Method | Input | Output | Async | Error Types |
|--------|-------|--------|-------|-------------|
| `refine_if_needed` | `NavCameraFrame, MatchResult, residual_threshold_px: float` | `MatchResult` (possibly enriched with refined inliers) | No | `RefinerBackboneError` |
| `was_invoked` | `()` | `bool` — last-call flag for FDR provenance | No | — |

**Input DTOs**:
```
NavCameraFrame:                see C1
MatchResult:                   see C3
```

**Output DTOs**:
```
MatchResult (refined):
  Same shape as C3's MatchResult, with the following enrichments when refinement was invoked:
    per_candidate[i].inlier_correspondences:    refined coordinates from AdHoP perspective preconditioning
    per_candidate[i].per_candidate_residual_px: post-refinement residual
    refinement_label:                            "adhop" | "passthrough"
    refinement_added_latency_ms:                 float — for FDR latency partition
```

## 3. External API Specification

Not applicable.

## 4. Data Access Patterns

| Query | Frequency | Hot Path | Index Needed |
|-------|-----------|----------|--------------|
| Nav-frame pixel access (1 frame per invocation) | conditional, <3 Hz | When invoked | already in memory from C1/C2 path |

No additional caching.

## 5. Implementation Details

**Algorithmic Complexity**: AdHoP perspective preconditioning is `O(F)` in feature count plus one extra backbone forward pass. Steady-state cost is zero (passthrough); worst-case adds ~30–90 ms per invocation on the Jetson.

**State Management**: stateless per-frame.

**Key Dependencies**:

| Library | Version | Purpose |
|---------|---------|---------|
| OrthoLoC AdHoP (research code drop) | upstream HEAD pinned per Plan-phase | Conditional refinement |
| OpenCV | ≥ 4.12.0 | Reprojection residual computation, perspective transforms |
| TensorRT | matches C7 | AdHoP backbone engine when invoked |

**Error Handling Strategy**:
- `RefinerBackboneError`: AdHoP backbone failure. Fall through to passthrough — emit C3's original `MatchResult` unchanged; downstream pose estimation may fail subsequent quality gates and trigger F6 satellite re-localization.
- The conditional gate is a configuration parameter (`adhop.residual_threshold_px`); no runtime decision logic outside that comparison.

## 6. Extensions and Helpers

| Helper | Purpose | Used By |
|--------|---------|---------|
| `RansacFilter` | shared with C3 | C3, C3.5, C4 |

## 7. Caveats & Edge Cases

**Known limitations**:
- The threshold is a tunable; setting it too low triggers AdHoP every frame and breaks the AC-4.1 budget; too high lets bad matches through to C4. Calibration via NFT-PERF-01 invocation-rate measurement during planning's NFT validation cycle.
- D-CROSS-LATENCY-1 hybrid does not directly change C3.5's behaviour; under thermal throttle, the elevated frame-to-frame latency may push the threshold to be raised at the operator-tooling level pre-flight (C12 concern).

**Potential race conditions**:
- Stateless; concurrent calls are safe in principle but the hot path is single-threaded by design.

**Performance bottlenecks**:
- AdHoP invocation is the variable cost in the F3 budget. NFT-PERF-01 measures the invocation rate; an invocation rate above ~30% suggests the threshold needs revisiting.

## 8. Dependency Graph

**Must be implemented after**: C3 (input), C7 (inference runtime).

**Can be implemented in parallel with**: C1, C6 — independent paths.

**Blocks**: C4, F3 / F6.

## 9. Logging Strategy

| Log Level | When | Example |
|-----------|------|---------|
| ERROR | `RefinerBackboneError` | `AdHoP backbone failure; frame=12345; falling through to passthrough` |
| WARN | invocation rate over rolling 60 s exceeds budget | `AdHoP invocation rate 38% > target 25%; frame=12345` |
| INFO | Strategy ready | `Refiner ready: strategy=adhop, threshold=2.5px` |
| DEBUG | per-frame invoked/passthrough decision | `Refiner frame=12345 invoked=true added_latency_ms=42` |

**Log format**: structured JSON.
**Log storage**: stdout / journald / FDR via C13 (ERROR + WARN only). DEBUG goes to FDR's per-frame estimate stream as a flag (`refinement_label`).
