# C3 ALIKED+LightGlue Secondary Matcher

**Task**: AZ-346_c3_aliked_lightglue
**Name**: C3 ALIKED+LightGlue Secondary Matcher
**Description**: Implement `AlikedLightGlueMatcher`, the secondary `CrossDomainMatcher`. Same architecture as `DiskLightGlueMatcher` (AZ-345) — DISK is replaced by ALIKED for the per-frame keypoint+descriptor extraction step; LightGlue + RANSAC stages are unchanged. Selectable via `config.matcher.strategy = "aliked_lightglue"`. ALIKED is the candidate alternative if D-C3-1 IT-12 verdict shifts away from DISK; until then it ships as the secondary path linked into airborne / research binaries (per ADR-002, both backbones can be linked; only one is selected at runtime).
**Complexity**: 3 points
**Dependencies**: AZ-344 (Protocol + factory + DTOs + errors + RollingHealthWindow), AZ-263_initial_structure, AZ-269_config_loader, AZ-278_lightglue_runtime, AZ-282_ransac_filter, AZ-298_c7_tensorrt_runtime, AZ-299_c7_onnxrt_fallback, AZ-303_c6_storage_interfaces, AZ-281_engine_filename_schema (ALIKED engine self-describing filename), AZ-321_c10_engine_compiler (ALIKED engine compile path), AZ-266_log_module, AZ-272_fdr_record_schema
**Component**: c3_matcher (epic AZ-257 / E-C3)
**Tracker**: AZ-346
**Epic**: AZ-257 (E-C3)

### Document Dependencies

