# Common Helper — `DescriptorNormaliser`

## Purpose

L2-normalise descriptors so cosine similarity aligns with Euclidean distance — required because FAISS HNSW operates on Euclidean / inner-product spaces but the upstream backbones (UltraVPR, MegaLoc, MixVPR, etc.) emit raw cosine-similar embeddings. The same normalisation MUST be applied at both the **corpus** side (C10 during F1 provisioning) and the **query** side (C2 at runtime); centralising the helper guarantees they don't drift apart.

## Used By

- C2 — VPR (query side; per-frame embedding before FAISS lookup).
- C10 — Pre-flight Cache Provisioning (corpus side; per-tile embedding before FAISS index population).

## Interface (sketch)

```
class DescriptorNormaliser:
    @staticmethod
    def l2_normalise(descriptor: ndarray[D]) -> ndarray[D]
    @staticmethod
    def l2_normalise_batch(descriptors: ndarray[N, D]) -> ndarray[N, D]
    @staticmethod
    def descriptor_metric() -> str                        # always "inner_product" — for FAISS index config
```

## Implementation Notes

- Stateless; pure function.
- Backed by numpy / numpy-CUDA depending on the runtime; preserves dtype (fp16 in/out → fp16 out, fp32 in/out → fp32 out).
- The `descriptor_metric()` static return is the source of truth for the FAISS HNSW index distance metric — both C6's `DescriptorIndex.search_topk` and C10's index-build code consult it.

## Caveats

- Zero-norm vectors are returned as the zero vector (no division-by-zero); callers must filter or accept that such descriptors will match nothing.
- The choice of "inner product on L2-normalised" rather than "cosine" is FAISS-idiomatic — FAISS does not have a built-in cosine metric; cosine is achieved by L2-normalising and using inner product.
