# Matcher v1 et Match Store v1 ## Contrat Le pipeline v1 est `ORB → BFMatcher Hamming` ou `SIFT/RootSIFT → BFMatcher L2`, puis KNN `k=2`, Lowe ratio, tri canonique, Match File content-addressed et Match Result. Il s'arrête avant toute vérification géométrique. Pour chaque descripteur A, OpenCV fournit zéro, un ou deux voisins. Zéro ne produit rien. Un seul voisin valide est accepté. Avec deux voisins, le premier est accepté exactement si `d1 < threshold * d2`; une seconde distance nulle, une égalité, NaN, Inf ou une distance négative est rejetée. `-0.0` est accepté comme zéro conformément à IEEE-754. Indices invalides et distances du premier voisin non finies ou négatives sont rejetés. Un `feature_index_a` produit donc au maximum une correspondance acceptée. Sans cross-check, plusieurs indices A peuvent viser le même index B. L'ordre final est `(feature_index_a, distance, feature_index_b)` croissant. BFMatcher fournit une liste KNN par query et le filtre ne conserve que son premier voisin : la déduplication est donc mathématiquement inutile. Le Matcher rejette toute sortie anormale contenant deux fois le même index A. ## Match File v1 Header fixe de 32 octets, tous les entiers en little-endian : ``` 0..3 octets ASCII exacts "L3DM" 4 format_version = 1 5 descriptor_type (1=U8, 2=F32) 6..7 reserved = 0 8..11 match_count uint32 12..15 descriptor_dimension uint32 (32 ou 128) 16..23 feature_set_id_a uint64 24..31 feature_set_id_b uint64 ``` Chaque entrée fait 12 octets : index A uint32, index B uint32, distance float32. Le format accepte zéro entrée pour tester reader/writer, mais le Matcher ne publie pas d'asset vide. La borne est 8192 entrées et la taille maximale exacte est `32 + 8192 * 12 = 98336` octets, environ 96 Kio. Le reader valide avant toute allocation : magic physique, version, reserved, type/dimension, borne du compte, calcul de taille, taille physique exacte, Feature Set IDs A/B sans permutation, indices contre les comptes attendus et distance finie non négative. Fichiers tronqués, trailing bytes et versions futures sont rejetés. ## Publication et reuse Pour `NO_MATCH`, le fichier temporaire est supprimé et seule une ligne sans asset est créée. Pour `MATCHED` : temp dans `assets/matches`, écriture complète, `fsync` du fichier, SHA-256, chemin `assets/matches//`, puis publication atomique par `link`. Une race de même contenu est validée; un contenu différent déjà présent n'est jamais écrasé lors d'une publication fraîche. Lorsqu'une ligne existante prouve que son asset content-addressed est corrompu, le Matcher recalcule puis remplace atomiquement cet asset par `rename` et répare les métadonnées de la même ligne sous transaction. Le temp est nettoyé et le répertoire final synchronisé. Le reuse `MATCHED` exige métadonnées complètes, fichier régulier non symbolique, taille, SHA-256, header, type/dimension, IDs A/B, compte et entrées valides. Il n'existe aucun fallback faisant confiance au chemin ou à l'existence seuls. La validation de reuse lit le fichier borné une seule fois et calcule le SHA-256 sur ce même buffer. ## Bornes mémoire et performance Les deux buffers de descripteurs contigus occupent au maximum 512 Kio pour ORB ou 8 Mio pour SIFT/RootSIFT. Les résultats KNN ne contiennent que deux DMatch par query, les matches filtrés et le Match File sont chacun bornés à environ 96 Kio. Le working set contrôlé du Matcher est donc inférieur à environ 10 Mio, hors scratch interne borné par OpenCV; aucune structure `A × B` n'est matérialisée. Le Match File complet est sérialisé dans un buffer heap borné à 98336 octets et écrit par un unique `write_exact`, puis synchronisé une fois. Les mesures locales restent dans le rapport de session, pas dans ce contrat canonique. À 8192 features, le coût CPU dominant reste l'évaluation exacte des distances dans `cv::BFMatcher::knnMatch`. ORB peut remplacer ce seul hot path par Vulkan. Une feasibility réelle a rejeté Vulkan pour SIFT et RootSIFT : leur accumulation flottante ne garantit pas le top-2 OpenCV sur les égalités adversariales et le gain n'est présent que sur les grandes paires carrées. Ils restent intégralement sur BFMatcher CPU. **IMPLEMENTATION IN PROGRESS — parallélisme interne P4.** `matcher.run` v1 orchestre le Matcher sans connaître son backend interne. La tâche persiste uniquement la configuration, l'identité des Feature Sets à sélectionner et un curseur Candidate Pair. Une paire est atomique et publiée immédiatement. Les lots 1/2/4/8 sont séparés par checkpoint et `task_sequence_break()`. La tâche utilise l'unique Resource Governor avec une estimation couvrant ce working set ; aucune seconde logique de budget n'est introduite. Dans un lot, des fenêtres bornées calculent au plus deux paires par thread CPU effectivement admis, avec un plafond de huit. Le callback Queue compte comme un participant, joint au plus `cpu_threads - 1` enfants, puis publie seul les stages en ordre `candidate_pair_id`. OpenCV reste à un thread interne pendant cette séquence, ce qui interdit un fan-out BFMatcher imbriqué. Le curseur ne suit que le préfixe contigu durable ; le premier échec scelle tout le suffixe calculé. Sa ligne durable `matcher_tasks` appartient au schéma Project DB v11 ; le Match Result reste le contrat publié en v10. Project DB v12 ajoute un enfant `Geometric Verification Result` référencé par `match_result_id`. Le Matcher ne calcule, ne stocke et ne valide aucun inlier géométrique ; seul son Match File canonique définit l'ordre indexé par le masque. ### Backend Vulkan ORB La frontière évaluée remplace uniquement KNN Hamming par un compute top-2 : un thread GPU par feature A parcourt B, conserve deux indices/distances et applique le tie-break du plus petit index. Elle ne matérialise jamais A×B. Lowe, canonicalisation et persistance restent communs. Sur Radeon 780M, la parité top-2 avec OpenCV est exacte et le gain warm est supérieur à 90 % à 4096/8192. Le backend de production conserve exactement cette frontière. Sa parité entière permet au CPU et à Vulkan de partager l'identité persistante. La sélection est une politique runtime explicite ; le CPU reste le fallback portable. Les APIs historiques de création/soumission choisissent toujours le mode CPU parallèle. Les variantes additives `*_with_mode` permettent de demander ORB Vulkan avant l'admission ; elles refusent SIFT/RootSIFT, un profil sans GPU sélectionné ou un backend absent. Ce mode immutable demande exactement CPU 1, GPU 1 et 640 Kio GPU/UMA. Un échec Vulkan pendant une exécution déjà admise reprend le top-2 CPU exact sans changer fingerprint ni résultat. Le contrat détaillé est décrit dans [vulkan_matcher.md](vulkan_matcher.md). La reprise reconnaît uniquement quatre estimations complètes : CPU8/GPU0 et CPU1/GPU1/640 Kio courantes, plus CPU12/GPU0 et CPU12/GPU1/640 Kio historiques. Les deux formes CPU12 sont normalisées éphémèrement vers leur forme courante avant admission, sans checkpoint d'estimation seule. Une forme voisine est rejetée ; aucun champ isolé ne sert à deviner le backend. ## Déterminisme et fingerprint Le fingerprint couvre kind, version, `k=2`, threshold float32 et absence de cross-check. À Feature Files, configuration, implémentation/version OpenCV et environnement numérique compatibles identiques, SIFT/RootSIFT garantissent les mêmes paires attendues, l'ordre et la sérialisation stables. Aucune promesse bit-à-bit cross-platform n'est faite pour les distances L2 flottantes. ORB/Hamming bénéficie d'une garantie plus forte grâce à sa distance entière. NEXT: GEOMETRIC VERIFIER v3