- `_docs/02_document/contracts/c3_matcher/cross_domain_matcher_protocol.md` — Protocol contract (every invariant satisfied; mirrors AZ-345's contract behavior).
- `_docs/02_document/components/04_c3_matcher/description.md` — § 1 ALIKED secondary; § 5 same error handling; § 9 logging.
- `_docs/02_document/module-layout.md` — `c3_matcher` Per-Component Mapping (`aliked_lightglue.py` Internal); `BUILD_MATCHER_ALIKED_LIGHTGLUE` row.
- `_docs/02_document/contracts/shared_helpers/lightglue_runtime.md`.
- `_docs/02_document/contracts/shared_helpers/ransac_filter.md`.
- `_docs/02_document/contracts/c2_5_rerank/rerank_strategy_protocol.md`.
- `_docs/02_document/contracts/c7_inference/inference_runtime_protocol.md`.

## Problem

Without this task: D-C3-1 IT-12 evaluation has no comparison point against DISK; if a future cycle's IT-12 verdict shifts the production-default to ALIKED, the airborne binary cannot be re-configured without a new task; the ADR-002 build-time exclusion machinery is under-tested (only one matcher would exist). ALIKED is also the documented fallback if DISK's licensing or upstream maintenance changes mid-cycle.

## Outcome

- `src/gps_denied_onboard/components/c3_matcher/aliked_lightglue.py` defining:
  - `AlikedLightGlueMatcher` class implementing the `CrossDomainMatcher` Protocol.
  - Constructor identical shape to `DiskLightGlueMatcher` (AZ-345); the only differences are: ALIKED engine loaded instead of DISK, `matcher_label = "aliked_lightglue"`, ALIKED-specific preprocessor (resize / normalise per the upstream ALIKED contract).
  - `match` method: identical control flow to AZ-345's `match` — drop-and-continue, RANSAC + median residual, deterministic best-candidate selection, `RollingHealthWindow.update`, FDR `matcher.frame_done`. The ONLY difference is the keypoint+descriptor extraction step calls the ALIKED engine instead of DISK.
  - `health_snapshot()` delegates to the constructor-injected `RollingHealthWindow`.
  - Module-level `create(config, lightglue_runtime, ransac_filter, inference_runtime, health_window) -> CrossDomainMatcher`:
    1. `aliked_weights_path = config.matcher.aliked_weights_path` (TRT engine produced by AZ-321).
    2. Load ALIKED engine via `inference_runtime.load_engine(...)`.
    3. Construct `AlikedLightGlueMatcher(...)`.
- Composition-root wiring path for `config.matcher.strategy == "aliked_lightglue"`.
- `BUILD_MATCHER_ALIKED_LIGHTGLUE` flag wiring (per ADR-002): ON in airborne + research binaries; OFF in operator-tooling.
- ALIKED-specific preprocessor lives next to the strategy in the same module (NOT in `helpers/` — preprocessing parameters are weights-coupled per the same rule applied in AZ-337 / AZ-345).
- All logging + FDR records identical structure to AZ-345 with `matcher_label = "aliked_lightglue"`.

## Scope

### Included
- `AlikedLightGlueMatcher` implementation per the `CrossDomainMatcher` Protocol.
- ALIKED forward via C7 `InferenceRuntime`.
- LightGlue matching via shared helper.
- RANSAC + median residual via `RansacFilter`.
- Same drop-and-continue + below-threshold + best-candidate selection semantics as AZ-345.
- Same `RollingHealthWindow.update` invocation pattern.
- Composition-root wiring path.
- ALIKED-specific preprocessor inline.
- Unit tests covering Invariants 1–9 + drop-and-continue + below-threshold + deterministic ordering, parametrised so they share fixtures with AZ-345's tests where possible.
- `BUILD_MATCHER_ALIKED_LIGHTGLUE` flag wiring.

### Excluded
- The Protocol + DTOs + errors + factory + `RollingHealthWindow` — owned by AZ-344.
- `LightGlueRuntime` (AZ-278) and `RansacFilter` (AZ-282) helpers.
- C7 runtime stack (AZ-297..AZ-300).
- ALIKED engine compile (AZ-321).
- Component-internal acceptance tests beyond Protocol + invariants smoke: deferred to Step 9 / E-BBT.
- DISK matcher (AZ-345) and XFeat matcher (AZ-347).

## Acceptance Criteria

**AC-1 through AC-12**: identical contract to AZ-345 AC-1..AC-12 with `matcher_label = "aliked_lightglue"` and ALIKED-specific tile preprocessing. The Protocol invariants are the same; the implementation is the same modulo backbone. Tests parametrise across both backbones so any divergence is caught.

**AC-special-1: ALIKED engine output schema is asserted at `create` time**
Given a TRT engine whose ALIKED output dimensionality differs from the upstream-published value (e.g., descriptor_dim != expected)
When `AlikedLightGlueMatcher.create(...)` is called
Then `ConfigurationError` is raised with the offending shape; the strategy is NOT instantiated.

**AC-special-2: Strategy selection — `config.matcher.strategy == "aliked_lightglue"`**
Given the runtime composition with `config.matcher.strategy = "aliked_lightglue"` AND `BUILD_MATCHER_ALIKED_LIGHTGLUE = ON`
When `compose_root(config)` runs
Then an `AlikedLightGlueMatcher` is instantiated; ONE INFO log `kind="c3.matcher.ready"` with `{strategy: "aliked_lightglue", ...}` is emitted; `_lightglue_runtime` identity-equal to the runtime root's shared helper.

## Non-Functional Requirements

**Performance** (deferred validation to C3-PT-01):
- Same envelope as AZ-345: `match` p95 ≤ 180 ms; per-candidate ≤ 60 ms; GPU mem ≤ 800 MB.

**Compatibility**
- ALIKED engine file format owned by C10 + C7; consumed via `config.matcher.aliked_weights_path`.

**Reliability**
- Same as AZ-345: drop-and-continue, single-thread by contract, `InsufficientInliersError` triggers VIO-only fallback.

## Unit Tests

| AC Ref | What to Test | Required Outcome |
|--------|--------------|------------------|
| AC-1..AC-12 | Identical to AZ-345 AC-1..AC-12 with ALIKED label | Same outcomes; `matcher_label = "aliked_lightglue"` |
| AC-special-1 | ALIKED engine output shape mismatch | `ConfigurationError` at create time |
| AC-special-2 | `compose_root(config="aliked_lightglue")` | Wired; INFO log emitted; helper identity-shared |
| Parametrised drop-and-continue | Run AZ-345's drop-and-continue tests against ALIKED matcher fixture | Same drop-and-continue semantics |

## Constraints

- **Same constraints as AZ-345** — drop-and-continue mandatory, median residual, constructor injection, helpers constructor-injected, ALIKED engine load at `create` time, `RollingHealthWindow.update` called exactly once per `match`.
- **ALIKED-specific preprocessing parameters are hard-coded** — weights-coupled (same rule as DISK and UltraVPR); making them config-knobs would let an operator silently break the AC-1.1 inlier floor.
- **Both DISK and ALIKED engines may be linked into the same binary** — ADR-002 allows multiple backbones at link time; only `config.matcher.strategy` selects which is instantiated. NOT mutually exclusive at build time (operator-tooling excludes both via `BUILD_MATCHER_*` flags OFF).

## Risks & Mitigation

**Risk 1: ALIKED upstream code drop preprocessing differs from DISK in non-obvious ways**
- *Mitigation*: ALIKED preprocessor lives next to the strategy with hard-coded parameters; tests assert the preprocessor matches the upstream-published values; engine compile (AZ-321) consumes the same parameters.

**Risk 2: ALIKED's keypoint count distribution differs from DISK** (e.g., ALIKED returns more or fewer keypoints by default)
- *Mitigation*: LightGlue and RANSAC are agnostic to keypoint count distribution; the median residual + inlier count metrics are normalised. C3-IT-01 (deferred) measures this empirically.

**Risk 3: Switching from DISK to ALIKED at runtime requires a corpus rebuild**
- *Mitigation*: NO. C2's descriptor index (built by C10) is for VPR retrieval, not for cross-domain matching. C3 operates per-frame on raw tile pixels; switching matcher backbones does not require corpus rebuild. Documented in description.md § 8 (independent paths).

## Runtime Completeness

- **Named capability**: `AlikedLightGlueMatcher` — secondary `CrossDomainMatcher` (architecture / E-C3 / `solution.md` / AC-1.1 partition).
- **Production code that must exist**: real `AlikedLightGlueMatcher` calling real C7 `InferenceRuntime` with real TRT-compiled ALIKED engine; same shared `LightGlueRuntime` + `RansacFilter` + `RollingHealthWindow` invocation pattern as AZ-345.
- **Allowed external stubs**: same as AZ-345 — `FakeInferenceRuntime`, `FakeLightGlueRuntime`, `FakeRansacFilter`, `FakeFdrClient`.
- **Unacceptable substitutes**: same as AZ-345 — Python+NumPy ALIKED forward; per-strategy RANSAC; skipping `RollingHealthWindow.update` on all-failed path; using mean residual instead of median.
