From a3a005b38e8a09d49e694eb4462dc8b3921729a5 Mon Sep 17 00:00:00 2001 From: fy59 Date: Wed, 2 Sep 2026 22:30:33 +0200 Subject: [PATCH] docs: reconcile current reconstruction architecture --- docs/architecture/geometric_verification.md | 474 +++-- docs/architecture/geometric_verifier.md | 836 ++++----- docs/architecture/runtime.md | 703 +++++--- docs/architecture/sparse_sfm.md | 1802 +++++-------------- docs/architecture/tracks.md | 756 ++++---- docs/architecture/visual_index.md | 559 ++++-- 6 files changed, 2407 insertions(+), 2723 deletions(-) diff --git a/docs/architecture/geometric_verification.md b/docs/architecture/geometric_verification.md index 20b332e..330f77f 100644 --- a/docs/architecture/geometric_verification.md +++ b/docs/architecture/geometric_verification.md @@ -1,180 +1,408 @@ -# Geometric Verification +# Geometric Verification Model -## Scope - -Geometric Verification Model est le contrat persistant placé après le Matcher. -Sa représentation stocke les identités scientifiques Geometric Verifier v1/v2 -historiques et v3 courantes, sans changement de schéma : `verifier_version` et -`parameter_fingerprint` appartiennent déjà à l'identité exacte. Il stocke un -résultat terminé, compact et immutable. Il n'est ni un moteur de calcul ni une -tâche. -Aucun RANSAC, USAC, MAGSAC, calcul d'inliers ou backend géométrique n'appartient à ce ticket. - -## Position in reconstruction pipeline - -La chaîne d'ownership est : +## Status ```text -Feature Set → Candidate Pair → Match Result → Geometric Verification Result +GEOMETRIC_VERIFICATION_MODEL=IMPLEMENTED +PROJECT_DB_GEOMETRIC_VERIFICATION=v12 + +HISTORICAL_VERIFIER_V1=VALID +HISTORICAL_VERIFIER_V2=VALID +CURRENT_PRODUCTION_VERIFIER_V3=FROZEN + +CURRENT_PROJECT_DB_SCHEMA=v25 +REAL_S21_GV_V3=PASS/FROZEN +REAL_A6000_PRE_SFM=PASS/FROZEN ``` -Le masque indexe exclusivement l'ordre des entrées du Match File canonique du Match Result. Il -n'indexe directement ni les features, ni la Candidate Pair, ni un ordre temporaire de backend. +This document owns the **persistent Geometric Verification Result model**. -## Scientific ownership +It does not own the numerical estimator implementation. The current executable scientific verifier is +documented in `geometric_verifier.md`. -Le parent scientifique est `match_result_id`. L'API accepte uniquement un Match Result existant, -`MATCHED`, avec `match_count` strictement positif. `NO_MATCH` et les erreurs runtime ne peuvent pas -produire de résultat géométrique. +The persistence model was deliberately version-ready from Project DB v12: `verifier_version` and +`parameter_fingerprint` already belong to exact result identity. Therefore historical verifier v1/v2 +and current v3 results coexist without a schema reinterpretation. -## Parent Match Result +## Pipeline position -Le Match Store reste propriétaire de la validation du Match File. La création consulte le parent -et son `match_count` en DB ; elle ne relit pas l'asset. Un load valide aussi l'existence et l'état du -parent afin qu'une ligne corrompue ne soit jamais rendue comme résultat valide. +```text +Feature Set +-> Candidate Pair +-> Match Result +-> Geometric Verification Result +-> Track Builder +-> Track Model +``` + +The inlier mask indexes the canonical Match File entry order. + +It does not directly index: + +- Feature Store physical order; +- Candidate Pair order; +- temporary backend order. + +## Scientific parent + +The exact parent is: + +```text +match_result_id +``` + +A Geometric Verification Result may be created only for a valid `MATCHED` parent with positive +`match_count`. + +`NO_MATCH` and runtime failures do not produce a scientific geometric result. ## Persistent identity -L'identité demandée et unique est : +Exact identity: ```text -(match_result_id, verifier_kind, verifier_version, parameter_fingerprint) +( + match_result_id, + verifier_kind, + verifier_version, + parameter_fingerprint +) ``` -Le fingerprint est le SHA-256 opaque de 32 octets déjà standard dans le projet. Il représentera -un encodage de paramètres versionné, stable, à ordre de champs explicite et, pour les nombres -binaires, little-endian. Aucun timestamp, résultat, PID, durée ou identifiant matériel n'y entre. +No selection by timestamp or "latest" is permitted. + +The fingerprint is an opaque canonical SHA-256 scientific parameter identity. + +It excludes: + +- Task ID; +- PID; +- elapsed time; +- CPU count; +- batch size; +- GPU identity; +- hardware identity. ## Verifier kind -Le modèle supporte uniquement `FUNDAMENTAL`, valeur persistante stable 1. Aucun comportement fictif -`ESSENTIAL` ou `HOMOGRAPHY` n'est réservé dans l'API publique. +The persistent supported model kind is: -## Persistent states +```text +FUNDAMENTAL = 1 +``` -- `GEOMETRIC_REJECTED=1` : calcul scientifique terminé, critère non satisfait ; -- `GEOMETRIC_VERIFIED=2` : calcul scientifique terminé, critère satisfait. +Do not reserve fictitious `ESSENTIAL` or `HOMOGRAPHY` values in prose without an explicit versioned +implementation decision. -`FAILED`, `RUNNING`, `PAUSED` et `CANCELLED` appartiennent au Task Runtime. REJECTED peut conserver -un nombre d'inliers non nul. +## Scientific states -## Model representation +Completed scientific states are: -FUNDAMENTAL utilise neuf colonnes SQLite `REAL`, en ordre ligne-major `m00` à `m22`. SQLite -convertit les valeurs numériques en binary64 sans exposer une ABI C. VERIFIED exige les neuf -valeurs présentes et finies. REJECTED exige les neuf valeurs NULL. Le modèle n'impose ni rang 2, -ni déterminant, ni normalisation ou échelle canonique ; ces règles relèvent du futur verifier. +```text +GEOMETRIC_REJECTED = 1 +GEOMETRIC_VERIFIED = 2 +``` -## Inlier representation +Runtime states such as RUNNING, FAILED, PAUSED or CANCELLED belong to Task Runtime, not this model. -Le masque est un BLOB SQLite obligatoire de taille exacte `ceil(match_count / 8)`. Pour l'entrée -`i`, `byte_index=i/8`, `bit_index=i%8` et le masque vaut `1u << bit_index`. Le bit 0 est donc le bit -de poids faible de l'octet 0. Cette convention est indépendante de l'endianness CPU et de l'ABI. -Les bits de padding du dernier octet valent zéro et le popcount est exactement `inlier_count`. +A rejected result may still contain non-zero inlier support. -Le masque existe pour REJECTED comme pour VERIFIED. Avec 8192 matches, il mesure au maximum -1024 octets. Un BLOB SQLite évite les milliers de lignes secondaires et la publication, le hash, -le nettoyage et la récupération d'un asset externe d'environ 1 Kio. Une liste `uint32_t` serait -jusqu'à 32 fois plus grande au cas dense et aurait un encodage supplémentaire à versionner. +## Fundamental matrix representation -## Invariants +A verified Fundamental result contains nine SQLite `REAL` values: -- `0 <= inlier_count <= parent.match_count <= 8192` ; -- longueur, padding et popcount du masque sont canoniques ; -- REJECTED possède un masque cohérent et aucun modèle ; -- VERIFIED possède un masque cohérent et exactement neuf valeurs finies ; -- kind, version et fingerprint ont une sérialisation stable ; -- une ligne publiée est complète et immutable. +```text +m00 ... m22 +``` -Exemple : pour 100 matches, une identité FUNDAMENTAL v1, v2 ou v3/fingerprint -X peut publier REJECTED avec 23 inliers, un masque de 13 octets et aucun modèle. -Une autre identité peut publier VERIFIED avec 67 inliers, le même format de -masque et une matrice 3×3 finie. +in row-major order. -## Persistence semantics +The persistent representation is binary64 through SQLite numeric semantics, not a C ABI struct dump. -Une création valide puis insère identité, état, masque et modèle dans une transaction courte. Le -calcul futur se fera entièrement avant cette transaction. SQLite fournit l'atomicité ; aucun asset -ou journal secondaire n'est créé. +A verified row requires nine finite values. + +A rejected row contains no model. + +Rank/canonicalization/scientific-estimator rules belong to the versioned verifier contract. + +## Inlier mask + +The mask is a required SQLite BLOB of exact size: + +```text +ceil(match_count / 8) +``` + +Bit convention for Match File entry `i`: + +```text +byte = i / 8 +bit = i % 8 +mask[byte] & (1u << bit) +``` + +The mask is LSB-first inside each byte. + +Padding bits in the final byte are zero. + +The mask popcount must equal `inlier_count`. + +The mask exists for both verified and rejected scientific results. + +With the current Match File bound of 8192 matches, the mask is at most 1024 bytes. + +## Persistent invariants + +For every row: + +```text +0 <= inlier_count <= parent.match_count <= 8192 +mask size is canonical +padding bits are zero +mask popcount == inlier_count +``` + +Additionally: + +```text +REJECTED -> no Fundamental matrix +VERIFIED -> exactly nine finite matrix coefficients +``` + +A published row is immutable. + +## Publication + +Numerical estimation completes before the short Project DB publication transaction. + +Publication inserts: + +- exact parent; +- exact verifier identity; +- completed state; +- canonical mask; +- optional verified Fundamental model. + +No external asset is required because the bounded mask/model fit naturally in SQLite. + +Rollback leaves no partial scientific result. ## Reuse -Le reuse cherche uniquement l'identité exacte, jamais le résultat le plus récent. Une identité -existante retourne une erreur de contrainte à `create`; le runtime fera `find`, validera puis -réutilisera. `INSERT OR REPLACE` est interdit, même si le nouveau contenu semble identique. +Exact reuse uses only the full persistent identity. -## Invalidations +Existing exact result: -Un nouveau Match Result possède un nouvel ID et ne réutilise donc aucun ancien résultat -géométrique. La FK emploie `ON DELETE CASCADE` : supprimer explicitement le parent supprime ses -enfants et ne crée pas d'orphelin. Aucun moteur d'invalidation parallèle n'est nécessaire. +```text +find +-> validate +-> reuse +``` -## Project DB schema +Never: -Project DB v12 ajoute `geometric_verification_results`, une contrainte UNIQUE sur l'identité et un -index de pagination `(match_result_id, geometric_verification_result_id)`. Les CHECK SQL portent -les bornes scalaires, tailles locales et nullabilité modèle/état. La cohérence avec le parent, le -padding, le popcount et la finitude restent validés en C. +```text +INSERT OR REPLACE +latest result +closest fingerprint +same parent with different version +``` -## API +A new scientific verifier version creates another result identity. -L'API publique implémente : +## Parent deletion -- `lardon3d_project_db_create_geometric_verification_result()` ; -- `lardon3d_project_db_load_geometric_verification_result()` ; -- `lardon3d_project_db_find_geometric_verification_result()` ; -- `lardon3d_project_db_list_geometric_verification_results()`. +The parent FK uses delete-cascade semantics. -La liste est bornée à 256 entrées, filtrée par parent puis ordonnée par ID croissant avec curseur. -Le résultat en mémoire contient son `created_at` et son masque dans une capacité fixe de 1024 -octets : aucun ownership dynamique ni fonction de destruction. Les fonctions copient fingerprint, -masque et neuf coefficients ; l'appelant conserve ses entrées. +Explicit deletion of a Match Result deletes its dependent geometric results. -Parent absent retourne `NOT_FOUND`; parent NO_MATCH ou parent incohérent retourne `CONSTRAINT` à -la création. Masque, modèle ou arguments locaux invalides retournent `INVALID_ARGUMENT`; duplicate -identity retourne `CONSTRAINT`. Un loader qui rencontre une ligne ou un parent incohérent retourne -`CORRUPT`, sans résultat partiel. +No parallel invalidation engine is required. -## Resource bounds +## Schema -Un résultat contient au plus 1024 octets de masque et 72 octets de valeurs numériques, plus de -petites métadonnées. Une page est bornée. Le loader vérifie les entiers et tailles SQLite avant -tout cast ou copie. Il n'existe ni cache global, ni lecture non bornée, ni Content Store associé. -Le Match File parent mesure au plus 98 336 octets ; le futur job peut donc rester une petite unité. +Project DB v12 introduced `geometric_verification_results`. -## Error ownership +The current schema head is v25. -Seuls les résultats scientifiques terminés sont persistés. OOM, exception, annulation, timeout, -device lost, I/O transitoire ou panne de thread appartiennent à l'exécution de tâche. État du modèle -et état d'exécution sont deux contrats distincts. +Later schema additions do not redefine the v12 row format or identity. -## Recovery semantics +## Public API -Après commit, le résultat est complet et réutilisable après réouverture. Avant commit, le rollback -ne laisse aucune ligne partielle. Un loader rejette toute ligne incohérente comme corruption au -lieu de réparer ou d'interpréter au mieux. +The model provides bounded create/load/find/list APIs for Geometric Verification Results. -## Verifier execution contract +The list API is paged and ordered by increasing ID. -L'exécution prend un Match Result et son Match File borné. L'accès nécessaire existe via -`lardon3d_feature_reader_keypoints()`, borné à 256 keypoints par appel ; l'intégration devra relier -les deux Feature Sets et les indices du Match File sans modifier le Feature Store. Le verifier -estimera hors transaction, dérivera état/masque/modèle, publiera en une courte transaction, -checkpoint puis libérera les buffers. Une paire est l'unité atomique. Task Runtime et Resource -Governor décideront admission, threads et lots ; zram/swap ne sont jamais un budget. +In-memory result storage remains bounded: the inlier mask has fixed maximum capacity and no result-owned +heap destructor is required for the core row object. -Un backend reste hors identité seulement s'il est scientifiquement transparent. Sinon son -algorithme ou contrat doit apparaître dans kind/version/fingerprint avant publication. Toute seed -influençant le résultat doit avoir une politique déterministe versionnée ou être couverte par le -fingerprint. Aucun nombre de threads ou hardware ID n'est un paramètre scientifique par défaut. +Exact function declarations in the public headers remain authoritative. -## Explicitly out of scope +## Error semantics -GPU, Vulkan, OpenCL, shader et nouvelle orchestration restent hors périmètre de ce contrat de -persistance. +Creation distinguishes invalid local arguments from parent/identity constraints. -## Versioning +Loaders return corruption rather than a partially interpreted result if: -Project DB schema version 12 décrit le stockage. `verifier_version` décrit indépendamment le -contrat scientifique. Changer un algorithme n'impose une migration DB que si la représentation -persistante change. +- parent is missing or invalid; +- stored mask length is wrong; +- padding is non-canonical; +- popcount disagrees; +- model/state nullability is inconsistent; +- a verified matrix contains non-finite values. + +Scientific rejection is not a database/runtime failure. + +Runtime OOM, exception, cancellation, estimator failure or device failure are not persisted as +`GEOMETRIC_REJECTED`. + +## Current verifier lineage + +The model stores all supported versions through the same identity fields. + +### v1 + +Historical Fundamental verifier v1 remains immutable and valid. + +### v2 + +Historical Fundamental verifier v2 remains immutable and valid. + +V2 added the distinct-canonical-observation preflight in the scientific execution contract. + +### v3 + +Current production verifier is Fundamental v3. + +Production fingerprint: + +```text +6944a471d611d8ffc59dac7cf15a5b79b97e2371d4c51785c477d68c1577f74c +``` + +V3 preserves the persistent model and adds its versioned scientific preflight before the unchanged +eligible estimator path. + +No Project DB migration was needed for v3 because v12 already stores verifier version and fingerprint. + +## Task relationship + +The production Task Kind is: + +```text +geometric_verifier.run/1 +``` + +Project DB v13 adds only its typed durable Task payload. + +The Task: + +```text +pages Match Results +-> validates eligibility +-> computes or reuses exact GVR identity +-> owner publishes in canonical parent order +-> advances typed cursor +-> checkpoints +-> sequence_break +``` + +Task/runtime state remains separate from GVR scientific state. + +## Current resource boundary + +One Match Result is the scientific atomic item. + +Current validated outer-parallel Task execution may prepare independent parents concurrently. + +The owner publishes the contiguous canonical prefix. + +Current validated bounds include: + +```text +useful CPU participants <= 8 +safe parent/window size <= 16 +per-item reservation approximately 8 MiB +GPU = 0 +``` + +The internal USAC/MAGSAC scientific solver remains `isParallel=false`. + +These operational values do not enter GVR identity. + +## Real S21 v3 evidence + +Retained S21 proof: + +```text +REAL_S21_GV_V3=PASS/FROZEN + +Match Results 172,741 +Applicable MATCHED 172,275 +Verified GVRs 24,065 +Rejected GVRs 148,210 +non-applicable 466 +duplicate mappings 0 +``` + +The source Matcher project was retained unchanged and GV ran only from the Match Result boundary. + +Restart/idempotence evidence preserved the complete GVR result set. + +No Track/Sparse work belonged to the original GV-only boundary. + +## Real A6000 v3 evidence + +Retained current A6000 pre-SfM continuation: + +```text +Match Results 38,420 +Applicable GVRs 37,805 +Verified GVRs 10,952 +Rejected GVRs 26,853 +duplicate mappings 0 +``` + +Fingerprint: + +```text +6944a471d611d8ffc59dac7cf15a5b79b97e2371d4c51785c477d68c1577f74c +``` + +The continuation then built Tracks and stopped before real Sparse SfM. + +Checkpoint: + +```text +REAL_A6000_PRE_SFM=PASS/FROZEN +``` + +## Out of scope + +This persistence model does not define: + +- RANSAC/USAC/MAGSAC implementation; +- GPU kernels; +- Task scheduling; +- Track construction; +- Essential pose; +- triangulation; +- Sparse SfM; +- Homography competition. + +Those belong to their versioned scientific/runtime contracts. + +## Summary + +```text +GEOMETRIC_VERIFICATION_MODEL=IMPLEMENTED +PROJECT_DB_GEOMETRIC_VERIFICATION=v12 +PROJECT_DB_GEOMETRIC_VERIFIER_TASK=v13 + +CURRENT_PRODUCTION_VERIFIER=FUNDAMENTAL_V3 +CURRENT_VERIFIER_FINGERPRINT=6944a471d611d8ffc59dac7cf15a5b79b97e2371d4c51785c477d68c1577f74c + +REAL_S21_GV_V3=PASS/FROZEN +REAL_A6000_PRE_SFM=PASS/FROZEN + +CURRENT_PROJECT_DB_SCHEMA=v25 +``` diff --git a/docs/architecture/geometric_verifier.md b/docs/architecture/geometric_verifier.md index c213673..de466a6 100644 --- a/docs/architecture/geometric_verifier.md +++ b/docs/architecture/geometric_verifier.md @@ -1,468 +1,486 @@ # Geometric Verifier v1 / v2 / v3 -## Scope +## Status -Ce document décrit l'exécution scientifique qui transforme un Match Result `MATCHED` en résultat -Fundamental `GEOMETRIC_REJECTED` ou `GEOMETRIC_VERIFIED`. Le contrat persistant reste défini par -[`geometric_verification.md`](geometric_verification.md). Tracks, pose, Essential, compétition -Homography, triangulation et SfM sont hors périmètre. +```text +HISTORICAL_GEOMETRIC_VERIFIER_V1=FROZEN +HISTORICAL_GEOMETRIC_VERIFIER_V2=FROZEN +CURRENT_GEOMETRIC_VERIFIER_V3=PASS/FROZEN -V1 et v2 restent des identités scientifiques historiques et immutables : leurs versions, -fingerprints, lignes et résultats existants ne sont jamais réinterprétés. V2 conserve l'estimator, -les paramètres, l'ordre et l'acceptance v1, mais ajoute avant USAC le support minimal par -observations canoniques distinctes décrit ci-dessous. V3 est la policy de production courante : -après les mêmes validations intégrales, elle compose ce support v2 avec la preuve exacte de -faisabilité d'acceptation `match_count >= min_inlier_count`. Chaque policy possède sa version et son -fingerprint distincts ; le schéma les stocke déjà depuis v12 et la tête courante -Project DB v23 ne nécessite aucune migration GV. +CURRENT_VERIFIER_KIND=FUNDAMENTAL +CURRENT_VERIFIER_VERSION=3 +CURRENT_VERIFIER_FINGERPRINT=6944a471d611d8ffc59dac7cf15a5b79b97e2371d4c51785c477d68c1577f74c + +GEOMETRIC_VERIFIER_GPU=NOT_JUSTIFIED +REAL_S21_GV_V3=PASS/FROZEN +REAL_A6000_PRE_SFM=PASS/FROZEN +``` + +This document owns the scientific execution that turns one valid `MATCHED` Match Result into one +completed Fundamental Geometric Verification Result. + +The persistent row model is owned by `geometric_verification.md`. + +Tracks, Essential pose, triangulation and Sparse SfM are downstream. + +## Version lineage + +### v1 + +Verifier v1 is a frozen historical scientific identity. + +Its existing fingerprints and GVR rows remain immutable. + +### v2 + +Verifier v2 preserves the v1 estimator/acceptance path but adds a bounded preflight requiring enough +distinct canonical observations on both sides before estimator execution. + +It has its own version/fingerprint. + +### v3 + +Verifier v3 is the current production policy. + +It preserves v2 validation and additionally rejects estimator-ineligible parents when: + +```text +match_count < min_inlier_count +``` + +because acceptance is mathematically impossible in that case. + +At equality, the parent remains estimator-eligible. + +V3 does not relabel or mutate v1/v2 rows. + +Project DB v12 already stores verifier version/fingerprint, so no GVR schema migration is needed. + +Current project schema head is v25. ## Inputs -Le parent DB fournit les deux Feature Set IDs, le compte, le chemin, la taille et le SHA-256 du -Match File. Le reader Feature Store ouvre séparément chaque Feature Set validé et expose les -keypoints par plages d'au plus 256. Le verifier n'a besoin d'aucun descriptor : charger les blocs -ORB ou SIFT/RootSIFT serait inutile et est interdit dans le chemin normal. +The exact Match Result supplies: -Les keypoints persistants portent des coordonnées `binary32`. `x/y` sont exprimés en pixels de -l'image exactement décodée par OpenCV lors de l'extraction, avec origine en haut à gauche et -positions subpixel possibles. Les dimensions décodées sont disponibles dans les métadonnées du -Feature File. +- Candidate Pair relation; +- Feature Set A/B identities; +- Match File path/size/SHA; +- `match_count`. -## Fundamental matrix contract +Feature readers provide keypoints for the two immutable Feature Sets. -Le seul modèle v1 est une matrice Fundamental 3×3. Une sortie acceptée doit être unique, finie, -de norme non nulle et canonique avant publication. V1 ne projette pas la matrice vers le rang 2. +The verifier does not need descriptor blocks in its normal geometry path. + +Feature coordinates are persistent binary32 decoded-image pixels with top-left origin. + +They are converted to binary64 `Point2d` for geometric computation. ## Input ordering -L'entrée `i` de l'estimator correspond exactement à l'entrée `i` du Match File : -`feature_index_a` sélectionne le Feature Set A et `feature_index_b` le Feature Set B. Le Match -File impose déjà des indices A strictement croissants ; le verifier ne trie et ne filtre pas les -correspondances. Toute corruption d'index est une erreur d'exécution, jamais un rejet scientifique. +Estimator row `i` corresponds exactly to Match File entry `i`. -V2 distingue le nombre brut de lignes des observations canoniques distinctes. Les identités sont -`A=(feature_set_id_a, feature_index_a)` et -`B=(feature_set_id_b, feature_index_b)`. Après validation intégrale du parent, du Match asset, des -Feature Sets et Feature assets, v2 exige au moins sept A distincts **et** sept B distincts. Sinon il -publie `GEOMETRIC_REJECTED`, `inlier_count=0`, masque intégralement nul de longueur exactement -`ceil(match_count/8)`, sans modèle et sans appel USAC. +The verifier does not reorder or deduplicate Match File rows. -Ce préflight ne modifie jamais l'évidence Matcher : aucune déduplication, tri, contrainte -one-to-one, unicité de coordonnées, limite de multiplicité, analyse de rang/conditionnement/ -colinéarité ou compétition Homography n'est appliquée. Des IDs distincts ayant les mêmes -coordonnées restent des observations distinctes. Le Match File canonique rendant A strictement -croissant, une insuffisance A implique en pratique moins de sept lignes valides ; B peut en revanche -être insuffisant malgré un grand nombre de lignes brutes. +The published inlier bit `i` always maps back to Match File entry `i`. -V3 exécute ensuite USAC seulement si `match_count >= min_inlier_count`. Lorsque cette inégalité -échoue, au plus `match_count` bits du masque pourraient être inliers : l'acceptation est donc -mathématiquement impossible. V3 publie alors le même rejet zéro borné sans appel estimator. La -borne vient du paramètre durable, jamais d'une constante `16`. À l'égalité, l'entrée reste éligible. -Ce contrat n'ajoute aucune règle `N<20`, rang, coordonnées, homographie, déduplication ou retry ; -toute entrée qui franchit les deux préflights appelle l'USAC inchangé et toute exception inattendue -reste un échec Task sans publication. +Out-of-range Feature indices or corrupt assets are runtime/input failure, not scientific rejection. -## Coordinate representation +## Canonical observation identity -Le stockage source reste `binary32`. Sur 1024 points, bruit 0,75 px et 50 % d'outliers, Point2f et -Point2d ont produit le même masque et la même qualité, en 3,58 et 3,55 ms. La production convertit -vers Point2d pour rendre le calcul et la sortie binary64 explicites, pour 256 Kio au maximum. -Aucune mise à l'échelle par résolution ni conversion de repère n'est appliquée implicitement. +For preflight counting: -## Algorithm candidates +```text +A = (feature_set_id_a, feature_index_a) +B = (feature_set_id_b, feature_index_b) +``` -OpenCV 5 installé expose `FM_RANSAC`, `USAC_DEFAULT`, `USAC_ACCURATE`, `USAC_PROSAC` et -`USAC_MAGSAC`. La shortlist Gate A est FM_RANSAC comme baseline, puis USAC_DEFAULT, -USAC_MAGSAC et USAC_ACCURATE. PROSAC est `NOT_APPLICABLE` en v1 : la distance descriptor est -persistée mais l'ordre canonique suit l'index de query, pas un classement de qualité benchmarké. +V2/v3 require at least seven distinct A observations and seven distinct B observations. -## Benchmark methodology +Failure publishes a zero-inlier `GEOMETRIC_REJECTED` result with a correctly sized all-zero mask and +no Fundamental model. -Un corpus synthétique déterministe avec Fundamental ground truth couvrira bruit, outliers, -résolutions, tailles, géométries saines, faibles et adversariales. Les méthodes seront comparées -par précision/recall du masque, erreurs épipolaires, échecs, repeatability, temps et ressources. -Le benchmark lourd restera hors build et suite par défaut. Aucune fixture photo réelle ne sera -revendiquée sans fixture non sensible présente dans le dépôt. +This preflight does not: -La campagne Gate A du 9 août 2026 utilise OpenCV 5.0.0, Clang 22.1.8, une seed fixe et 32 -répétitions. Elle couvre 7 à 8192 points, 0 à 100 % d'outliers, bruit 0 à 1,5 px, 1280×720 à -4000×3000, baseline faible/large, concentration, quasi-colinéarité, planéité, rotation dominante -et duplications. Aucune fixture photo réelle représentative n'existe dans le dépôt. +- rewrite Matcher evidence; +- enforce one-to-one matching; +- deduplicate coordinates; +- perform a rank test; +- perform collinearity analysis; +- run Homography competition. -| Algorithme | P/R 1024, 30 % | P/R 8192, 70 % | Médiane/p95/pire 8192 | Seed locale | Stable 32× | -|---|---:|---:|---:|---|---| -| FM_RANSAC | 0,998/0,720 | 0,993/0,413 | 316,4/318,9/319,7 ms | non | oui observé | -| USAC_DEFAULT | 0,996/0,960 | 0,997/0,959 | 43,0/43,2/44,8 ms | preset non | oui | -| USAC_MAGSAC | 0,993/0,965 | 0,994/0,962 | 11,4/12,0/12,1 ms | preset non | oui | -| USAC_ACCURATE | 0,996/0,960 | 0,996/0,961 | 30,5/32,0/32,1 ms | preset non | oui | -| MAGSAC params v1 | 0,997/0,957 | 0,996/0,894 | 10,8/11,0/11,4 ms | oui | oui | +Distinct Feature IDs with identical coordinates remain distinct observations. -FM_RANSAC est rejeté pour son recall et son pire temps. DEFAULT et ACCURATE n'améliorent pas assez -la qualité pour leur coût. La production emploie des UsacParams explicites : la seed par appel -prime sur la variation du cas extrême liée à la seed fixe. À bruit 0,75 px/50 % d'outliers, les -seuils 0,5/1,0/1,5/2,0/3,0 donnent des recalls 0,535/0,811/0,961/0,990/1,000 et des precisions -0,996/0,988/0,990/0,986/0,985. Le compromis retenu est 1,5 px. +## v3 acceptance-feasibility preflight -## Determinism +V3 additionally checks the durable configured `min_inlier_count`. -USAC expose `cv::UsacParams::randomGeneratorState`, un entier par appel, ainsi que les paramètres -de sampling, score, optimisation locale et polishing. Cette API est préférable à une mutation de -`cv::theRNG()` process-global. FM_RANSAC restera une baseline scientifique tant que son contrôle -RNG et sa repeatability n'ont pas été mesurés. +When: -Les cinq candidats ont donné un hash modèle+masque identique sur 32 appels et dans trois processus -distincts. La garantie v1 reste intra-environnement : mêmes octets, ordre, configuration, seed, -OpenCV 5.0.0 et architecture. Aucun bit-exact cross-version ou cross-architecture n'est promis. +```text +match_count < min_inlier_count +``` -## Random seed policy +the maximum possible support cannot satisfy acceptance, so V3 publishes the canonical zero rejection +without calling USAC. -La policy v1 calcule SHA-256 sur `L3DGVSE1`, le SHA-256 du Match File puis le fingerprint. Les -quatre premiers octets sont décodés little-endian et les 31 bits faibles alimentent -`randomGeneratorState`. La policy est version 1. +The threshold comes from configuration, never a hidden constant. -## Parameter fingerprint +## Scientific model -Le fingerprint v1/v2/v3 est SHA-256 des 84 octets suivants. Les entiers sont little-endian ; les doubles -sont leurs bits IEEE-754 binary64 écrits comme `uint64_t` little-endian. NaN/Inf sont refusés et -le seul champ autorisant zéro signé, `min_inlier_ratio`, normalise `-0.0` en `+0.0`. Aucun octet ne -provient d'un dump de structure. +The only production model is a 3x3 Fundamental matrix. -| Offset | Taille | Champ | -|---:|---:|---| -| 0 | 8 | domaine ASCII `L3DGVFP1` | -| 8 | 4 | version encodage = 1 | -| 12 | 4 | kind FUNDAMENTAL = 1 | -| 16 | 4 | verifier version = 1, 2 ou 3 | -| 20 | 4 | algorithme USAC_MAGSAC explicite = 1 | -| 24 | 8 | threshold binary64 | -| 32 | 8 | confidence binary64 | -| 40 | 4 | max iterations | -| 44 | 4 | minimum inlier count | -| 48 | 8 | minimum inlier ratio binary64 | -| 56 | 4 | seed policy version | -| 60 | 4 | canonicalisation version | -| 64 | 1 | représentation Point2d = 2 | -| 65 | 1 | sampler uniforme = 0 | -| 66 | 1 | score MAGSAC = 2 | -| 67 | 1 | isParallel = 0 | -| 68 | 1 | LO inner = 1 | -| 69 | 4 | LO iterations = 5 | -| 73 | 4 | LO sample size = 14 | -| 77 | 1 | neighbor grid = 1 | -| 78 | 1 | COV polisher = 3 | -| 79 | 4 | polisher iterations = 3 | -| 83 | 1 | réservé nul | +Accepted output must be finite, non-zero and canonical. -Le vector golden v1 de la configuration historique commence par les 84 octets hexadécimaux -`4c33444756465031...0300000000` et donne le SHA-256 -`ddb44bb070c62be66c405946e89cbb49c084f8f30a21d6f408dc239225b7bbd0`. Pour un Match File SHA -composé de 31 octets nuls puis `01`, cette configuration donne la seed décimale `1910542150`. -V2 conserve cet encodage et place `2` au champ `verifier version`; son fingerprint diffère donc -obligatoirement même lorsque les sept paramètres numériques sont identiques. La seed dérivée suit -ce fingerprint v2 et appartient à cette nouvelle identité. Pour la configuration production, le -fingerprint v2 est `7868a893437ee611a10008a093286997212fa8bd80b2afd2bb1d11f04f01c5ae` ; -le même Match SHA golden donne la seed décimale `1528046088`. -V3 conserve encore exactement les 84 octets et place `3` au champ version. Pour la configuration -production, son fingerprint est -`6944a471d611d8ffc59dac7cf15a5b79b97e2371d4c51785c477d68c1577f74c` ; le même Match SHA golden -donne la seed décimale `188721673`. -Les politiques de -ressources, hardware, PSI, lot, worker et réservation CPU ne sont ni des champs ni des entrées. - -## Acceptance policy - -Un modèle candidat qui échoue à cette policy publie REJECTED avec son masque et son compte -d'inliers, sans matrice. La production exige `inlier_count >= 16` et -`inlier_count / match_count >= 0,20`. Les cas 100 % faux produisent 10/64, 14/256, 26/1024 et -28/4096 inliers, ratio maximal 0,15625. Les scènes saines produisent 45/64, 129/256 et 297/1024 ; -la faible baseline produit 126/256. - -## Fundamental matrix canonicalization - -La production adopte cette canonicalisation version 1. Les neuf valeurs doivent être finies. La -norme de Frobenius est calculée avec une accumulation `hypot` résistante au débordement ; zéro est -refusé. Le premier coefficient de valeur absolue strictement maximale gagne, donc un tie conserve -le plus petit index ligne-major. Après division, le signe rend ce pivot positif et les zéros signés -sont normalisés à `+0.0`. Sur 8192/70 %, les singular values sont -3,392e-2, 1,374e-4 et 5,915e-24. OpenCV fournit déjà rank-2 à précision numérique. V1 ne calcule -aucune SVD en production, n'impose aucun seuil de rang et n'effectue aucune post-projection rank-2. -Les validations production portent uniquement sur la forme 3×3 unique, la finitude et la norme. - -## Inlier mask generation - -Le masque OpenCV est validé en type, taille et valeurs, puis converti sans réordonnancement vers -le bitset LSB-first du modèle. Les frontières 7/8/9, 63/64/65 et 8191/8192 sont testées. - -Le core conserve strictement l'ordre d'entrée du Match File. Les tests utilisent des indices B -permutés et des masques non contigus ; le bit `i` publié reste l'élément `i` du fichier, jamais -l'index de feature. Les tailles 1, 2, 7, 8, 9, 63, 64, 65, 8191 et 8192, le padding nul et le -popcount sont couverts avec le Model v1 inchangé. - -## Scientific rejection - -En v1, un nombre brut de matches inférieur au minimum réel produit le rejet zéro historique. En -v2, moins de sept observations canoniques distinctes sur A ou B produit le rejet zéro décrit dans -`Input ordering`. V3 conserve ce test et rejette également quand le nombre brut est strictement -inférieur au `min_inlier_count` configuré. L'absence de modèle sur entrée valide ou l'échec de -l'acceptance policy produit un résultat scientifique REJECTED cohérent. - -Le minimum USAC observé est sept. Sept à quinze observations supportées peuvent produire une -hypothèse mais ne franchissent pas nécessairement le support d'acceptation production. - -## Execution failure - -Match/Feature asset absent ou corrompu, index hors bornes, exception OpenCV, OOM, masque malformé, -matrice non finie ou invariant interne invalide échoue dans le Task Runtime. Aucun résultat -scientifique n'est publié dans ces cas. - -Le core traduit parent absent/NO_MATCH et Feature Set absent en erreur d'exécution `NOT_FOUND` ; -asset absent, tronqué, hash divergent, ownership ou index incohérent en `CORRUPT` ; exception ou -sortie estimator malformée/non finie en `ESTIMATOR_ERROR` ; `bad_alloc` en `OUT_OF_MEMORY` ; et -échec Model en `DATABASE_ERROR`. Les seams test-only couvrent erreur estimator, mask/matrice -malformés, NaN, OOM et publication. Aucun de ces chemins ne crée de résultat scientifique. - -## Resource bounds - -Une unité atomique est un Match Result, au maximum 8192 correspondances. Le Match File est borné -à 98 336 octets et le bitset à 1024 octets. Aucun cache global ni préchargement de projet complet -n'est utilisé. - -À 8192 matches, les allocations directement contrôlées maximales sont 98 304 octets d'entries, -393 216 octets de keypoints A/B, 262 144 octets de Point2d A/B, deux cartes v2 de présence de -8192 octets, 1024 octets de bitset, environ 8192 octets de mask OpenCV et 72 octets de modèle, soit -environ 761 Kio hors petits objets et scratch OpenCV. Les cartes appartiennent à une paire et sont -libérées au retour ; leur borne Feature Store est opérationnelle et n'ajoute aucune limite -scientifique. Aucun descriptor ni matrice A×B n'est lu. Massif mesure 2,445 Mio de heap au pic -du test E2E complet, incluant SQLite, OpenCV, fixtures Feature Store et toutes les séquences de test. -La forme sérielle historique réservait 4 Mio fixes. La Task outer-parallel -courante réserve 8 Mio par item admis afin de couvrir aussi la pile enfant -bornée de 4 Mio, l'objet préparé, les readers et le scratch opaque. Avec un lot -maximal 16, cette charge reste bornée à 128 Mio et ne limite pas la cardinalité -du dataset. - -## CPU policy - -Le parallélisme scientifique interne OpenCV reste explicitement désactivé : -`UsacParams::isParallel=false` appartient au fingerprint FROZEN. Le verifier ne -change pas `cv::setNumThreads()` par paire. - -Le parallélisme courant porte uniquement sur des Match Results indépendants. -Le Governor peut admettre CPU1..8 pour une fenêtre/lot d'au plus 16 ; le -callback Queue compte comme participant, crée au plus `cpu_threads-1` enfants, -les joint, puis publie seul dans l'ordre. La fenêtre participant a été validée -sûre jusqu'à 16, mais CPU8 est le maximum utile mesuré. Ce choix opérationnel -n'entre ni dans le fingerprint, ni dans le GVR. - -## GPU policy - -Aucun backend Vulkan n'est implémenté avant profil du chemin CPU final. La décision attendue est -`NOT_JUSTIFIED` si les unités restent sub-millisecondes ou de quelques millisecondes. - -Verdict Gate A : `NOT_JUSTIFIED`. Les cas usuels prennent 0,3 à 5,6 ms et le pire MAGSAC local -mesuré reste à 11,4 ms. Aucun backend Vulkan de vérification n'est implémenté. - -## Task Runtime - -L'audit Gate C conclut que le checkpoint générique v1 est insuffisant : il conserve l'état, -la progression, le compteur de séquences et les temps, mais aucun payload propre au kind. Le -reconstructeur doit retrouver les paramètres scientifiques immuables et le curseur sans les -inventer depuis un fingerprint irréversible. - -`geometric_verifier_tasks` est donc la seule raison de Project DB v13. Elle doit conserver -`task_id`, `after_match_result_id`, les sept paramètres de configuration v1 et le fingerprint -calculé à la création pour validation à la reconstruction. Aucun `cv::Mat`, buffer, état RNG, -paramètre Governor ou donnée hardware n'y appartient. La tâche calcule hors transaction et publie -chaque résultat par transaction courte avant avancement du curseur. - -Le payload v22 ne duplique pas `verifier_version`. À la reconstruction, le fingerprint exact est -comparé aux encodages supportés v1, v2 et v3 des sept paramètres durables : cela restaure sans -ambiguïté chaque policy, sans migration ni nouveau système d'identité. Toute nouvelle tâche -sélectionne v3 ; le payload et l'ABI publique de configuration restent inchangés. Une tâche v3 -démarre avec une identité neuve et ne reprend ni ne ré-étiquette une tâche v1/v2. - -Le Task Kind production est `geometric_verifier.run` version 1. Il pagine les Match Results par ID -strictement croissant avec une page de `batch + 1`, et traite des lots Governor -1..16 avec CPU utile 1..8. Les préparations éligibles peuvent s'exécuter en -parallèle, mais la publication, l'avancement du curseur contigu et le checkpoint -restent owner-only et ordonnés. Les parents autres que `MATCHED` avec -`match_count > 0` sont seulement traversés par le curseur. Une unité éligible -appelle le core, qui reuse l'identité exacte avant toute lecture d'asset. - -WHY GENERIC TASK PERSISTENCE IS INSUFFICIENT: aucun champ de payload métier dans le snapshot v1. - -REQUIRED DURABLE FIELDS: configuration scientifique v1, fingerprint et dernier Match Result -publié puis checkpointé. - -WHY EXISTING DB CANNOT STORE THEM: `tasks` et `checkpoints` ne portent que le résumé générique ; -aucune table v12 ne possède une ligne 1:1 adaptée à ce Task Kind. - -## Checkpoint/recovery - -La pagination suit `match_result_id` croissant sans supposer des IDs contigus. Le résultat est -publié avant que `after_match_result_id` avance en mémoire ; le curseur n'est persisté qu'après le -lot. Après chaque lot non terminal, `task_sequence_break()` rend la réservation au Governor. - -Le test de crash publie puis interrompt avant checkpoint du curseur, ferme runtime et DB, recharge -le checkpoint antérieur et reconstruit le Task Kind. Le parent est revu, son résultat exact est -réutilisé, puis le curseur progresse. - -## Cancellation - -Pause et annulation sont coopératives avant chaque parent et entre lots. Une petite estimation -OpenCV engagée finit et publie avant l'arrêt ; aucun résultat scientifique CANCELLED n'est créé. -Les résultats déjà publiés restent durables. - -## Backend policy - -Un backend n'est transparent pour l'identité que si ses sorties scientifiques sont équivalentes -selon le contrat. V1 possède une seule implémentation CPU de production. - -## Core publication and reuse - -Le core charge les métadonnées DB, relâche les mutex internes après chaque API, lit les assets et -calcule sans transaction longue, puis appelle une publication Model v1 courte. Une identité exacte -VERIFIED ou REJECTED est retournée avant toute lecture Feature/Match et sans appel estimator. Une -contrainte concurrente déclenche un unique `find` de l'identité, jamais un overwrite ou une -récursion. Changer un paramètre scientifique produit un autre fingerprint et un autre résultat. - -Les tests E2E utilisent le vrai Project DB (migré séquentiellement jusqu'à v15 à -l'ouverture), deux Feature Files à 8192 points, des Match Files hashés, le vrai -MAGSAC et le Model v1. VERIFIED est rechargé après close/reopen avec modèle et -masque bit-identiques ; REJECTED conserve son support et est également réutilisé. +The current verifier does not add Essential/Homography model competition. ## Production algorithm -UsacParams explicites, sampler uniforme, score MAGSAC, non parallèle et seed locale par appel. -Les champs LO et polishing effectifs sont encodés explicitement ; aucun preset enum caché. +The production estimator uses explicit OpenCV USAC/MAGSAC parameters rather than a hidden preset. -## Production parameters +Scientific choices include: -FUNDAMENTAL version 1 ; seuil 1,5 px ; confiance 0,999 ; 5000 itérations ; 16 inliers ; ratio 0,20 ; -seed policy 1 ; canonicalisation 1 ; Point2d. Tous les champs scientifiques appartiennent au -fingerprint version 1. +```text +model FUNDAMENTAL +algorithm USAC_MAGSAC +point representation Point2d +threshold 1.5 px +confidence 0.999 +max iterations 5000 +minimum inlier count 16 +minimum inlier ratio 0.20 +sampler uniform +score MAGSAC +isParallel false +LO iterations 5 +LO sample size 14 +polisher COV +polisher iterations 3 +``` -## Validation +The internal scientific estimator remains serial: -Gate A couvre corpus, comparaison, seed et repeatability. Gate B couvre fingerprint/seed golden, -canonicalisation, mapping bit à bit, frontières d'acceptation, E2E DB, reuse, corruption, -publication, 8192 matches et ASan/UBSan. Gate C couvre Task, publication avant curseur et reprise. +```text +UsacParams::isParallel=false +``` -La référence A6000 v3 complète contient 37 805 parents `MATCHED`. Le préflight v3 rejette à zéro -9 368 parents avec `N<16`, puis le support distinct A/B rejette 117 parents supplémentaires avec -`N>=16`. Les 28 320 autres parents appellent USAC sous leur seed v3 exacte ; ils terminent tous par -un modèle ou une absence de modèle, sans exception estimator. Cette exécution démarre une tâche v3 -neuve et ne reprend ni ne ré-étiquette la tâche v2 historique 1385. +Outer Task-level concurrency is separate. -Gate D a exécuté 1000 parents configurés dans la vraie Task, puis les reprises et variantes de -configuration du test : environ 2001 traversées réutilisées en 5,870 s, soit environ 341/s. Ce -run valide pagination, checkpoints, Governor et reuse ; il n'est pas une mesure de latence MAGSAC -et n'en revendique ni médiane ni p95. Le RSS pic observé est 25 964 Kio pour le processus de test -complet. `MemAvailable` passe de 10 702 988 à 10 692 916 Kio ; `pswpin/pswpout` restent 0/0 ; en -fin de run, PSI avg10 vaut 0,34 % CPU, 0 % mémoire et 0 % I/O. Le chemin calculé reste couvert par -le vrai E2E MAGSAC Gate B et ses bornes, sans campagne scientifique répétée. +## Random seed -TSan couvre core, Task, sequencing et Governor (4/4), avec uniquement la suppression OpenCV -existante. Le build CPU-only couvre la suite normale (31/31). La suite normale ne contient ni -benchmark lourd ni stress. Le clean build Clang/Clang++ et la campagne normale finale passent -32/32 avec ORB Vulkan matériel sur Radeon 780M RADV PHOENIX. +The seed is derived locally from immutable scientific input. + +The frozen seed domain is based on: + +```text +L3DGVSE1 +Match File SHA-256 +verifier parameter fingerprint +``` + +The resulting seed is supplied to the per-call USAC parameter object. + +Global `cv::theRNG()` mutation is forbidden. + +## Parameter fingerprint + +The verifier fingerprint is SHA-256 over the frozen canonical 84-byte encoding. + +Domain: + +```text +L3DGVFP1 +``` + +It explicitly encodes: + +- verifier kind; +- verifier version; +- algorithm; +- threshold; +- confidence; +- iteration bound; +- minimum inlier count; +- minimum inlier ratio; +- seed-policy version; +- canonicalization version; +- Point2d selection; +- sampler; +- score; +- `isParallel`; +- LO settings; +- neighbor mode; +- polisher settings; +- reserved zero byte. + +Integers are little-endian. + +Binary64 values use explicit IEEE-754 bits encoded little-endian. + +No C struct dump participates. + +Current v3 production fingerprint: + +```text +6944a471d611d8ffc59dac7cf15a5b79b97e2371d4c51785c477d68c1577f74c +``` + +Historical v1/v2 fingerprints remain distinct and valid. + +## Acceptance + +A candidate is verified only when the frozen support policy is satisfied: + +```text +inlier_count >= 16 +inlier_count / match_count >= 0.20 +``` + +Otherwise the result is scientific `GEOMETRIC_REJECTED`. + +A scientific rejection is a completed valid result. + +Runtime failure publishes no scientific GVR. + +## Fundamental canonicalization + +The nine coefficients must be finite. + +Frobenius norm is computed robustly; zero norm is rejected. + +The pivot is the first row-major coefficient having the strictly greatest absolute value. + +The matrix is normalized and signed so that pivot is positive. + +Signed zeros are normalized to `+0.0`. + +No production SVD/rank-2 post-projection is performed by this verifier version. + +## Inlier mask + +OpenCV output mask is validated for type, length and values. + +It is converted without reordering to the persistent LSB-first GVR bitset. + +Padding bits are zero and popcount matches `inlier_count`. + +## Runtime failure versus rejection + +Scientific rejection includes valid cases such as: + +- insufficient canonical support; +- v3 impossible acceptance support; +- estimator returns no acceptable model; +- inlier support below frozen acceptance. + +Runtime/input failure includes: + +- missing/corrupt Match or Feature asset; +- out-of-range Feature index; +- malformed estimator mask; +- non-finite invalid model; +- OOM; +- unexpected OpenCV exception; +- Project DB publication failure. + +Runtime failure publishes no fake rejected result. + +## Resource bounds + +One scientific atomic item is one Match Result with at most 8192 correspondences. + +Current outer-parallel Task reservation uses approximately: + +```text +8 MiB per admitted parent +batch/window <= 16 +useful CPU participants <= 8 +GPU 0 +``` + +The window is safe to 16; CPU8 is the retained useful maximum from measurement. + +These are operational resource values, not fingerprint fields. + +No descriptors or dense A-by-B matrix are loaded by the normal verifier. + +## Outer parallel execution + +Current validated shape: + +```text +one admitted owner Task +-> bounded independent Match Result preparation +-> up to admitted CPU participants +-> join +-> owner publishes canonical contiguous prefix +-> owner advances cursor +-> checkpoint +-> sequence_break +``` + +Participant preparation writes no GVR rows. + +Only the owner publishes after join. + +This preserves deterministic parent ordering and restart semantics. + +The inner USAC solver remains scientifically serial. + +## GPU policy + +Current classification: + +```text +GEOMETRIC_VERIFIER_GPU=NOT_JUSTIFIED +``` + +Measured CPU units remained small enough that no production GPU backend was justified. + +No Vulkan/OpenCL/CUDA verifier backend is currently part of the production identity. + +CPU outer parallelism remains valid and should not be disabled merely because GPU is rejected. + +## Durable Task + +Task Kind: + +```text +geometric_verifier.run/1 +``` + +Project DB v13 adds the typed task payload required because generic checkpoint v1 does not contain the +scientific verifier parameters/cursor. + +Durable payload includes the immutable scientific configuration and: + +```text +after_match_result_id +``` + +The fingerprint is revalidated on reconstruction. + +The durable payload does not contain: + +- `cv::Mat`; +- RNG engine state; +- Governor feedback; +- hardware identity; +- CPU mask; +- transient buffers. + +## Restart + +Pagination follows increasing `match_result_id` and does not assume contiguous IDs. + +A GVR is published before the durable cursor advances. + +Crash after publication but before checkpoint may replay that parent. + +Restart finds/revalidates the exact GVR identity and reuses it. + +No overwrite is performed. + +## Historical resource normalization + +The exact historical serial resource shape remains accepted only for restart compatibility. + +It may be normalized ephemerally to the current outer-parallel capability. + +The original checkpoint is not rewritten. + +Neighboring resource shapes are rejected rather than guessed. ## Real S21 GV v3 -`REAL_S21_GV_V3=PASS/FROZEN` au 31 août 2026. La preuve part d'une copie reflink entière du projet -Matcher S21 gelé à 2 826 Feature Sets, 172 741 Candidate Pairs et 172 741 Match Results. Le SHA-256 -DB source vaut avant et après -`9f5ee4877bca25db3d4929be06d8e6ff4fa1c29e11249e4125266a833f09f3e0` ; le projet source n'est -jamais ouvert en écriture. La copie de travail reprend exclusivement à la frontière Match Result, -par la Task, la Queue et le Resource Governor AUTO de production, puis s'arrête avant Track -Builder. Avant GV, la Task Matcher 2831 est `COMPLETE`, progression 100, -`sequence_count=21629`, curseur 172 741 ; son checkpoint SHA-256 vaut -`636f4f4a20f27308d90142c495c9f6ffc04b4c0dfcca0fdc75cfeb5366ab50b1`. Le projet contient alors -zéro GVR, Track Set, Track ou Sparse Reconstruction. +Retained S21 proof: -La Task 2832 consomme le curseur complet de 172 741 Match Results. Parmi eux, 172 275 parents -`MATCHED` applicables produisent exactement 172 275 identités v3 : 24 065 -`GEOMETRIC_VERIFIED` et 148 210 `GEOMETRIC_REJECTED`. Les 466 autres Match Results sont traversés -sans GVR conformément au contrat. Le fingerprint est -`6944a471d611d8ffc59dac7cf15a5b79b97e2371d4c51785c477d68c1577f74c`. La Task termine -`COMPLETE`, progression 100, `sequence_count=21592`, curseur 172 741 et zéro mapping dupliqué. Son -checkpoint final SHA-256 vaut -`3e6bed97cee9c96f229ef19a3c905d19d4693cdbf43b30319edcdeed6c4e378e`. Le wall propre à -l'enqueue/attente GV vaut 3 221,757986763 s ; le wall du runner incluant l'audit intégral amont -vaut 3 252,89 s. +```text +REAL_S21_GV_V3=PASS/FROZEN -L'audit relit les 172 741 mappings Candidate/Match et les 172 275 Match Files : SHA, taille, -header, entrées, ordre et curseur Matcher restent valides. Le digest `L3DMRD1` demeure -`e5128a2e599ff593c4f79850e067254b1f249d19e8480a44973306b1af250f70`. Feature Sets, Candidate -Pairs et Match Results gardent respectivement 2 826, 172 741 et 172 741 lignes ; aucune Task -Feature, Candidate ou Matcher n'est rejouée. Track Set, Track, Track Builder Task, Sparse SfM Task -et Sparse Reconstruction restent tous à zéro. +Match Results 172,741 +Applicable MATCHED 172,275 +Verified 24,065 +Rejected 148,210 +non-applicable 466 +duplicate mapping 0 +``` -Le Governor enregistre 21 593 admissions, exclusivement backend fixe, sans changement de contrat. -Le dernier contrat est GREEN, CPU 1, GPU 0, I/O 1, batch 8, hôte 4 Mio et GPU 0. Les masques sont -compute `0-5,8-13` et reserve `6,7,14,15`. Sur l'échantillonnage coalescé de 21 590 changements, -le minimum `MemAvailable` vaut 8 907 714 560 octets, le RSS/HWM processus maximal 45 690 880 -octets, le PSI mémoire maximal 0,90 %, le PSI I/O maximal 50,17 % et les deltas swap-in/out sont -0/0. Les réserves 3 Gio/2 Gio alors en vigueur pour ce run historique restent -respectées ; aucune voie GPU GV n'est créée. +The v3 fingerprint is the current production fingerprint. -La seconde reprise complète crée la Task 2833, traverse le même curseur et crée zéro GVR. Les -172 275 lignes avant/après sont égales sur toutes leurs colonnes par `EXCEPT` dans les deux sens, -avec zéro différence, les mêmes IDs 1..172275 et les mêmes comptes accepté/rejeté. Sur une copie -reflink séparée, SIGKILL interrompt la Task 2834 après un préfixe checkpointé : l'état durable reste -`RUNNING/PENDING`, puis la registry de production reprend cette même Task (`inspected=1`, -`resumed=1`) jusqu'à `COMPLETE`, progression 100 et curseur 172 741. L'égalité complète des GVR -avec le projet terminé reste zéro différence dans les deux sens ; aucun résultat n'est perdu ou -dupliqué et aucun travail amont/aval n'est exécuté. +A complete replay produced zero new GVRs. -Les builds normaux Vulkan et portable sans Vulkan passent. Les 14 tests focalisés GV, Task, -checkpoint, Project DB/Project, registry, Queue et Governor passent dans chaque configuration ; le -test runner ciblé passe aussi sous ASan/UBSan. `REAL_S21_GV_V3`, `RESTART_IDEMPOTENCE`, -`DETERMINISM`, `GOVERNOR_ADMISSION` et `DOWNSTREAM_STOP` sont donc `PASS/FROZEN`. Ce gel porte sur -la preuve réelle de la policy v3 déjà gelée ; il ne rouvre ni algorithme, seuil, RNG, fingerprint, -Project DB v22, Matcher/Governor v2, Track Builder ou Sparse SfM. +A SIGKILL/restart proof resumed the same Task and converged to the same complete GVR set. -## Maintenance outer-parallel — preuve représentative réelle +The run stopped before Track Builder at the original GV-only checkpoint. -**IMPLEMENTED / VALIDATED / REVIEWED.** La maintenance sépare la préparation -scientifique de la publication sans modifier la policy v3. Une préparation -valide tout l'input immuable et produit un objet opaque borné ; elle n'écrit -jamais Project DB. Après jointure, le callback propriétaire publie ces objets -en ordre de `match_result_id`, détruit chacun exactement une fois et checkpoint -le seul préfixe contigu. Les pannes de création partielle, calcul, publication, -annulation et reprise ne peuvent donc ni publier un suffixe devant un trou, ni -laisser un enfant/handle vivant à la libération de réservation. +Later S21 Track evidence exists separately. -Le corpus représentatif réel contient 4 113 parents, dont 4 102 applicables, -578 `GEOMETRIC_VERIFIED` et 3 524 `GEOMETRIC_REJECTED`. CPU1/2/4/8/12 -conservent les mêmes IDs 1..4102, toutes les colonnes scientifiques et le digest -`9401ef6168804b6f1d51f4cdf64cd6b33cbebd2934e5294c8feacc87f9c8ce86`. -Les walls Task complets sont 67,521078032/48,859141912/39,158236068/ -35,170176868/34,251675780 s, soit 60,7514/83,9556/104,7545/116,6329/ -119,7606 parents/s. Le gain CPU8→CPU12 vaut seulement 2,68 %, sous le seuil de -5 %. La capacité production est donc CPU utile 8, batch 16 et fenêtre sûre 16. +## Real A6000 v3 -Les tests focalisés finaux passent 8/8, les répétitions de stress 60/60, -ASan/UBSan 3/3 et TSan 3/3, avec contrôles C17 GCC/Clang. La preuve S21 -historique ci-dessus reste le run complet CPU1/batch8 acquis ; aucun rerun -complet de 3 221 s n'est revendiqué pour cette maintenance bornée. Le manifest -retenu de cette preuve a le SHA-256 -`52a4412299c74050a66d5690122a793c9451c79faf47e32b6e65a5958f804856`. -Il compare littéralement les 4 102 GVR applicables — ordre/IDs 1..4102, -statut, compteur/masque d'inliers, présence et octets binary64 du modèle — et -vérifie intégrité DB, clés étrangères, absence de replay amont et absence de -travail Tracks/Sparse. Le run S21 complet acquis couvre déjà la policy v3 et -sa persistance FROZEN ; la maintenance ne change que la préparation externe et -la publication owner-only. Cette combinaison réelle bornée + tests ciblés de -panne/checkpoint/reprise discrimine donc le changement sans payer un second run -scientifique intégral ni prétendre l'avoir exécuté. +The retained A6000 pre-SfM continuation contains: -La validation globale fraîche qui englobe ce delta passe aussi dans le graphe -Clang portable Vulkan-off 931/931 et sa suite 64/64, puis le graphe Vulkan-on -939/939 et sa suite 65/65. Le TSan global reste volontairement Vulkan-disabled -et couvre les deux cibles GV dans sa matrice 14/14 plus répétitions ; il utilise -uniquement les suppressions externes OpenCV/TBB documentées par le projet. +```text +Match Results 38,420 +Applicable GVRs 37,805 +Verified GVRs 10,952 +Rejected GVRs 26,853 +duplicate mappings 0 +``` -## Out of scope +Current fingerprint: -Tracks, model competition, classification planaire ou faible parallaxe, Essential, calibration, -pose, triangulation, bundle adjustment, SfM et Vulkan RANSAC. +```text +6944a471d611d8ffc59dac7cf15a5b79b97e2371d4c51785c477d68c1577f74c +``` + +The Task completed the full cursor. + +Restart traversed the cursor and created zero new GVRs. + +The continuation then reused/built the frozen Track Set and stopped before real Sparse SfM. + +```text +REAL_A6000_PRE_SFM=PASS/FROZEN +``` + +## Validation boundaries + +The scientific verifier validation covers: + +- canonical fingerprint/seed; +- bit-exact mask mapping; +- acceptance boundaries; +- corruption; +- publication/reuse; +- maximum Match File cardinality; +- restart; +- deterministic canonicalization; +- outer-parallel owner publication. + +TSan qualification must preserve the external OpenCV/TBB boundary described by the concurrency/global +maintenance documents. + +Do not claim Vulkan verifier validation: there is no production Vulkan verifier backend. + +## Summary + +```text +CURRENT_GEOMETRIC_VERIFIER=FUNDAMENTAL_V3 +CURRENT_VERIFIER_VERSION=3 +CURRENT_VERIFIER_FINGERPRINT=6944a471d611d8ffc59dac7cf15a5b79b97e2371d4c51785c477d68c1577f74c + +GEOMETRIC_VERIFIER_TASK=geometric_verifier.run/1 +PROJECT_DB_GEOMETRIC_VERIFICATION=v12 +PROJECT_DB_GEOMETRIC_VERIFIER_TASK=v13 + +INNER_USAC_PARALLEL=false +OUTER_PARALLEL=VALIDATED +USEFUL_CPU_MAX=8 +SAFE_WINDOW_MAX=16 +PER_ITEM_RAM=8_MiB +GPU=NOT_JUSTIFIED + +REAL_S21_GV_V3=PASS/FROZEN +REAL_A6000_PRE_SFM=PASS/FROZEN +CURRENT_PROJECT_DB_SCHEMA=v25 +``` diff --git a/docs/architecture/runtime.md b/docs/architecture/runtime.md index e590d23..21951ac 100644 --- a/docs/architecture/runtime.md +++ b/docs/architecture/runtime.md @@ -1,314 +1,505 @@ -# Exécution et runtime Lardon3D +# Lardon3D Runtime -## Modèle d'exécution - -### Threads -- Thread principal : entrée, modèle de vue et rendu TUI ncursesw (exclusif) -- Thread worker Queue : exécution sérielle des tâches métier -- Participants internes : uniquement ceux du contrat Task admis, joints par le - callback propriétaire avant publication -- Opération SSD : au plus un thread joinable, uniquement pendant une opération - UDisks bornée ; il ne rend rien et ne devient ni Queue ni scheduler - -### Synchronisation -- Mutex pour les accès partagés -- Variables de condition pour la coordination -- Atomicité des opérations critiques - -## Cycle de vie d'une tâche +## Status ```text -1. Création (PENDING) -2. Soumission à la file -3. Sélection FIFO/adaptative par la Queue -4. Réservation obligatoire -5. Exécution (RUNNING) - - Pause/reprise coopérative - - Annulation coopérative - - Séquences adaptatives -6. Complétion (COMPLETED) ou Échec (FAILED) -7. Nettoyage des ressources +CURRENT_PROJECT_DB_SCHEMA=v25 +CURRENT_PRODUCTION_TASK_KINDS=16 + +TASK_QUEUE_WORKERS=1 +INTERNAL_PARALLELISM=BOUNDED +INTER_TASK_PARALLELISM=NOT_IMPLEMENTED + +RUNTIME_OBSERVER=CURRENT/VALIDATED_OPERATIONAL +RESOURCE_UTILIZATION_POLICY=MAXIMUM_SAFE_USEFUL_THROUGHPUT +SERIALISM_REQUIRES_PROOF=CANONICAL + +REAL_A6000_PRE_SFM=PASS/FROZEN ``` -## Synchronisation +The runtime coordinates the main thread, Task Queue, Resource Governor, Project DB lifecycle, bounded +Task-internal participants, runtime observation and optional external-SSD controller. -### Mutex -- Protection des données partagées -- Accès exclusif aux ressources critiques +It does not introduce a second scheduler. -### Variables de condition -- Coordination entre threads -- Notification de changement d'état -- Attente passive ; timeout borné seulement pour réévaluer un `WAIT` ressources +## Thread model -### Atomicité -- Opérations indivisibles -- État cohérent garanti +### Main thread -## Gestion des erreurs +Owns: -### Rollback -- Rollback des transactions locales avant publication -- Nettoyage complet des ressources possédées par l'opération -- Une publication fichier réussie suivie d'un échec DB peut laisser un orphelin - valide ; aucune transaction distribuée fichier+SQLite n'est revendiquée +- input; +- ncurses; +- TUI model binding; +- project open/close orchestration; +- bounded polling of runtime/SSD state. -### Récupération -- Reprise à la dernière frontière connue -- Ignorance des artefacts partiels -- Validation avant publication +ncurses remains main-thread-only. -## Limites actuelles +### Task Queue worker -- Worker Queue unique (pas de pools inter-Tasks multiples) -- Pas de parallélisme inter-Tasks ; certains kinds possèdent des participants - internes bornés, comptés par leur contrat Governor -- Reprise automatique limitée aux tâches indépendantes reconstructibles +The single Queue worker owns one active heavy Task callback at a time. -## Reprise durable +A Task callback may create bounded internal participants only when its admitted Task contract permits +them. -Un snapshot ne conserve que l'état logique d'une tâche. `RUNNING` et `PAUSED` -sont normalisés vers `PENDING`; aucun worker, callback brut, pointeur, contrat -ou réservation n'est restauré. Le propriétaire fournit un nouveau callback et -resoumet la tâche. Les états terminaux sont conservés. +Those participants join before owner publication. -`started_at` désigne le début de la tentative d'exécution courante, pas le -premier démarrage historique. Un checkpoint `RUNNING` restauré en `PENDING` -conserve temporairement l'horodatage de la tentative interrompue pour -l'observation ; lors de `lardon3d_task_start()`, `started_at` est remplacé par le -nouveau démarrage et `finished_at` est remis à zéro. `finished_at` n'est fixé -qu'à la terminaison de cette tentative. +### SSD operation thread -**IMPLEMENTED** — snapshot, codec v1 et restauration isolée. +The SSD controller may own at most one bounded joinable operation thread while executing a synchronous +UDisks operation. -**IMPLEMENTED** — l'import `import.images` se sauvegarde à chaque fin de lot et -se reconstruit explicitement avec un userdata neuf lié au projet rouvert. Son -intention durable contient `source_path + scanset_id`; le hash/copie et la -transaction catalogue restent hors mutex Task et hors mutex DB pendant l'I/O. +It does not become: -**IMPLEMENTED** — `project_open()` inventorie par pages de 8, restaure puis -resoumet automatiquement les tâches production valides. Il retourne après -l'enqueue et n'attend jamais leur terminaison. +- a Task worker; +- a Queue; +- a scheduler; +- an ncurses owner. -L'ordre d'initialisation production est : politique driver, profil matériel, -Governor, backend, Queue/worker, contrôleur SSD optionnel et binding -Governor, puis TUI. L'ouverture DB/projet et la reprise synchrone sont pilotées -ensuite depuis le thread principal. Une fermeture ne peut commencer qu'après le -retour de `project_open()`. Le worker peut consommer pendant le scan ; chaque -tâche exécutée est néanmoins réadmise normalement. +## Task lifecycle -**NOT_YET_WIRED** — reprise ordonnée par dépendances/DAG. Les kinds de -production reconstructibles checkpointent déjà à leurs frontières métier ; -aucun timer autosave générique ne doit avancer devant leur publication durable. +Conceptual lifecycle: -**IMPLEMENTED** — reprise sélective des kinds reconstructibles via Project DB, -Task Kind Registry et Queue. Les dépendances/DAG restent différées ; il -n'existe pas de scheduler global distinct à restaurer. +```text +create PENDING +-> persist typed intent where required +-> enqueue +-> Queue selects +-> Resource Governor admits/reserves +-> RUNNING +-> bounded sequence work +-> Task-specific durable publication +-> generic checkpoint +-> optional sequence_break/re-admission +-> COMPLETED | FAILED | CANCELLED +-> terminal callback +-> destruction +``` -## Accès Project Database +Pause/cancel are cooperative. -**IMPLEMENTED** — une connexion SQLite opaque sérialisée par mutex interne ; -les opérations multi-tables sont transactionnelles et bornées. +A sequence break is not a Task state. -**IMPLEMENTED** — le cycle de vie projet ouvre/crée `project.db`, vérifie -l'identité et ferme la connexion. Ouvrir, fermer ou changer de projet est une -frontière exacte : l'observateur et la vue optique libèrent leurs borrows, puis -l'unique Queue est annulée, jointe et détruite, callbacks terminaux inclus, -avant la fermeture de Project DB. Une seule Queue vide est ensuite recréée et -les observateurs sont rebondés. Il n'existe jamais deux schedulers simultanés. -L'historique terminal et l'espace d'IDs Queue sont ainsi propres à la session ; -les mêmes Task IDs durables de deux projets restent indépendants et aucun -historique fourni n'est affiché lorsqu'aucun projet n'est chargé. +## Durable restart -**IMPLEMENTED** — la registry reconstruit explicitement callback/userdata hors -mutex DB pour un kind connu ; elle ne soumet aucune tâche. +A generic snapshot stores logical Task state, not live execution machinery. -**IMPLEMENTED** — la queue accepte un identifiant restauré préassigné s'il -n'entre en collision avec aucune tâche connue. L'import production peut donc -être reconstruit puis soumis explicitement. +On restoration: -**IMPLEMENTED** — la resoumission automatique utilise la registry production, -conserve le task ID et laisse le worker obtenir une nouvelle réservation. -Kinds inconnus, tâches legacy, checkpoints invalides et sources absentes ne -bloquent pas l'ouverture. +```text +RUNNING -> PENDING +PAUSED -> PENDING +``` -**IMPLEMENTED** — `visual_index.update` reprend à la dernière membership -commitée. Un segment temporaire n'est jamais visible et un rejeu exclut les -Feature Sets déjà membres. +Terminal states remain terminal. -## Durée de vie terminale et fermeture Queue +Restart never restores: -Une Task terminale reste vivante jusqu'au retour complet de son callback -terminé. Queue la retire alors de la liste active et la détruit hors de son -mutex ; seule une histoire de 64 snapshots reste observable. Les appels déjà -enregistrés avant `task_queue_destroy()` sont attendus. Le propriétaire doit -empêcher tout nouvel appel dès le début de la destruction, règle nécessaire à -toute API C adressée par pointeur brut. +- worker thread; +- callback pointer; +- userdata pointer; +- CPU affinity; +- live reservation; +- GPU handle; +- scratch lease; +- adaptive feedback history. -Un callback terminé peut consulter les vues Queue tant que le propriétaire la -maintient vivante. Il ne peut pas détruire cette Queue, retirer son propre -record ni attendre une opération dépendante de son retour. +Task Kind Registry reconstructs fresh runtime binding from exact durable kind/version and typed payload. -## Observatoire TUI actuel +Every resumed Task is re-admitted by the Resource Governor. -**CURRENT / VALIDATED OPERATIONAL.** Ce statut décrit l'implémentation et ses -tests courants. L'audit global qui contient cette frontière est désormais -`PASS/FROZEN` après revue indépendante ; le statut TUI reste volontairement -opérationnel et n'interdit pas ses évolutions futures sous un ticket distinct. +## Project-open recovery -### Séparation modèle, observation et rendu +`project_open()` discovers durable Tasks in bounded pages, validates their checkpoint/typed identity, +reconstructs eligible bindings and submits them to the existing Queue. -Le modèle `tui_model` est pur et testable sans terminal. Le renderer reçoit -seulement des copies bornées et n'interroge ni Queue, ni Governor, ni Project -DB, ni contrôleur SSD. Toutes les fonctions ncurses, l'entrée clavier et le -rendu demeurent sur le thread principal. +It returns after enqueue; it does not wait for those Tasks to finish. -L'observateur runtime emprunte Queue et Governor et conserve une seule copie -cohérente. Les captures ordinaires sont coalescées pendant au moins une seconde -monotone ; un échec conserve la dernière vue bornée en la marquant stale. -Il observe au plus 129 Tasks : les 64 pending possibles, l'unique active et les -64 snapshots terminaux récents. L'ordre Queue place le travail vivant du plus -récent au plus ancien, puis l'histoire par terminaison décroissante ; une Task -active ne peut donc pas être masquée par un vieux préfixe historique. Il -n'existe ni scan DB par frame, ni lecture `/proc` volumineuse, ni historique -non borné. +Unknown kinds, unsupported versions, legacy-untyped Tasks, invalid checkpoints or Task-specific +non-reconstructible input remain inspectable and do not cause guessed execution. -Les ABI historiques restent exactes : `Lardon3DTaskSnapshot`, -`Lardon3DResourceSnapshot`, `Lardon3DAppState` et -`lardon3d_layout_draw()` ne sont pas étendus en place. Les surfaces additives -`Lardon3DTaskObservation`, `lardon3d_task_queue_observe()`, -`Lardon3DResourceObservation`, `Lardon3DRuntimeSnapshot` et -`lardon3d_layout_draw_runtime()` portent les nouveaux champs. De même, -`lardon3d_tui_run()` reste le symbole historique ; l'application utilise -`lardon3d_tui_run_with_ssd_operation()` avec un owner SSD conservé hors de -`Lardon3DAppState`. +Generic dependency/DAG recovery remains unimplemented. -### Progression et ETA +## Initialization order -Une Task typée publie `completed/total` seulement après son propre commit -métier durable. Quand ces compteurs sont connus, la TUI les affiche toujours et -en dérive le pourcentage sans utiliser le message ou le nom. Une Task marquée -`COMPLETED` avec un préfixe durable incomplet est une erreur d'intégrité -visible, jamais 100 %. Quand les comptes typés sont inconnus, le lifecycle peut -être terminal mais la progression scientifique reste indéterminée. Le -pourcentage générique non typé, lorsqu'il est utile, porte explicitement le -libellé runtime. +Production startup establishes the safe driver/runtime policy before heavy worker/backend activity. -Le débit est un EWMA borné. La première observation établit seulement le -préfixe de reprise et ne contribue pas au taux ; une reprise de RUNNING remet -également la fenêtre temporelle à zéro. Deux intervalles strictement positifs -sont nécessaires avant un débit et une ETA connus. Une absence de progrès, -une pression Governor, une régression ou une preuve insuffisante produit -respectivement `STALLED`, `THROTTLED`, reset ou `INDETERMINATE/CALCULATING`. -Seule une complétion cohérente vaut exactement 100 % et ETA zéro ; aucune fausse -précision n'est affichée. +Conceptually: -### Pipeline et ressources +```text +driver policy +-> hardware profile +-> Resource Governor +-> optional backend metadata +-> Task Queue / worker +-> optional SSD controller + Governor binding +-> TUI +``` -La synthèse utilise les étapes Acquisition, RAW, Quality, Features, Visual -Index, Candidate, Matcher, GV, Tracks, Sparse SfM et future Dense. Les états -sont `NOT_READY`, `READY`, `QUEUED`, `RUNNING`, `THROTTLED`, `BLOCKED`, -`COMPLETE`, `FAILED` et `NOT_APPLICABLE`. Dense reste explicitement -`NOT_APPLICABLE` tant qu'aucun Task kind de production n'existe ; une étape -future n'est jamais devinée active depuis un nom ou un message. +Project open/recovery is then driven from the main thread. -Le panneau ressources expose CPU actif/admis/disponible et sa raison, GPU -présent/mémoire/busy/backend lorsqu'ils sont connus, RAM/MemAvailable/réserve, -swap total/utilisé et deltas actifs, lot/inflight/helpers/I/O, scratch et -pression Governor GREEN/YELLOW/RED. Le contrat installé de l'exacte Task active -est l'autorité pour CPU et lot. Un dernier diagnostic privé seulement indexé -par kind peut appartenir à une autre Task ou séquence : backend, inflight, -helpers, utilisation ou raison restent donc `UNKNOWN` sans association exacte -Task+séquence. La mémoire UMA est comptée une seule fois et ni swap ni scratch -ne sont ajoutés à la capacité RAM. +The exact source initialization sequence remains authoritative. -### Dimensions, couleurs et clavier +## Project lifetime boundary -Le layout complet demande au moins 100×30. Le layout compact est validé à la -frontière 72×20 et reste supporté jusqu'au minimum 60×15. En dessous, le rendu -se réduit au message borné `Terminal trop petit`; un resize recalcule la classe -sans faire travailler un worker. Les rôles sémantiques sont healthy vert, -warning jaune, error rouge, GPU cyan, CPU bleu, SSD magenta, plus dim/bold. -Les libellés textuels demeurent l'autorité lorsqu'il n'y a pas de couleur ou -pas assez de paires terminal. +Changing/closing project is an exact ownership boundary. -Les écrans courants sont accueil, projets, import, viewer futur, tâches, -ressources, optique, SSD et aide. `F1..F7` naviguent respectivement vers aide, -projets, import, viewer, tâches, ressources et optique. Le segment littéral -`F10 SSD` est réservé au début du footer et reste visible à 60 colonnes dans -tous les modes pertinents. Les footers dérivent du même mode que le handler : +Before Project DB close: -- saisie active : Enter valide, Échap annule, F10 reste disponible ; -- import actif : `X` demande l'annulation et F10 reste disponible ; `q` et - Échap sont affichés comme désactivés ; -- mode idle : `q`, Échap/navigation et les commandes propres à l'écran sont - annoncés seulement lorsqu'ils sont réellement traités ; -- Tasks : flèches/`j`/`k`, `P` pause, `R` reprise, `C` annulation ; -- Optique : Tab change de panneau, flèches/`j`/`k` sélectionnent, `[` revient à - la première page et `]` charge la suivante ; `B/L/C/V/A/G/K/E` déclenchent - les opérations indiquées et `R` retente explicitement un bind/chargement. +```text +views release DB borrows +-> Queue ingress closes +-> Queue cancels/joins/destroys +-> terminal callbacks finish +-> Project DB closes +``` -### Workflow optique +A fresh empty Queue can then be created for the next project. -La TUI consomme les API v23 décrites dans -[Project Database](project_database.md), sans SQL direct ni édition d'une ligne -immuable. Elle inspecte une affectation Capture, effectue seulement des lookup -metadata exacts, liste les profils de boîtier/objectif/configuration et accepte -un objectif manuel sans électronique ni alias — le Meike de test est un cas -normal, pas une branche produit. « Modifier » signifie créer un nouveau profil -ou une nouvelle configuration immuable, puis l'assigner explicitement à un -groupe de campagne encore éligible ou à un Capture non affecté. Les -calibrations listées doivent être compatibles avec l'exacte configuration et -la sélection reste explicite ; absence, ambiguïté, incompatibilité, BUSY, I/O -et corruption sont rendues sans profil fabriqué. Les pages ont 16 lignes, -rapportent un compte page-local et un indicateur « suite » exact. +No terminal callback may dereference a closed Project DB. -### SSD F10 et lifetime application +Queue terminal history belongs to the current runtime session and does not leak between projects. -La TUI affiche les huit états physiques `ABSENT`, `DETECTED`, `ENABLING`, -`ENABLED`, `IN_USE`, `DRAINING`, `SAFE_TO_UNPLUG` et `ERROR`, avec identité -stable, modèle/télémétrie lorsqu'ils sont connus, swap, scratch, mount, usage, -leases, drain et raison. `UNKNOWN` n'est jamais remplacé par zéro ou par une -supposition ; `SAFE_TO_UNPLUG` est mis en évidence comme endpoint sûr. F10 -choisit exclusivement l'une des capacités exactes -`can_enable`, `can_disable` ou `can_cancel_drain` publiée par le contrôleur ; un -état incomplet, une paire de remplacement ou un résultat malformé n'accorde -aucune autorité. L'opération synchrone UDisks s'exécute dans au plus un thread -joinable, tandis que le main continue de rendre et de poller sans blocage. +## Project Database -La validation qui alimente ces capacités est fail-closed par état : toute -autorité exige Drive et deux partitions détectés, identités Drive+UUID exactes, -extents positifs connus et faits mount/activité/drain cohérents. `ABSENT` ne -peut transporter aucun fait actif, `DETECTED` partiel n'a aucune action et un -hazard `ERROR` déconnecté ne peut que retenir l'identité originale sans -allocation. Seule la reconnexion complète de ce tuple peut autoriser son drain. +Project DB uses an opaque serialized SQLite connection with bounded transactional operations. -Après chaque observation ou résultat validé, l'adaptateur enregistre une copie -bornée de l'état physique auprès du Governor. Une copie malformée devient -`ERROR` et interdit les nouvelles allocations ; l'observation ressources lit -cet état Governor-owned, tandis que les détails/permissions F10 restent dans -le snapshot physique. La génération source peut saturer à `UINT64_MAX` : une -update publique égale ne réaccorde jamais une autorité stale ; seule la -complétion du wrapper exact déjà engagé réconcilie son lease adressé. À l'arrêt, -l'ordre est : destruction/join de la Queue et -libération de chaque lease Task, fermeture du projet/DB, join puis unregister -vérifié de l'adaptateur SSD, destruction du contrôleur, puis destruction du -Governor. Les tests utilisent un provider factice et n'exécutent aucune vraie -mutation SSD. +Current schema head: -## Invariants +```text +CURRENT_PROJECT_DB_SCHEMA=v25 +``` -- ncurses appartient exclusivement au thread principal -- Aucune tâche ne démarre sans réservation active -- Les réservations sont libérées exactement une fois -- Les buffers sont strictement bornés -- Le Resource Governor reste l'unique propriétaire de l'admission ; ni Queue, - ni contrôleur SSD ne constituent un second orchestrateur de ressources +Current additive selected-execution overlays include: -## Statut : CURRENT / VALIDATED OPERATIONAL +```text +v24 raw.develop.batch/1 +v25 features.extract.batch/1 +``` -La TUI/runtime et son raccordement SSD sont implémentés, testés et relus dans -leur tranche. Le statut global est -`GLOBAL_MAINTENANCE_AUDIT=PASS/FROZEN`. Les builds portables/Vulkan, -sanitizers, contrôles de concurrence et ABI frais sont acquis ; l'unique revue -finale indépendante a conclu PASS sans finding bloquant. +The v23 optical model remains valid but is no longer the schema head. + +## Current production Task inventory + +Production currently has sixteen Task Kinds. + +Important selected-execution additions: + +```text +raw.develop.batch/1 +features.extract.batch/1 +``` + +The Queue still has one active heavy callback; batch Task Kinds obtain throughput from bounded +participants inside that callback. + +Per-item atomicity does not require cross-item serial execution. + +## Resource Governor + +Every production Task goes through the Resource Governor, including fixed-resource Tasks. + +The canonical host policy is: + +```text +preserve defined interactive reserve +then maximize safe useful throughput +``` + +```text +RESOURCE_UTILIZATION_POLICY=MAXIMUM_SAFE_USEFUL_THROUGHPUT +SERIALISM_REQUIRES_PROOF=CANONICAL +``` + +Reference-host CPU counts are evidence, not portable constants. + +Pressure may reduce a later admission; healthy recovery may ramp useful width again. + +Swap/zram/scratch do not enlarge admitted RAM. + +UMA GPU memory is charged once against host RAM. + +## Internal parallelism + +Validated Task Kinds may execute: + +```text +one Queue owner callback +-> bounded participants +-> join +-> deterministic owner publication +``` + +Current examples include: + +- Feature selected batch; +- Visual Index; +- Candidate Pair; +- Matcher CPU work; +- Geometric Verifier outer parallel preparation; +- RAW selected batch. + +This is not inter-Task parallelism and does not create another global worker pool. + +## Current Matcher backend policy + +ORB Matcher normal production is Governor-owned AUTO. + +Eligible ORB work prefers the validated Vulkan backend. + +Fallback is complete CPU recomputation. + +Normal Vulkan contract: + +```text +inflight = 1 +helpers = 0 +useful batch <= 8 +``` + +Depth 2 remains validated private safety/benchmark capacity but was rejected as normal useful policy. + +SIFT/RootSIFT Matcher remains CPU. + +## Runtime observation + +The runtime observer borrows Queue/Governor state and publishes one bounded coherent snapshot for the +TUI. + +It does not retain Task userdata. + +Ordinary snapshots are rate-limited/coalesced. + +On observation failure, the previous bounded view may be retained and explicitly marked stale. + +Task observation includes live/pending/recent-terminal entries only within fixed capacity. + +No unbounded Project DB scan is performed per frame. + +## Durable progress and ETA + +Typed Tasks publish exact `completed/total` only after their Task-specific durable prefix is committed. + +The TUI must not infer exact scientific progress from: + +- Task name; +- message text; +- generic percentage. + +When exact counters exist, they are authoritative. + +Throughput/ETA needs enough positive-time progress observations. + +No-progress/pressure/restart cases become explicit states such as stalled, throttled or indeterminate +rather than fabricated precision. + +A terminal Task with inconsistent durable progress is visible as an integrity problem rather than +silently forced to 100%. + +## Pipeline observation + +Current observable stages include: + +```text +Acquisition +RAW +Quality +Features +Visual Index +Candidate +Matcher +GV +Tracks +Sparse SfM +Dense +``` + +Sparse SfM capability exists. + +Dense has no production Task Kind and remains not applicable/unimplemented at the current checkpoint. + +Historical S21/A6000 real campaigns have not executed Sparse SfM because known calibration is +unavailable for those campaigns. + +## TUI resource observation + +Resource UI may display known bounded values for: + +- active/admitted/available CPU; +- GPU presence/backend/busy/memory; +- RAM and `MemAvailable`; +- host reserve; +- swap state and active deltas; +- batch/inflight/helpers; +- IO; +- scratch; +- Governor pressure. + +The installed contract of the exact active Task is authoritative for that sequence. + +A diagnostic indexed only by Task Kind cannot automatically be attributed to another Task/sequence. + +Unknown values remain unknown. + +## TUI layout + +The validated layout classes remain bounded. + +Current thresholds include: + +```text +full layout >= 100x30 +compact boundary = 72x20 +supported minimum = 60x15 +``` + +Below the supported minimum, the UI uses its too-small-terminal fallback. + +Repository documentation is English. Any remaining non-English executable UI literal is legacy runtime +text and must be changed only in the explicitly scoped UI-language remediation pass; documentation does +not redefine executable behavior by pretending that source literal has already changed. + +## Navigation + +The current TUI provides screens for the implemented runtime surfaces, including: + +- home; +- projects; +- import; +- tasks; +- resources; +- optics; +- SSD; +- help; +- viewer placeholder/future surface. + +Key bindings and exact executable labels remain owned by the TUI source and its tests. + +Documentation should describe behavior rather than preserve stale localized literals as authority. + +## Optical workflow + +The TUI uses the public optical APIs introduced by the v23 overlay. + +It does not write optical SQLite rows directly. + +It supports explicit inspection/selection and immutable profile creation. + +Manual lenses without electronic metadata are valid data. + +No workflow may fabricate: + +- "unknown" lens identity; +- calibration compatibility; +- metadata match; +- focal/lens substitution. + +Calibration selection remains explicit and exact. + +## SSD F10 boundary + +The SSD UI reflects controller capability/state rather than inventing actions. + +The controller owns physical detection, pairing, mount/swap/scratch state and bounded UDisks operations. + +The Resource Governor owns scratch lease admission. + +A state that is incomplete, stale or physically inconsistent grants no control/lease authority. + +Scratch is storage capacity, never RAM. + +## Global shutdown + +Ownership shutdown preserves: + +```text +Task Queue / Task leases +-> project close +-> SSD operation join / Governor unregister +-> SSD controller +-> Resource Governor +``` + +A real outstanding scratch lease can block unregister and must remain an observable error. + +Do not abandon a live lease pointer. + +## Error/recovery model + +Runtime operations use local rollback and explicit publication boundaries. + +File asset publication plus SQLite is not treated as one distributed transaction. + +A successfully published physical asset followed by DB failure may leave a valid orphan. + +Recovery validates known durable representations; it does not guess or silently repair scientific +identity. + +## Current real checkpoint + +Current retained A6000 checkpoint: + +```text +real-a6000-pre-sfm-2026-09-02 +REAL_A6000_PRE_SFM=PASS/FROZEN +``` + +It exercised current runtime/Queue/Governor behavior through: + +```text +selected RAW batch +selected Feature batch +Visual Index +Candidate Pair +Matcher +Geometric Verifier v3 +Track Builder +``` + +The final continuation recorded deterministic restart/reuse and stopped with: + +```text +Sparse SfM Tasks 0 +Sparse Reconstructions 0 +Dense/MVS 0 +``` + +This is a current real runtime checkpoint, later than the historical global-maintenance checkpoint. + +Both remain valid for the boundaries they prove. + +## Current limits + +Current runtime intentionally does not provide: + +- multiple concurrent heavy Queue callbacks; +- generic inter-Task DAG scheduling; +- generic Task priorities beyond current Queue policy; +- generic autosave ahead of Task-specific durable publication; +- Dense/MVS production Task Kind; +- live capture/viewer reconstruction loop; +- generic scratch-consuming Task Kind. + +Those are future product/implementation decisions, not silently missing state. + +## Summary + +```text +CURRENT_PROJECT_DB_SCHEMA=v25 +CURRENT_PRODUCTION_TASK_KINDS=16 + +TASK_QUEUE_WORKERS=1 +ACTIVE_HEAVY_CALLBACKS=1 +INTER_TASK_PARALLELISM=NOT_IMPLEMENTED +INTERNAL_PARALLELISM=BOUNDED + +GENERIC_DAG=NOT_IMPLEMENTED +DENSE_TASK_KIND=NOT_IMPLEMENTED +CURRENT_SCRATCH_CONSUMING_TASK_KINDS=0 + +RESOURCE_UTILIZATION_POLICY=MAXIMUM_SAFE_USEFUL_THROUGHPUT +SERIALISM_REQUIRES_PROOF=CANONICAL + +REAL_A6000_PRE_SFM=PASS/FROZEN +``` diff --git a/docs/architecture/sparse_sfm.md b/docs/architecture/sparse_sfm.md index 12e5ec2..cf039dd 100644 --- a/docs/architecture/sparse_sfm.md +++ b/docs/architecture/sparse_sfm.md @@ -1,72 +1,111 @@ -# Sparse SfM / Triangulation — Gate A +# Sparse SfM / Triangulation -## Status and boundary +## Status -**GATE A — DECISION after contract and probe study.** This document defines the -scientific and architectural contract for the Sparse SfM layer. B2 implements -the immutable v16 persistence model and its bounded readers; it does not -implement a numerical solver, change Track Model v1, change Track Builder v1, -or add a Task Kind. `FACT`, `CANDIDATE`, `DECISION` and `FROZEN` remain explicit: -the upstream Track Model and Track Builder are FROZEN, while numerical Sparse -SfM remains deferred to later gates. +```text +SPARSE_SFM_V1=IMPLEMENTED +GATE_A=DECISION/HISTORICAL +GATE_B=PASS/FROZEN +GATE_C=PASS/FROZEN +GATE_D=PASS/FROZEN +GATE_E=PASS/FROZEN +GATE_F=PASS/FROZEN +GATE_G=PASS/FROZEN -**GATE B — PASS.** The v16 persistence model, migration, corruption/lifecycle -proofs, structural comparator, representative resource validation, normal -suite and ASan/UBSan closure are complete. Numerical Sparse SfM remains -deferred to Gate C and later gates. +REAL_S21_SPARSE_SFM=NOT_EXECUTED +REAL_A6000_SPARSE_SFM=NOT_EXECUTED +REAL_SPARSE_SFM_BLOCKER=KNOWN_CALIBRATION_DATA + +CURRENT_PROJECT_DB_SCHEMA=v25 +``` + +Sparse SfM v1 is implemented through Gate G. + +The opening Gate A/Gate B design history remains valid historical evidence, but numerical Sparse SfM is +no longer deferred: Gates C through G were implemented, validated and frozen after the original +decision/persistence work. + +The retained historical S21 and A6000 campaigns have **not** executed real Sparse SfM. Their current +blocker is the absence of known calibration data that satisfies the frozen calibration contract. This +is a data-eligibility boundary, not a missing Sparse SfM implementation. + +Dense/MVS is also unexecuted for the retained A6000 pre-SfM checkpoint. ## Scope -Sparse SfM consumes exactly one immutable, complete Track Set. It estimates a -set of camera poses and sparse 3D landmarks from coherent 2D observations. It -does not alter the Track Set, Match Results, GVRs or Feature Store. It does not -perform dense matching, meshing, texturing, metric alignment or bundle -adjustment in Gate A. +Sparse SfM consumes exactly one immutable complete Track Set plus an explicit immutable known-calibration +scope. -The input is one explicit Track Set identity, never “the latest Track Set” or -an enumeration of mutable project state. A disconnected image graph is -reconstructed as independent components, each with its own similarity gauge; -no metric or spatial relation between disconnected components is invented. +It estimates: -## Terminology and input contract +- connected reconstruction components; +- registered camera poses; +- sparse 3D landmarks; +- per-component diagnostics; +- final per-component Bundle Adjustment through the frozen Gate E contract. -- **Image**: an acquisition with immutable pixel dimensions and an `image_id`. -- **Calibration**: the immutable intrinsic model assigned to one image or an - explicit calibration group. -- **Pose**: the rigid transform relating world coordinates to one camera frame. -- **Track**: the frozen coherent set of 2D observations from Track Model v1. -- **Landmark**: a Sparse SfM-owned 3D estimate derived from zero or one Track; - it is never stored in Track Model v1. -- **Observation coordinate**: the Feature File keypoint `x,y`, not a descriptor - vector and not a coordinate inferred from a feature index. +It does not mutate: -The Feature Store v1/v2 Feature File is the canonical coordinate source. Its -keypoint records contain binary32 `x,y`, decoded image width/height, and use a -top-left origin with +x right and +y down. A Gate B reader must page keypoint -records by index; it must not load descriptors merely to obtain coordinates. +- Track Model v1; +- Track Builder v1; +- Geometric Verification Results; +- Match Results; +- Feature Sets; +- calibration records. -Input validation requires the Track Set to be complete and loadable, every -referenced Feature Set and Feature File to validate, every calibration to be -present and finite, and every observation index to remain within its Feature -Set. A corrupt upstream object is a runtime/input error, not an SfM outlier. +It does not perform: -## Camera and calibration decision +- unknown-intrinsics recovery; +- silent EXIF calibration fallback; +- metric scale inference; +- dense matching; +- meshing; +- texturing; +- multi-campaign metric registration. -### v1 supported calibration +## Input identity -**DECISION: known calibration only.** Sparse SfM v1 accepts an immutable -calibration for every input image. Unknown, partially known and shared-focal -estimation are rejected until a later model gate. EXIF focal data is advisory -input for constructing a calibration, never an implicit scientific fallback. +The scientific input is explicit. -This is deliberate for phone imagery: autofocus, digital crops, orientation, -rescaling and device variation make “all images share one perfect K” unsafe. -The calibration owner is therefore an explicit per-image or calibration-group -input whose membership and parameters are part of the reconstruction identity. +It contains one exact Track Set identity and one exact compatible calibration scope. -### Pinhole model +Never select inputs by: -The v1 camera model is pinhole with binary64: +```text +latest Track Set +latest calibration +most recent timestamp +path similarity +lens-name guess +EXIF-only guess +``` + +A disconnected image graph is reconstructed as independent components. No metric or spatial relation +between disconnected components is invented. + +## Calibration contract + +Sparse SfM v1 is **known-calibration only**. + +Every participating image must resolve to an immutable compatible calibration. + +Unknown, partially known or silently substituted calibration is rejected. + +EXIF focal metadata may help an explicit calibration workflow, but it is not a scientific fallback. + +Historical S21 and A6000 evidence remains `CALIBRATION_UNAVAILABLE`; it must not be retroactively +declared calibrated. + +The frozen future calibration path is owned by: + +- `calibration_science_v1.md`; +- `calibration_tooling.md`; +- `calibration_bootstrap.md`; +- `calibration_solver_preflight_v1.md`. + +## Camera model + +The v1 camera model is pinhole binary64: ```text K = [ fx 0 cx ] @@ -74,1413 +113,474 @@ K = [ fx 0 cx ] [ 0 0 1 ] ``` -Skew is fixed to zero. `fx > 0`, `fy > 0`, `0 <= cx < width`, and -`0 <= cy < height`. The supported distortion candidate is OpenCV-compatible -radial `k1,k2` plus tangential `p1,p2`; all four values are either supplied as -an immutable calibrated model or the model is explicitly zero-distortion. -Higher radial coefficients, rational models and thin-prism terms are not v1. -The exact distortion model and values are scientific identity fields. - -### Coordinates and pose - -Pixel coordinates are continuous binary64 coordinates with origin at the -top-left pixel corner, +x right and +y down. Pixel centers therefore have the -usual half-pixel interpretation supplied by the Feature File convention. A -calibrated point is undistorted first, then normalized: +with: ```text -xn = (u_undistorted - cx) / fx -yn = (v_undistorted - cy) / fy -ray_camera = normalize([xn, yn, 1]) +fx > 0 +fy > 0 +0 <= cx < width +0 <= cy < height ``` -The camera frame is right-handed with x right, y down and z forward. The world -frame is also right-handed but is otherwise a gauge choice. Pose is -**world-to-camera**: +Skew is zero. + +The supported distortion model is OpenCV-compatible: + +```text +k1, k2, p1, p2 +``` + +or an explicit zero-distortion calibration. + +Higher radial, rational and thin-prism models are outside v1. + +The exact distortion model and coefficients are scientific identity. + +## Coordinates and pose + +Feature File keypoint `x,y` is the canonical observation coordinate. + +The source convention is decoded-image pixel coordinates: + +```text +origin: top-left ++x: right ++y: down +``` + +The camera frame is right-handed with: + +```text +x right +y down +z forward +``` + +Pose is world-to-camera: ```text Xc = R_cw * Xw + t_cw Cw = -transpose(R_cw) * t_cw ``` -The public/persisted representation is a row-major binary64 rotation matrix -plus binary64 translation. Solver-private angle-axis or quaternion variables -are permitted later. A persisted quaternion is not required, so the `q/-q` -sign ambiguity is avoided. Rendering/FreeCAD coordinate transforms are -downstream export concerns and do not change this scientific convention. +The public/persisted representation uses a row-major binary64 rotation matrix plus binary64 translation. -### Gauge and scale +Private quaternion/angle-axis solver representations do not cross the C17 boundary. -Monocular reconstruction has a seven-degree-of-freedom similarity ambiguity. -For each connected reconstruction component, the deterministic seed camera is -the lowest canonical image ID in the selected seed pair. Its pose is fixed to -`R=I,t=0`. The second seed camera's translation direction is selected by the -deterministic essential decomposition and its norm is fixed to one arbitrary -world unit. The remaining gauge is thereby fixed to a unit seed baseline. +## Gauge and scale -This unit is not metres, millimetres or any physical scale. Metric scale, -absolute orientation and georeferencing require a future explicit alignment -stage using control distances, markers or surveyed points. No fake millimetres -are inferred from focal pixels, image resolution or baseline normalization. +Monocular Sparse SfM has a similarity ambiguity. -## Reconstruction strategy +Each connected component owns its own gauge. -**DECISION: incremental SfM followed by final per-component refinement.** It -matches the expected sequential vehicle/phone capture, -allows unregistered images to remain visible as a scientific result, and keeps -the active problem bounded. Global-only rotation/translation averaging would -add a larger initialization and robustness surface without a current project -requirement. A hybrid strategy is rejected for v1 complexity. - -### Seed selection and relative pose - -The seed is selected from the Track Set/covisibility graph, not raw Match -Results. Candidate pairs require at least the later configured minimum of -valid shared Tracks, non-degenerate essential geometry, positive-depth support -and measurable parallax. Candidates are sorted by a deterministic tuple: +The deterministic seed camera is fixed to: ```text -(-shared_track_count, -robust_parallax_score, image_id_a, image_id_b) +R = I +t = 0 ``` -The numerical thresholds are Gate B parameter candidates and must be -fingerprinted when frozen; this tuple is the ordering policy, not a descriptor -score. Pure rotation, near-zero baseline, planar ambiguity and insufficient -cheirality reject a seed rather than inventing a scale. +and the second seed establishes a unit arbitrary baseline. -Known intrinsics convert the upstream Fundamental relation into normalized -coordinates and an Essential candidate. Relative pose uses deterministic -essential decomposition with all four hypotheses tested by cheirality and -triangulation support. An F matrix is never treated as an E matrix. +That unit is not millimetres, metres or any physical scale. -### Registration +Metric scale and global alignment require a separate explicit downstream stage using measured control +information. -After the seed, an unregistered image is eligible when it has enough -Track-to-landmark correspondences to registered cameras. It is selected by -descending visible landmark count, then spatial-distribution score, then image -ID. Pose estimation uses a deterministic robust PnP candidate with fixed -binary64 validation, explicit iteration/confidence parameters and a local seed; -global OpenCV RNG state is forbidden. An image that cannot register remains -`UNREGISTERED` in the future SfM result and does not make the whole result a -runtime failure. Retry is bounded to deterministic graph-growth rounds; no -infinite retry loop exists. +No physical scale is inferred from: -### Components +- focal pixels; +- image resolution; +- normalized baseline; +- vehicle dimensions; +- device metadata. -Every connected image component is processed independently. A component with -fewer than two registered cameras has no valid 3D reconstruction. Components -with two valid cameras are allowed. Each accepted component carries its own -unit-baseline gauge and component ID; combining components requires a future -metric/alignment stage. +## Incremental reconstruction -## Triangulation decision +The frozen strategy is incremental SfM followed by final per-component refinement. -**DECISION: normalized-coordinate linear DLT initialization followed by -multi-view binary64 reprojection refinement when the acceptance checks pass.** -For a Track, all currently registered observations are used in a bounded linear -system; the result is dehomogenized only when finite and well-conditioned. A -small deterministic nonlinear point-only refinement may follow. The solver does -not mutate the Track and does not split it in v1. - -Accepted points require finite coordinates, positive depth in the required -observing cameras, a non-degenerate condition estimate, and reprojection -residuals within the frozen later threshold. Low parallax, planar/collinear -ill-conditioning, behind-camera points, non-finite values and excessive -reprojection error reject the landmark while leaving the source Track intact. -No arbitrary “best descriptor” or Match score is used. Pair quality is based -only on geometry; multi-view Tracks use all valid observations rather than a -random pair. Robust observation dropping is deferred: v1 rejects the landmark -as a whole, so Track identity and observation ownership remain simple. - -## Gate E v1 — Final Bundle Adjustment decision - -**GATE E — PASS / FROZEN.** The synchronous CPU-only final per-component -Bundle Adjustment implementation, E01--E35 matrix, normal suite, targeted -ASan/UBSan with LeakSanitizer, full sequential ASan/UBSan suite and at least -20 fresh-process E27 comparisons are validated. Gate F project orchestration -is now **PASS / FROZEN**; Gate G resource integration is **PASS / FROZEN**. - -**DECISION: Gate E v1 is a synchronous, independent final per-component Bundle -Adjustment applied as post-processing to a copy of the immutable final Gate D -result.** It consumes two caller-owned immutable views that must remain coherent -for the complete call: that final Gate D result, and the same resolved -observation/calibration view used to construct the scientific Gate D input. It -never mutates either view, never creates constraints between disconnected -components, preserves each component's independent gauge, and produces a -distinct in-memory BA result. - -The Gate D result alone is authoritative for final components, registered -cameras, initial poses, landmarks and the observations associated with each -landmark. The second view only resolves an observation already published by -Gate D. Its canonical key is `(feature_set_id, feature_index)`; resolution must -return the matching `image_id`, source keypoint `x,y` and immutable calibration, -and must also agree with the published Track and image identities. Missing, -ambiguous, duplicate or inconsistent resolution is a Gate E input error. Gate E -must not use array position, proximity or another heuristic fallback, add an -observation, restore a rejected association or camera, or rerun incremental SfM. - -Source keypoint coordinates are the Feature File binary32 `x,y` in decoded-image -pixels, with top-left origin, +x right and +y down. Gate E converts them to -binary64 for computation; it does not treat them as already undistorted or -normalized. Given `Xc = R_cw * Xw + t_cw`, define `xn = Xc.x / Xc.z`, -`yn = Xc.y / Xc.z`, and `r2 = xn*xn + yn*yn`. The canonical OpenCV-compatible -forward model is: +The high-level flow is: ```text +Track Set + known calibration +-> build deterministic covisibility +-> choose valid deterministic seed +-> recover relative pose +-> triangulate supported Tracks +-> register additional cameras with deterministic calibrated PnP +-> grow landmarks +-> stop on bounded graph-growth convergence +-> final per-component Bundle Adjustment +-> publish immutable Sparse Reconstruction +``` + +Unregisterable images remain scientifically unregistered; they do not become a runtime failure by +themselves. + +Disconnected components remain separate. + +## Seed selection + +Seed candidates come from the Track/covisibility graph rather than raw descriptor score. + +Candidates are ordered deterministically by the frozen policy, including shared-track support, +geometric/parallax quality and canonical image IDs. + +Pure rotation, negligible baseline, insufficient cheirality and other frozen degeneracy conditions +reject a seed. + +A Fundamental matrix is never treated as an Essential matrix. Known intrinsics are used explicitly. + +## Camera registration + +An unregistered image becomes eligible only from explicit Track-to-landmark correspondences. + +Selection is deterministic. + +Pose estimation uses the frozen calibrated robust PnP path with bounded attempts and local deterministic +randomness. + +Global OpenCV RNG mutation is forbidden. + +A failed image remains `UNREGISTERED`. + +## Triangulation + +Triangulation uses normalized-coordinate linear DLT initialization and the frozen bounded refinement +path. + +All valid registered observations of a Track participate. + +The source Track is immutable and is not split by Sparse SfM v1. + +A landmark is rejected on frozen invalid conditions such as: + +- non-finite values; +- invalid depth; +- insufficient parallax; +- ill-conditioning; +- excessive reprojection error. + +Track identity remains unchanged. + +## Gate lifecycle + +### Gate A — historical decision + +Gate A established the scientific architecture, known-calibration requirement, camera convention, +gauge, deterministic incremental strategy and triangulation direction. + +The original text that said numerical Sparse SfM would be implemented later is historical lifecycle +language. + +### Gate B — persistence + +Gate B froze the immutable Project DB persistence model and bounded readers. + +Project DB v16 is the Sparse Reconstruction persistence foundation. + +Later Project DB migrations through v25 are additive and do not reinterpret Sparse SfM identity. + +### Gate C — geometry + +Gate C implemented and validated the calibrated geometry primitives required by the Sparse SfM core, +including deterministic relative pose, triangulation, calibrated PnP and degeneracy rejection. + +Status: + +```text +GATE_C=PASS/FROZEN +``` + +### Gate D — incremental core + +Gate D implemented the synchronous in-memory incremental Sparse SfM core. + +It consumes explicit immutable inputs and produces deterministic per-component sparse reconstruction +state without persistence side effects inside the scientific core. + +Status: + +```text +GATE_D=PASS/FROZEN +``` + +### Gate E — final Bundle Adjustment + +Gate E is a synchronous CPU-only final per-component Bundle Adjustment over a private copy of the +final Gate D result. + +It never mutates Gate D input. + +Known intrinsics/distortion, Track membership and observations remain fixed. + +Status: + +```text +GATE_E=PASS/FROZEN +``` + +### Gate F — project/task orchestration + +Gate F connected the frozen scientific path to the Project/Task persistence boundary. + +The production Task Kind is: + +```text +sparse_sfm.run/1 +``` + +The Task restores explicit scientific inputs from its typed payload and recomputes from those immutable +inputs rather than persisting transient solver state. + +Status: + +```text +GATE_F=PASS/FROZEN +``` + +### Gate G — Resource Governor integration + +Gate G integrated Sparse SfM with the existing Resource Governor. + +Its frozen resource contract remains conservative and atomic. + +The historical fixed CPU1/batch1 estimate is a property of this frozen Task, not a global rule for +other Task Kinds. + +Status: + +```text +GATE_G=PASS/FROZEN +``` + +## Gate E Bundle Adjustment contract + +Gate E works independently per reconstructed component. + +It resolves observations by the canonical key: + +```text +(feature_set_id, feature_index) +``` + +Resolution must agree with image, Track, source keypoint and calibration identity. + +No heuristic fallback is allowed. + +The canonical distorted forward projection is: + +```text +xn = Xc.x / Xc.z +yn = Xc.y / Xc.z +r2 = xn*xn + yn*yn + radial = 1 + k1*r2 + k2*r2*r2 xd = xn*radial + 2*p1*xn*yn + p2*(r2 + 2*xn*xn) yd = yn*radial + p1*(r2 + 2*yn*yn) + 2*p2*xn*yn + u = fx*xd + cx v = fy*yd + cy -residual = [u - observed_x, v - observed_y] ``` -The residual is therefore binary64 in source pixels and uses the complete -canonical calibration model. A private Gate D validation helper that omits -distortion does not redefine this contract and is not a precedent for Gate E. -The second immutable view is an explicit scientific input, not persistence, -Project DB integration, a loader, resolver subsystem, cache, handle or Resource -System. +Residuals are binary64 source-pixel errors. -### Scientific and numerical contract +### BA variables -Gate E processes every reconstructed Gate D component independently. It -resolves the selected observations, copies the component poses and landmarks -into a private working set, builds and solves one BA problem, validates the -complete candidate, then either publishes that candidate in the distinct Gate -E result or preserves the original Gate D component. No component constrains or -influences another component. +Gate E optimizes only: -Gate E v1 optimizes only camera extrinsic rotations, camera centers and -landmark positions. The known `fx`, `fy`, `cx`, `cy`, `k1`, `k2`, `p1`, `p2`, -observations, Track membership, identities and observation/landmark -associations are fixed and immutable. Future intrinsic optimization requires a -separate scientific and identity decision. +- camera extrinsic rotation; +- camera center; +- landmark position. -The public boundary remains solver-independent and world-to-camera. The private -C++ adapter uses a unit quaternion for `R_cw`, with an appropriate quaternion -manifold, and world camera center `Cw`: +It does not optimize: -```text -Xc = R_cw * (Xw - Cw) -t_cw = -R_cw * Cw -``` +- `fx`, `fy`, `cx`, `cy`; +- `k1`, `k2`, `p1`, `p2`; +- Track membership; +- observation identity; +- calibration identity. -Conversion to or from public rotation matrices canonicalizes quaternion sign, -so `q` and `-q` cannot produce distinct observable representations. No Ceres -type crosses the future C17 ABI. +### Gauge anchors -### Gate E gauge +The lowest `image_id` registered camera is the fixed pose anchor. -Gate E derives deterministic BA anchors from the final Gate D result and does -not depend on historical seed IDs. In each component, the registered camera -with the lowest `image_id` is the pose anchor; its complete initial Gate D -rotation and camera center are fixed. +The deterministic farthest camera supplies the scale anchor, with exact tie-breaks. -Among the other registered cameras, the scale anchor is the camera whose -binary64 Euclidean distance from the pose anchor is greatest. An exact distance -tie selects the lowest `image_id`; no hidden tolerance participates. For -`delta = C_scale - C_anchor`, the coordinate with greatest absolute value is -the scale axis, with exact ties resolved X, then Y, then Z. That one initial -Gate D coordinate of `C_scale` is fixed. Its other two center coordinates and -its rotation remain variable. The fixed pose removes the six rigid degrees of -freedom and the fixed nonzero scale coordinate removes the scale degree of -freedom without fixing a second pose. +One coordinate of the scale-anchor center is fixed according to the frozen rule. -The scale anchor is degenerate when: +Degenerate scale anchor: ```text max(abs(delta.x), abs(delta.y), abs(delta.z)) <= 1e-9 ``` -Gate D fixes each valid component to a unit seed baseline, so `1e-9` world -units is a numerically negligible separation in that scientific gauge. A -component with no second valid camera or a degenerate scale anchor is not -optimized; its Gate D data is retained with a gauge/degenerate diagnostic. +Such a component is not optimized. -### Objective and solver +### Robust objective -Every valid observation contributes one two-dimensional source-pixel residual -block `f_i = [dx, dy]`, where `dx = predicted_x - observed_x` and -`dy = predicted_y - observed_y`. Gate E uses binary64 throughout. Define: +Each observation is one 2D residual block. + +With Huber delta `2.0` pixels: ```text -s_i = dx*dx + dy*dy -delta = 2.0 -delta2 = 4.0 +s = dx*dx + dy*dy -rho_delta(s) = s if s <= delta2 -rho_delta(s) = 2*delta*sqrt(s) - delta2 if s > delta2 +rho(s) = s if s <= 4 +rho(s) = 4*sqrt(s) - 4 otherwise -robust_cost = 0.5 * sum_i(rho_delta(s_i)) +robust_cost = 0.5 * sum(rho(s)) ``` -Thus, with `delta = 2.0` source pixels, the second branch is -`4.0*sqrt(s) - 4.0`. The Huber loss applies once to the norm squared of the -complete 2D observation, never independently to `dx` and `dy`; the factor -`0.5` is contractual. Each observation must likewise be one 2D Ceres residual -block, not two scalar blocks. +The Huber loss applies to the complete 2D residual norm, not independently to `dx` and `dy`. -Lardon3D computes initial and final robust costs independently of the solver -using exactly this formula, and those values govern acceptance. Ceres summary -costs may only diagnose or cross-check them. With the identical problem, a -disagreement beyond the applicable numerical tolerance stops implementation -for contract review; neither value silently replaces the other. Non-finite -`dx`, `dy`, `s_i`, `rho_delta(s_i)`, accumulation, pose, landmark or projection, -and camera-frame depth invalid under the frozen camera invariants, reject the -candidate. No clamp or fallback is permitted. +### Solver -The Huber kind and scale are Gate E scientific policy, not Governor parameters, -Resource parameters or a Gate E fingerprint. +Gate E uses the frozen Ceres 2.2.x CPU path with explicit deterministic ordering and one solver thread. -Gate E v1 selects the Ceres Solver 2.2.x API, CPU-only, with these explicit -options: +Key frozen choices include: ```text -minimizer_type = TRUST_REGION -trust_region_strategy_type = LEVENBERG_MARQUARDT -linear_solver_type = ITERATIVE_SCHUR -preconditioner_type = SCHUR_JACOBI +TRUST_REGION +LEVENBERG_MARQUARDT +ITERATIVE_SCHUR +SCHUR_JACOBI num_threads = 1 max_num_iterations = 50 -function_tolerance = 1e-6 -gradient_tolerance = 1e-10 -parameter_tolerance = 1e-8 ``` -Landmark parameter blocks form elimination group 0 in increasing canonical -Track/landmark identity; camera blocks form group 1 in increasing `image_id`. -Components, cameras, landmarks, observations and residual blocks are all built -in canonical identity order. Automatic Ceres ordering is not used when the API -accepts an explicit ordering. +No GPU/CUDA Bundle Adjustment belongs to Gate E v1. -There is exactly one solver attempt per eligible component, with no automatic -retry, wall-clock timeout or `max_solver_time`. Environment variables, hardware -profiles, Tasks, schedulers and the Governor cannot change the single-thread -reference. `ITERATIVE_SCHUR` with `SCHUR_JACOBI` provides the required -block-sparse path without a functional SuiteSparse dependency; `SPARSE_SCHUR`, -CUDA and GPU execution are not Gate E v1. +### Structural eligibility -### Eligibility, bounds and acceptance +The frozen manifest-underconstraint checks include the explicit camera/landmark/observation degrees of +freedom and graph-support checks defined by Gate E. -An eligible component has at least two registered cameras, at least one valid -BA landmark, exactly resolved observations and calibrations, finite inputs, a -valid gauge, overflow-safe dimensions and no manifest underconstraint after -the anchors. Gate D already guarantees multi-view support for every published -landmark, so Gate E introduces no separate support threshold. +They are structural checks, not a numerical rank claim. -For a component with a valid non-degenerate Gate E gauge, let `C` be its -registered camera count, `P` its optimized landmark count and `O` its retained, -resolved observation count. Camera intrinsics and distortion are fixed. The -free tangent dimension is therefore: +No dense camera-by-landmark matrix is permitted. + +## Persistence + +Sparse Reconstruction persistence was introduced by the frozen Sparse SfM Project DB lineage. + +The typed Sparse SfM Task payload was added separately for deterministic restart. + +Current schema: ```text -free_dof = 6*C + 3*P - 7 -scalar_residual_count = 2*O +CURRENT_PROJECT_DB_SCHEMA=v25 ``` -The completely fixed pose anchor removes six degrees of freedom, and the fixed -scale-anchor center coordinate removes one. Gate E v1 defines **manifest -underconstraint** as at least one of these exact structural conditions: +Later v22/v23/v24/v25 additions do not alter Sparse SfM scientific identity. -- **UC1:** `2*O < 6*C + 3*P - 7`, using overflow-checked integer arithmetic; -- **UC2:** an optimized landmark is observed by fewer than two distinct - registered cameras; -- **UC3:** an optimizable camera, including the scale anchor but excluding the - completely fixed pose anchor, observes fewer than three distinct landmarks; -- **UC4:** the bipartite camera-landmark optimization graph is not one connected - component containing the pose anchor. +## Restart -E19 evaluates UC1--UC4 only after the existing structural validation and valid -anchor selection. E18 remains the existing insufficient-camera case and is not -redefined by E19. These conditions are necessary structural checks, not proof -of full numerical rank. Gate E v1 performs no numerical rank estimate, SVD, -singular-value or condition-number threshold, Jacobian/Hessian rank epsilon, or -Ceres covariance/rank heuristic for E19. Geometry that passes UC1--UC4 can -still be rejected by the existing projection, solver termination, finite-value, -cost non-regression and atomic-publication contracts. +`sparse_sfm.run/1` is reconstructed from explicit durable references. -UC1--UC4 are deterministic and solver-independent. Their implementation uses -the existing canonical flat Gate E working set and temporary storage bounded by -`O(C + P + O)` or better. It uses no hash-order dependency, dense `C * P` -storage, materialized rank matrix or new Resource subsystem. +Restart does not persist or revive: -Gate E retains the identically-scoped Gate D bounds of at most 4096 registered -cameras, 250,000 Tracks/landmarks and 1,000,000 observations. The Gate D -landmarks-per-growth-round bound is not a Gate E bound. All allocation and -dimension arithmetic is overflow-checked. The architecture is block-sparse; -no dense camera-count × landmark-count allocation or Jacobian is permitted. +- Ceres internal state; +- OpenCV internal state; +- temporary triangulation buffers; +- partial in-memory reconstruction; +- random engine state outside the frozen deterministic derivation. -Ceres `NO_CONVERGENCE` is rejection even if an intermediate candidate has -lower cost. Only a termination classified as successful convergence by the -private Ceres adapter is acceptable. The robust-cost comparison uses exactly: +The atomic frozen execution recomputes from the immutable inputs. + +## Resource policy + +Sparse SfM's frozen execution remains CPU1/batch1. + +That serialism is explicitly justified by its frozen scientific/numerical validation boundary. + +It must not be used as evidence that unrelated stages should run serially. ```text -cost_tolerance = 1e-12 * max(1.0, abs(initial_robust_cost)) -final_robust_cost <= initial_robust_cost + cost_tolerance +SERIALISM_REQUIRES_PROOF=CANONICAL +RESOURCE_UTILIZATION_POLICY=MAXIMUM_SAFE_USEFUL_THROUGHPUT ``` -This comparison tolerance absorbs insignificant binary64 noise and is not a -Ceres convergence tolerance. A component is published only when its inputs are -coherent and eligible, termination is accepted, all candidate poses, -landmarks, required projections and robust costs are finite, both gauge anchors -are strictly preserved in their contract representations, the cost condition -holds, and no consumed frozen invariant is violated. Otherwise the original -Gate D component is preserved exactly and accompanied by a rejection -diagnostic. All optimization occurs on a private copy, so publication is atomic -per component and requires no in-place rollback. +For Sparse SfM v1, the proof chooses the conservative atomic contract. -### Result and diagnostics +## Current real-execution boundary -The solver-independent Gate E result has these conceptual states: +### Historical S21 -- `COMPLETE`: at least one component is eligible and every eligible component - is optimized and accepted; -- `PARTIAL`: at least one component is accepted and at least one other eligible - component is rejected or fails; -- `FAILED`: no eligible component produces an accepted BA result, including an - input with no eligible component. +The retained S21 real proof reached a frozen Track Set. -`Lardon3DSparseBundleAdjustmentStatus` contains only these three scientific -result states. In particular, `FAILED` is not an invalid-argument, -out-of-memory or internal execution error. +It did not execute Sparse SfM. -The synchronous execution function returns the separate, -solver-independent `Lardon3DSparseBundleAdjustmentExecutionStatus`: +The historical S21 campaign cannot be retrofitted with invented calibration. + +### Historical A6000 + +The retained A6000 pre-SfM proof reached: ```text -LARDON3D_SPARSE_BUNDLE_ADJUSTMENT_EXECUTION_OK -LARDON3D_SPARSE_BUNDLE_ADJUSTMENT_EXECUTION_INVALID_ARGUMENT -LARDON3D_SPARSE_BUNDLE_ADJUSTMENT_EXECUTION_OUT_OF_MEMORY -LARDON3D_SPARSE_BUNDLE_ADJUSTMENT_EXECUTION_INTERNAL_ERROR +Feature Sets 689 +Candidate Pairs 38,420 +Match Results 38,420 +Applicable GVRs 37,805 +Verified GVRs 10,952 +Rejected GVRs 26,853 +Track Set 1 +Tracks 130,714 +Track observations 318,944 ``` -`EXECUTION_OK` means the public input was structurally valid, Gate E reached a -complete scientific decision and produced the owned result. Its scientific -status may be `COMPLETE`, `PARTIAL` or `FAILED`; `EXECUTION_OK` with scientific -`FAILED` is valid and means that no eligible component was accepted. - -`EXECUTION_INVALID_ARGUMENT` covers a violated public input contract, including -pointer/count, bounds, identity, finiteness, observation-resolution or -Gate-D/result-view coherence failures. `EXECUTION_OUT_OF_MEMORY` covers an -allocation failure, including `std::bad_alloc` caught at the C/C++ boundary, -that prevents production of a complete scientific result. -`EXECUTION_INTERNAL_ERROR` is reserved for an unexpected internal failure that -prevents safe completion; it is not a component-rejection fallback. Normal -component rejection for insufficient cameras, gauge degeneracy, manifest -underconstraint, invalid candidate projection, solver `NO_CONVERGENCE` or -`FAILURE`, a non-finite candidate or robust-cost regression contributes only to -the scientific `COMPLETE`/`PARTIAL`/`FAILED` result. - -On every execution status other than `EXECUTION_OK`, the public result remains -in its canonical zero state: all counts are zero, all owned array and diagnostic -pointers are null, and destruction is safe. The execution function never -publishes a partial owned result and then returns an execution error. - -Ineligible and rejected components retain their Gate D data. Each component -diagnostic contains at least component key, camera/landmark/observation counts, -pose-anchor and scale-anchor `image_id`, scale axis X/Y/Z, initial and final -robust cost, initial and final reprojection RMSE, iteration count, solver -termination class, accepted/rejected state and rejection reason. It exposes no -Ceres pointer or type. - -Diagnostic reprojection RMSE is non-robust: +Checkpoint: ```text -sqrt(sum(dx*dx + dy*dy) / observation_count) +real-a6000-pre-sfm-2026-09-02 +REAL_A6000_PRE_SFM=PASS/FROZEN ``` -Acceptance remains based on robust cost and all contract invariants. Raw RMSE -is not required to improve universally in the presence of outliers. +It then stopped. -### Reproducibility - -For identical input, executable, build, dependency versions and machine with -one solver thread, component order, anchors, parameter/residual ordering, -states, accept/reject decisions and structural diagnostics are deterministic. -Comparable binary64 geometric scalars satisfy: +The retained project contains: ```text -abs(a - b) <= 1e-12 * max(1.0, abs(a), abs(b)) +sparse_sfm_tasks=0 +sparse_reconstructions=0 +dense/mvs execution=0 ``` -Rotations are compared geometrically rather than by raw quaternion sign. If -fresh-process tests in an identical environment cannot meet this tolerance, -implementation stops for contract review; tests must not widen it silently. +Known calibration is unavailable for that historical campaign. -### Canonical Gate E validation matrix - -| Case | Contract evidence | -|---|---| -| E01 Null/invalid input | Safe rejection; no exception crosses C | -| E02 Empty/non-eligible result | Deterministic `FAILED` with diagnostics | -| E03 Clean synthetic component | Finite accepted result, gauge held, cost non-regression | -| E04 Perturbed poses | Fixture-defined measurable improvement | -| E05 Perturbed landmarks | Fixture-defined measurable improvement | -| E06 Perturbed poses and landmarks | Convergence and fixture-defined improvement | -| E07 Noise 0.5 px | Finite accepted result or contractually justified rejection | -| E08 Noise 1.0 px | Finite accepted result or contractually justified rejection | -| E09 Noise 2.0 px | Finite accepted result or contractually justified rejection | -| E10 Outliers 10% | Huber active; finite result or clean rejection; no invariant violation | -| E11 Outliers 20% | Huber active; finite result or clean rejection; no invariant violation | -| E12 Outliers 40% | Huber active; finite result or clean rejection; no invariant violation | -| E13 Disconnected components | Independent optimization and gauges | -| E14 One success, one failure | Global `PARTIAL` | -| E15 Pose anchor | Initial rotation and center strictly preserved | -| E16 Scale anchor | Selected center coordinate strictly preserved | -| E17 Deterministic anchors | Exact distance/ID and X/Y/Z ties; `1e-9` degeneracy boundary | -| E18 Insufficient cameras | No solve; Gate D data retained | -| E19 Underconstrained geometry | No solve; Gate D data retained | -| E20 Non-finite input | Input rejection | -| E21 Non-finite projection candidate | Atomic candidate rejection | -| E22 Forced non-convergence | Private summary interpreter rejects `NO_CONVERGENCE` | -| E23 Candidate regression | Cost condition prevents publication | -| E24 Atomic rejection | Original component preserved exactly | -| E25 Canonical ordering | Explicit groups and parameter/residual order | -| E26 Same-process repeats | Structural equality and numeric tolerance | -| E27 Fresh-process repeats | At least 20 processes in one identical environment | -| E28 Ownership/destruction | Caller inputs retained; owned result safely destroyed | -| E29 Null/repeated destroy | Required only if E1 adopts the existing null-safe convention | -| E30 Allocation/overflow | Checked rejection before allocation | -| E31 Maximum boundary guards | Exact documented limits without a giant solve where isolatable | -| E32 Sparse architecture | No dense camera-count × landmark-count allocation | -| E33 Calibration immutability | Before/after identical | -| E34 Track/observation immutability | Before/after identical | -| E35 Gate D immutability | Input unchanged after success and every failure path | - -E22 tests the private solver-summary-to-decision interpreter directly. It does -not expose an iteration override, add a production behavior for testing or -change `max_num_iterations = 50`. Synthetic ground-truth fixtures measure -pre/post geometric error and robust cost. Fixtures intended to improve define -their own scientifically measurable improvement; no universal pose or landmark -threshold is invented. - -The complete E01--E35 matrix is implemented and validated. E27 passed at least -20 fresh processes using exact structural comparison, the frozen binary64 -tolerance and geometric rotation comparison. - -Local BA after registration is deferred. Gate D exposes no intermediate -scientific seam or complete registration history, and an interleaved BA could -change its subsequent growth. Introducing that policy requires a future -explicit scientific seam/version architecture decision; Gate E v1 does not -create or name such a version. - -Gate E v1 remains independent of Project DB, Task Runtime, the Resource -Governor and any Resource System. It neither computes nor carries a parameter -fingerprint, defines no persistent identity, and publishes nothing. Gate F -retains project/task orchestration and persistence; Gate G retains Resource -Governor integration and final resource validation. - -Ceres availability on a host remains distinct from Lardon3D dependency -declaration. Gate E declares Ceres Solver `>=2.2.0,<2.3.0` through Meson CMake -discovery and uses its 2.2.x CPU API. CUDA is not required and Lardon3D has no -functional direct SuiteSparse dependency. - -## Determinism and scientific identity - -Canonical order is: component image IDs, seed tuple, registration candidates, -Track IDs, observation positions, and output landmarks by `(component_id, -track_id)`. No unordered container iteration, wall-clock value, queue position, -RAM state or task ID may affect science. Binary64 is the default for geometry, -residuals and persisted values; all accepted values must be finite. - -The candidate reconstruction identity is: +Therefore: ```text -(input_track_set_identity, - calibration_scope_identity, - sfm_kind="incremental", - sfm_version, - parameter_fingerprint) +SPARSE_SFM_IMPLEMENTATION=AVAILABLE +REAL_A6000_SPARSE_SFM=BLOCKED_BY_KNOWN_CALIBRATION_DATA ``` -Runtime task IDs, worker count, Governor state, pause timing and resource -observations are excluded. Any output-changing threshold, camera model, -initialization policy, triangulation policy, PnP policy, BA policy, precision or -loss parameter belongs in the parameter fingerprint materialized by the Gate F -persistent-identity seam. Gate D and Gate E neither receive, compute, serialize -nor transport it. Project DB stores it but does not own its meaning; Task Runtime -and the Resource System are also excluded. This seam is a Gate F orchestration -responsibility, not a new subsystem or scientific solver gate. +These statements are compatible and must not be collapsed into either "Sparse SfM is not implemented" +or "real Sparse SfM already ran". -### Sparse SfM parameter fingerprint v1 - -The parameter fingerprint is `SHA-256(record_v1)`, with one hash operation over -the exact fixed record below. Its output is the complete 32-byte digest. The -record begins with the eight ASCII bytes `L3DSFMFP` (`4c 33 44 53 46 4d 46 50`), -without a NUL byte, followed by `fingerprint_encoding_version=1`. Every -multi-byte scalar is little-endian. `f64` means the exact finite IEEE-754 -binary64 bit pattern, with both signed zeros encoded as positive zero; NaN and -infinity are invalid. Categorical values are the explicit `u32` policy IDs -defined here, never native enum ordinals. No native struct, padding, pointer, -`size_t`, host-endian value, JSON or locale-dependent text is hashed. A boolean, -if a future encoding uses one, is `u8`, with false `0` and true `1`; v1 has no -boolean field and no padding or reserved bytes. - -| Offset | Width | Field | Canonical type | Source/value | -|---:|---:|---|---|---| -| 0 | 8 | `domain` | ASCII bytes | `L3DSFMFP` | -| 8 | 4 | `fingerprint_encoding_version` | `u32` | `1` | -| 12 | 4 | `minimum_seed_tracks` | `u32` | effective Gate D parameter | -| 16 | 4 | `minimum_seed_landmarks` | `u32` | effective Gate D parameter | -| 20 | 4 | `minimum_pnp_correspondences` | `u32` | effective Gate D parameter | -| 24 | 4 | `maximum_seed_candidates` | `u32` | effective Gate D parameter | -| 28 | 4 | `maximum_registration_rounds` | `u32` | effective Gate D parameter | -| 32 | 4 | `maximum_landmarks_per_round` | `u32` | effective Gate D parameter | -| 36 | 4 | `maximum_images` | `u32` | effective Gate D parameter | -| 40 | 8 | `maximum_observations` | `u64` | effective Gate D parameter | -| 48 | 8 | `maximum_tracks` | `u64` | effective Gate D parameter | -| 56 | 8 | `reprojection_threshold_px` | `f64` | effective Gate D parameter | -| 64 | 8 | `minimum_track_parallax_rad` | `f64` | effective Gate D parameter | -| 72 | 8 | `relative_pose.robust_threshold_px` | `f64` | effective Gate D parameter | -| 80 | 8 | `relative_pose.confidence` | `f64` | effective Gate D parameter | -| 88 | 4 | `relative_pose.max_iterations` | `u32` | effective Gate D parameter | -| 92 | 4 | `relative_pose.minimum_inliers` | `u32` | effective Gate D parameter | -| 96 | 8 | `relative_pose.minimum_inlier_ratio` | `f64` | effective Gate D parameter | -| 104 | 8 | `relative_pose.minimum_parallax_rad` | `f64` | effective Gate D parameter | -| 112 | 8 | `relative_pose.minimum_cheirality_ratio` | `f64` | effective Gate D parameter | -| 120 | 8 | `relative_pose.deterministic_seed` | `u64` | effective Gate D parameter | -| 128 | 8 | `pnp.reprojection_threshold_px` | `f64` | effective Gate D parameter | -| 136 | 8 | `pnp.confidence` | `f64` | effective Gate D parameter | -| 144 | 4 | `pnp.max_iterations` | `u32` | effective Gate D parameter | -| 148 | 4 | `pnp.minimum_inliers` | `u32` | effective Gate D parameter | -| 152 | 8 | `pnp.minimum_inlier_ratio` | `f64` | effective Gate D parameter | -| 160 | 8 | `pnp.deterministic_seed` | `u64` | effective Gate D parameter | -| 168 | 4 | `refinement.max_iterations` | `u32` | effective Gate D parameter | -| 172 | 8 | `refinement.convergence_tolerance` | `f64` | effective Gate D parameter | -| 180 | 4 | `camera_model_policy` | `u32` | `PINHOLE_K1_K2_P1_P2_V1=1` | -| 184 | 4 | `calibration_policy` | `u32` | `KNOWN_FIXED_CALIBRATION_V1=1` | -| 188 | 4 | `source_pixel_policy` | `u32` | `SOURCE_PIXEL_TOP_LEFT_X_RIGHT_Y_DOWN_V1=1` | -| 192 | 4 | `pose_policy` | `u32` | `WORLD_TO_CAMERA_R_CW_CW_V1=1` | -| 196 | 4 | `seed_ranking_policy` | `u32` | `SHARED_PARALLAX_IMAGE_ID_V1=1` | -| 200 | 4 | `robust_seed_policy` | `u32` | `LOCAL_DETERMINISTIC_SEED_V1=1` | -| 204 | 4 | `next_image_policy` | `u32` | `VISIBLE_SUPPORT_THEN_IMAGE_ID_V1=1` | -| 208 | 4 | `track_order_policy` | `u32` | `CANONICAL_TRACK_OBSERVATION_V1=1` | -| 212 | 4 | `component_policy` | `u32` | `DISCONNECTED_INDEPENDENT_COMPONENTS_V1=1` | -| 216 | 4 | `triangulation_policy` | `u32` | `NORMALIZED_DLT_POINT_REFINE_V1=1` | -| 220 | 4 | `landmark_rejection_policy` | `u32` | `WHOLE_LANDMARK_ACCEPT_OR_REJECT_V1=1` | -| 224 | 4 | `cheirality_policy` | `u32` | `POSITIVE_DEPTH_THRESHOLD_V1=1` | -| 228 | 4 | `gate_d_numeric_policy` | `u32` | `BINARY64_DETERMINISTIC_V1=1` | -| 232 | 4 | `ba_mode` | `u32` | `FINAL_PER_COMPONENT_POSTPROCESS_V1=1` | -| 236 | 4 | `local_ba_policy` | `u32` | `LOCAL_BA_DISABLED_V1=1` | -| 240 | 4 | `ba_optimized_variables` | `u32` | `ROTATION_CENTER_LANDMARK_XYZ_V1=1` | -| 244 | 4 | `ba_fixed_inputs` | `u32` | `INTRINSICS_DISTORTION_OBSERVATIONS_IDENTITIES_V1=1` | -| 248 | 4 | `ba_pose_representation` | `u32` | `UNIT_QUATERNION_R_CW_PLUS_CW_V1=1` | -| 252 | 4 | `ba_gauge_policy` | `u32` | `MIN_IMAGE_FARTHEST_CENTER_ONE_AXIS_V1=1` | -| 256 | 8 | `ba_degenerate_scale_threshold` | `f64` | `1e-9` | -| 264 | 4 | `ba_residual_policy` | `u32` | `ONE_FULL_2D_BLOCK_PER_OBSERVATION_V1=1` | -| 268 | 4 | `ba_projection_policy` | `u32` | `SOURCE_PIXEL_PINHOLE_K1_K2_P1_P2_V1=1` | -| 272 | 8 | `ba_minimum_camera_depth` | `f64` | `1e-9` | -| 280 | 4 | `ba_robust_loss` | `u32` | `HUBER_FULL_2D_NORM_V1=1` | -| 284 | 8 | `ba_huber_delta_px` | `f64` | `2.0` | -| 292 | 4 | `ba_robust_cost_policy` | `u32` | `HALF_SUM_RHO_SQUARED_NORM_V1=1` | -| 296 | 4 | `ba_solver_contract` | `u32` | `CERES_2_2_CONTRACT_V1=1` | -| 300 | 4 | `ba_minimizer` | `u32` | `TRUST_REGION_V1=1` | -| 304 | 4 | `ba_trust_region_strategy` | `u32` | `LEVENBERG_MARQUARDT_V1=1` | -| 308 | 4 | `ba_linear_solver` | `u32` | `ITERATIVE_SCHUR_V1=1` | -| 312 | 4 | `ba_preconditioner` | `u32` | `SCHUR_JACOBI_V1=1` | -| 316 | 4 | `ba_num_threads` | `u32` | `1` | -| 320 | 4 | `ba_max_num_iterations` | `u32` | `50` | -| 324 | 8 | `ba_function_tolerance` | `f64` | `1e-6` | -| 332 | 8 | `ba_gradient_tolerance` | `f64` | `1e-10` | -| 340 | 8 | `ba_parameter_tolerance` | `f64` | `1e-8` | -| 348 | 4 | `ba_retry_count` | `u32` | `0` | -| 352 | 4 | `ba_convergence_acceptance` | `u32` | `CONVERGENCE_ONLY_V1=1` | -| 356 | 8 | `ba_cost_non_regression_factor` | `f64` | `1e-12` | -| 364 | 4 | `ba_underconstraint_policy` | `u32` | `MANIFEST_UC1_UC2_UC3_UC4_V1=1` | -| 368 | 4 | `ba_numeric_policy` | `u32` | `BINARY64_SINGLE_THREAD_V1=1` | - -The exact v1 record length is 372 bytes. Gate D field semantics and source -translations are exhaustive: - -| Field | Source type | Encoding/normalization | Scientific meaning | -|---|---|---|---| -| `minimum_seed_tracks` | `uint32_t` | `u32` | minimum shared Tracks for a seed | -| `minimum_seed_landmarks` | `uint32_t` | `u32` | minimum accepted seed landmarks | -| `minimum_pnp_correspondences` | `uint32_t` | `u32` | minimum correspondences for registration | -| `maximum_seed_candidates` | `uint32_t` | `u32` | bound on seed attempts | -| `maximum_registration_rounds` | `uint32_t` | `u32` | bound on growth rounds | -| `maximum_landmarks_per_round` | `uint32_t` | `u32` | bound on new landmarks per round | -| `maximum_images` | `uint32_t` | `u32` | accepted input image bound | -| `maximum_observations` | `uint64_t` | `u64` | accepted observation bound | -| `maximum_tracks` | `uint64_t` | `u64` | accepted Track bound | -| `reprojection_threshold_px` | `double` | finite canonical `f64` | landmark reprojection acceptance | -| `minimum_track_parallax_rad` | `double` | finite canonical `f64` | landmark parallax acceptance | -| `relative_pose.robust_threshold_px` | `double` | finite canonical `f64` | essential robust residual threshold | -| `relative_pose.confidence` | `double` | finite canonical `f64` | essential robust confidence | -| `relative_pose.max_iterations` | `uint32_t` | `u32` | essential robust iteration bound | -| `relative_pose.minimum_inliers` | `uint32_t` | `u32` | essential minimum inlier count | -| `relative_pose.minimum_inlier_ratio` | `double` | finite canonical `f64` | essential minimum inlier fraction | -| `relative_pose.minimum_parallax_rad` | `double` | finite canonical `f64` | relative-pose minimum parallax | -| `relative_pose.minimum_cheirality_ratio` | `double` | finite canonical `f64` | relative-pose positive-depth fraction | -| `relative_pose.deterministic_seed` | `uint64_t` | `u64` | local essential robust-estimator seed | -| `pnp.reprojection_threshold_px` | `double` | finite canonical `f64` | PnP robust residual threshold | -| `pnp.confidence` | `double` | finite canonical `f64` | PnP robust confidence | -| `pnp.max_iterations` | `uint32_t` | `u32` | PnP robust iteration bound | -| `pnp.minimum_inliers` | `uint32_t` | `u32` | PnP minimum inlier count | -| `pnp.minimum_inlier_ratio` | `double` | finite canonical `f64` | PnP minimum inlier fraction | -| `pnp.deterministic_seed` | `uint64_t` | `u64` | local PnP robust-estimator seed | -| `refinement.max_iterations` | `uint32_t` | `u32` | point-refinement iteration bound | -| `refinement.convergence_tolerance` | `double` | finite canonical `f64` | point-refinement stopping tolerance | - -Every row is fingerprinted. Integer fields require no normalization beyond -their fixed-width little-endian translation; every floating field uses the -canonical-zero rule above. The four relative-pose/PnP iteration/inlier fields -retain this `u32` syntax for F0 compatibility, while an executable call through -the public Gate C OpenCV boundary additionally requires the `INT_MAX` bound -defined below. That operational validation neither rewrites nor normalizes -fingerprint bytes. - -The policy IDs above freeze the full named Gate D and Gate E v1 semantics, -including canonical order and tie breaks, -whole-landmark rejection, positive-depth cheirality, the pose anchor chosen by -smallest image ID, the farthest-center scale anchor with exact image-ID tie, -X/Y/Z axis tie order and exactly one fixed center coordinate. The Gate E -projection is `R_cw + Cw`; intrinsics and `k1/k2/p1/p2` distortion are fixed. -The robust cost policy is exactly `0.5 * sum rho(dx^2+dy^2)` with one Huber loss -on each full two-dimensional residual. `CERES_2_2_CONTRACT_V1` denotes the -accepted Lardon3D Ceres contract `>=2.2.0,<2.3.0`, not package, build, linker or -transitive SuiteSparse metadata. - -For Gate D, `WORLD_TO_CAMERA_R_CW_CW_V1` includes the frozen SO(3) residual -limit `1e-6`; -`POSITIVE_DEPTH_THRESHOLD_V1` means strict camera depth greater than `1e-9`; -and `NORMALIZED_DLT_POINT_REFINE_V1` includes the frozen homogeneous-scale -epsilon `1e-12` and collinearity covariance-determinant limit `1e-10`. These -constants are not runtime members, so their stable policy IDs, rather than -duplicate floating fields or implementation enum ordinals, own their exact -v1 semantics. - -All 27 effective scalar members, including nested members, of -`Lardon3DSparseIncrementalParameters` occur exactly once. Serialization uses -the validated effective values actually passed to Gate D, so an omitted default -and the same explicitly supplied value produce identical bytes. Actual Track -Set identity, Track/Feature IDs, calibration-scope identity and individual -calibration IDs or numeric values, `sfm_kind`, `sfm_version`, project/task/ -transaction/reconstruction IDs, timestamps, resource state, result values and -metrics are excluded. The separate calibration scope hash binds sorted image -IDs to calibration hashes, and each calibration hash binds dimensions, -`fx/fy/cx/cy/k1/k2/p1/p2`, model/version and provenance; the parameter record -therefore records calibration semantics without duplicating calibration -instances. - -Changing only a parameter value retains encoding version 1 and naturally -changes the digest. Adding a fingerprint-owned field or changing byte layout -requires a new encoding version; changing the Sparse SfM algorithmic contract -may separately require a new `sfm_version`. Neither version substitutes for the -other. Equal complete candidate tuples therefore identify the same scientific -candidate regardless of runtime metadata. - -Gate F implementation must prefer an internal, solver-independent helper unless -a separate public C17 API decision is made. It must reuse the project's SHA-256 -implementation, use bounded constant-size storage without cache, scheduling, -Governor interaction or reservation, and add a golden 372-byte default record, -its expected SHA-256 digest, and mutations proving every fingerprint-owned -category changes the digest. This contract authorizes no public symbol. - -Exact byte identity is not promised for a future multi-threaded floating-point -solver until measured. The v1 target is deterministic ordering and numerical -reproducibility within documented tolerances; single-threaded reductions are -the initial reference. - -### Gate F publication policy - -**FROZEN.** Gate F keeps the Gate E execution domain separate from the Gate E -scientific result domain. `EXECUTION_OK` is the necessary and sufficient Gate E -condition for publication eligibility. Under that execution status, -`COMPLETE`, `PARTIAL` and `FAILED` are all valid complete in-memory Gate E -results and are published atomically. A successful Project DB publication or -exact-identity reuse makes the Task Runtime execution successful for all three -scientific statuses. - -| Gate E execution | Scientific status | Publish | Runtime after DB success | -|---|---|---|---| -| `EXECUTION_OK` | `COMPLETE` | exact Gate E result | success | -| `EXECUTION_OK` | `PARTIAL` | exact Gate E result | success | -| `EXECUTION_OK` | `FAILED` | exact Gate E result | success | -| `EXECUTION_INVALID_ARGUMENT` | none | no publication | failure | -| `EXECUTION_OUT_OF_MEMORY` | none | no publication | failure | -| `EXECUTION_INTERNAL_ERROR` | none | no publication | failure | - -Scientific `PARTIAL` describes a complete result in which accepted components -contain validated optimized values and rejected components preserve their exact -Gate D values. It never permits partial database visibility. Scientific -`FAILED` means that no eligible component accepted Bundle Adjustment; under -`EXECUTION_OK` it remains a valid complete Gate E result whose components -preserve the Gate D values selected by Gate E. - -Gate F never publishes Gate D as a fallback after a Gate E execution error. It -does not rerun Gate D or Gate E, retry automatically, reinterpret scientific -status as a Task Runtime state, or require scientific `COMPLETE` for runtime -success. A Project DB publication failure rolls back and fails the runtime -execution. The scientific status is output diagnostic metadata, not part of -the candidate identity or parameter fingerprint. Gate F uses an existing -status carrier when one exists; the absence of a dedicated Project DB v16 -column does not require a migration or authorize reuse of an unrelated column. - -### Gate F resource-demand boundary - -**FROZEN.** Gate F materializes the immutable `Lardon3DResourceEstimate` needed -to describe its task to the existing Task Runtime. The estimate is a pure -function of durable task input shape and known implementation characteristics; -it never depends on current RAM, swap, PSI, load, queue depth, task attempt or -Governor state. Gate F submits through the existing queue and does not perform -admission or reservation itself. - -The existing Governor owns admission and reservation policy. Gate G owns -telemetry, pressure and scheduling policy, but Gate G core neither changes the -frozen Sparse SfM producer estimate nor adds scratch support. The estimate never -enters the 372-byte parameter record, candidate identity, `sfm_version` or scientific decisions, and cannot -change Gate D or Gate E parameters. This clarification preserves -`NO_NEW_SUBSYSTEM` and the mandatory reservation invariant. - -Gate G G0a freezes consumption of the exact Gate F v1 estimate. A restored task -keeps its persisted estimate and is evaluated with newly captured machine -telemetry; it is never recomputed with later coefficients. A future formula -change requires a separate operational formula/version review for newly created -tasks and cannot affect F0, candidate identity, Gate D/E parameters or existing -tasks. Sparse SfM remains batch one and has no scratch, spill or out-of-core -path. Swap, zram and external storage do not enlarge its RAM capacity. - -### Gate F durable task payload - -**FROZEN.** Gate F advanced the then-current Project Database schema head from -v16 to v17 with one strictly additive `sparse_sfm_tasks` table. The historical v16 -migration and its immutable reconstruction model remain unchanged. The new -table follows the existing one-to-one typed-task pattern: its primary key is a -foreign key to `tasks(task_id)` with cascade cleanup, and creation records the -generic task snapshot and typed payload in one transaction. - -The payload stores the immutable Track Set reference, calibration-scope -reference, Sparse SfM kind and version, and every one of the 27 effective -scalars in `Lardon3DSparseIncrementalParameters`, using their existing fixed -integer widths and exact finite SQLite binary64 values. The Task Kind version -selects the payload interpretation. Reload never reapplies defaults and rejects -a missing row, incompatible kind/version, invalid identity or invalid -parameter. The generic checkpoint codec remains v1 and unchanged. - -The parameter fingerprint is not stored in `sparse_sfm_tasks`. Reconstruction -loads and validates the effective fields, rebuilds the unchanged 372-byte F0 -record and recomputes SHA-256. Calibration values, Track observations, -ResourceEstimate and runtime metadata are not duplicated in the typed payload. -This is an additive Project DB/typed-Task extension, not a generic payload or -persistence subsystem. - -### Gate F closure decisions - -**FROZEN.** The v1 declarative estimate uses the immutable pre-admission counts -`I` (distinct participating images), `T` (Tracks) and `O` (Track observations): +## Summary ```text -raw = 134217728 + I*65536 + T*2048 + O*512 -memory_fixed_bytes = raw rounded upward to a whole MiB +SPARSE_SFM_V1=IMPLEMENTED +GATE_A=DECISION/HISTORICAL +GATE_B=PASS/FROZEN +GATE_C=PASS/FROZEN +GATE_D=PASS/FROZEN +GATE_E=PASS/FROZEN +GATE_F=PASS/FROZEN +GATE_G=PASS/FROZEN + +SPARSE_SFM_TASK=sparse_sfm.run/1 +SPARSE_SFM_RESOURCE_SHAPE=CPU1/BATCH1/FROZEN + +KNOWN_CALIBRATION_REQUIRED=YES +SILENT_CALIBRATION_SUBSTITUTION=FORBIDDEN +METRIC_SCALE_INFERENCE=FORBIDDEN + +REAL_S21_SPARSE_SFM=NOT_EXECUTED +REAL_A6000_SPARSE_SFM=NOT_EXECUTED +REAL_A6000_SPARSE_SFM=BLOCKED_BY_KNOWN_CALIBRATION_DATA + +CURRENT_PROJECT_DB_SCHEMA=v25 +REAL_A6000_PRE_SFM=PASS/FROZEN ``` - -Every operation is checked `uint64_t` arithmetic; overflow rejects task -creation before persistence. The remaining estimate is RAM-per-item 0, all GPU -fields 0, minimum and maximum batch 1, one CPU thread, one IO slot and CPU task -class. These conservative coefficients cover Gate D containers, adapters, -camera/landmark state, Gate E copies, ordering/residual storage and Ceres -working storage. They are operational, machine-state independent and excluded -from scientific identity. A restored task uses its persisted generic estimate. - -The four full-domain `uint64_t` payload values (`maximum_observations`, -`maximum_tracks` and both deterministic seeds) are individual exact eight-byte -little-endian SQL BLOBs. Any other storage class or length is corrupt; there is -no signed cast, text, REAL conversion or domain restriction. Their F0 encoding -remains unchanged. - -Gate D `COMPLETE` and usable `PARTIAL` results proceed to Gate E. Gate D -scientific `FAILED` fails the Task, invokes no Gate E and publishes nothing. -Gate D invalid input or allocation failure likewise fails execution. A claimed -usable result that violates frozen structure is an internal integration -failure. Once Gate E is legitimately reached, every `EXECUTION_OK` scientific -status remains publication-eligible. - -Gate F computes publication diagnostics from every retained observation of the -exact final Gate E result. With source-pixel residual `dx,dy`, each observation -contributes `s=dx*dx+dy*dy` and `e=sqrt(s)`. Global RMSE is -`sqrt(sum(s)/N)`. Global median is the middle sorted `e`, or for even `N`, -`lower + (upper-lower)/2`. Projection is the frozen Gate E distorted pinhole -model, binary64, with strict minimum depth. Empty, non-finite or invalid -projection input fails publication; no observation is skipped or clamped. -Metrics are deterministic result diagnostics, never candidate identity. - -### Gate F durable reconstruction projection - -**FROZEN.** Gate F projects the complete Gate D/E scientific result onto the -existing Project DB reconstruction model. A component belongs to the durable -projection exactly when `registered_image_count > 0` and `landmark_count > 0`. -Every such component is persisted with its exact final Gate E geometry. This -includes a BA-rejected component whose valid Gate D geometry Gate E preserved. - -A non-reconstructed graph component failing either predicate remains an -ephemeral scientific/orchestration diagnostic and has no Project DB component -row. Gate F fabricates no camera, landmark or placeholder and does not fail an -otherwise publishable reconstruction merely because such diagnostics exist. -The projected result must still satisfy every top-level Project DB invariant; -otherwise publication does not occur and the Task fails. - -Persisted component and geometry counts describe only this durable projection. -Global reprojection metrics likewise include exactly the retained observations -belonging to persisted geometry. Omission neither renumbers scientific -component keys nor changes candidate identity. No persistent diagnostic table, -sidecar, metadata blob or schema beyond v17 `sparse_sfm_tasks` is introduced. - -Gate F v1 is **PASS / FROZEN**. Gate D and Gate E remain **PASS / FROZEN**; -Gate G architecture decisions and implementation are **PASS / FROZEN**. - -### Gate F validation closure - -**PASS / FROZEN.** Gate F freezes the F0 372-byte parameter record v1 and its -SHA-256 digest, Project DB v17 typed payload, `sparse_sfm.run` version 1, -pre-admission declarative estimate, governed Task Runtime execution, durable -replay, deterministic D→E orchestration, exact candidate reuse and atomic -reconstruction publication. Canonical gate progression: - -```text -Gate A — PASS / FROZEN -Gate B — PASS / FROZEN -Gate C — PASS / FROZEN -Gate D — PASS / FROZEN -Gate E — PASS / FROZEN -Gate F — PASS / FROZEN -Gate G — PASS / FROZEN -``` - -The five candidate-identity dimensions remain separate: - -```text -( - input_track_set_identity, - calibration_scope_identity, - sfm_kind, - sfm_version, - parameter_fingerprint -) -``` - -The F0 golden SHA-256 digest remains -`e1c83e5b2036e49254a9426ddbace42b7831373bc896f27abdd2f61e302f9e8c`. -Final validation completed with the normal suite at 41/41, targeted Gate F -ASan/UBSan/LeakSanitizer at 4/4 with leak detection enabled, and the full -sequential ASan/UBSan suite at 41/41. Fresh-process validation passed 20/20 for -the Gate F contract and 20/20 for the production `sparse_sfm.run` task. The C17 -public-header probe and `git diff --check` passed. Final human diff review -passed; no Gate F implementation work, validation work or human decision -remains. - -## Future persistence and API candidates - -No Project DB v16 is created in Gate A. A later model gate may define immutable -entities such as `sparse_reconstructions`, registered camera poses, landmarks, -and landmark observations. The reconstruction must reference exactly one Track -Set and calibration identity, publish atomically, and never expose half-solved -cameras or points. Upstream Track Set deletion policy requires an explicit -future ownership decision; silent CASCADE of a published reconstruction is not -assumed. - -The future public boundary remains C17-safe and solver-independent. Candidate -opaque APIs accept immutable Track Set/calibration inputs and return owned -opaque result pages with explicit free functions. No `cv::Mat`, Eigen, Ceres, -STL, callback or C++ exception crosses the boundary. Numeric kernels operate on -pure in-memory structures and never open SQLite or query the Governor. - -## Resource envelope - -Let `C` be registered cameras, `T` Tracks, `P` active landmarks, `O` -observations and `E_covis` sparse image-graph edges. The architecture requires -`O(C + T + P + O + E_covis)` memory for graph/index structures plus the solver -working set. It forbids a dense `C×P`, `C×C` or co-visibility matrix. Track -length has no arbitrary 256 cap; long Tracks are iterated through checked -bounded storage. - -Triangulation/registration are light CPU units and can be batched. Gate E v1 -uses local scientific limits for its final per-component BA and does not query -the Governor. Gate G consumes the frozen Gate F estimate derived from immutable -workload shape without changing scientific results. The existing Resource -Governor owns RAM/PSI/swap policy; Sparse SfM adds no system-pressure thresholds. -Swap is never normal working memory, and UMA RAM must preserve several GiB of -desktop/iGPU headroom. - -## Hardware and probe study - -Gate A preflight measured 16 logical CPUs, `MemTotal=15597716 KiB`, -`MemAvailable=8245288 KiB` at the study point, an 8 GiB swapfile, a 6 GiB -zram device, and zero current memory/IO PSI average. The host is the Ryzen 7 -8845HS/Radeon 780M UMA target described by the performance document. - -The project already links OpenCV 5.0.0. Host-installed libraries and their -pkg-config or CMake discovery metadata are capabilities, not Lardon3D -production dependencies. Gate E now declares Ceres 2.2.x through Meson CMake -discovery. No system setting, swap device or GPU mode was changed. - -Gate A probes use deterministic synthetic camera arcs, controlled noise and -degenerate planar/pure-rotation cases. Every RSS probe is a separate normal -optimized child process; fixture arrays, solver structures and peak RSS are -reported separately. Thread probes are limited to 1/2/4/8 threads and stop if -MemAvailable, swap, PSI or desktop responsiveness becomes unhealthy. No -production Sparse SfM code is created by this gate. - -## Gate decomposition - -- **Gate B — PASS / FROZEN — Sparse Reconstruction Model:** immutable in-memory model, result - states, calibration ownership and candidate persistence contract; no DB v16 - until this contract is reviewed. -- **Gate C — PASS / FROZEN — Geometry primitives:** normalized camera model, relative pose, - deterministic seed, triangulation and PnP with synthetic ground truth. -- **Gate D — PASS / FROZEN — Incremental core:** registration ordering, components, - unregistered-image policy and deterministic reconstruction output. -- **Gate E — PASS / FROZEN — Final Bundle Adjustment:** synchronous final per-component BA on a - copy of the immutable Gate D result, with its scientific and numerical - contract frozen here; interleaved local BA is deferred. -- **Gate F — PASS / FROZEN — Project orchestration:** explicit Track Set/calibration input, - atomic publication and durable runtime integration. -- **Gate G — PASS / FROZEN — Resource/freeze:** Governor - admission, sustained hardware safety and recovery are implemented and fully - validated. - -## Algorithm comparison and Gate A evidence - -### Incremental SfM - -Seed/order risk is controlled by deterministic policy. It is robust for -sequential capture, has canonical queues and seeds, moderate complexity, and -sparse `C,T,O` scaling followed by final per-component BA. **SELECTED v1.** - -### Global SfM - -Global averaging can spread weak geometry. It is sensitive to disconnected or -weak-baseline graphs, needs several global tie policies, and requires a larger -sparse solve. **Rejected for v1.** - -### Hybrid - -Hybrid design combines both failure surfaces, is hard to specify minimally and -harder to reproduce. **Rejected for v1.** - -Triangulation candidates: - -- Midpoint/ray only: fragile with noise and awkward beyond two views. Rejected. -- Linear normalized DLT: good initialization with explicit checks and all - registered observations. **Selected initialization.** -- DLT plus point-only refinement: better residual with bounded per-point work - and fixed termination. **Selected v1 candidate.** - -| BA candidate | Sparse support | Dependency status | Decision | -|---|---|---|---| -| Dense normal equations | Prohibited for serious `C×P` problems | No | Rejected | -| OpenCV generic optimization | Not a sparse BA contract | Present, wrong abstraction | Rejected | -| Ceres 2.2.x iterative Schur | Block-sparse | Declared through Meson CMake discovery | **Implemented Gate E v1** | - -### Synthetic geometry probe - -The normal OpenCV 5.0.0 installation was exercised in a fresh Python process on -100 deterministic points, binary64 K (`fx=fy=800`, `cx=640`, `cy=480`), a one-unit -baseline and a four-degree rotation. `recoverPose` retained 100 inliers with -zero measured rotation error and translation direction absolute dot product -`0.997564`; two-view DLT triangulation had median position error -`2.73e-15`; iterative PnP retained 100 inliers with camera-center error -`9.02e-8` and zero measured rotation error. This validates the candidate -primitive boundary, not production SfM correctness. - -The same probe deliberately tested pure rotation and planar points. OpenCV can -still return an Essential matrix with 100 nominal inliers in both cases; this -is why `findEssentialMat` success is not an acceptance criterion. Seed -selection must apply parallax, conditioning, cheirality and model-ambiguity -checks before accepting a component. - -### Dependency and hardware evidence - -The project already links OpenCV 5.0.0. Host probes found Eigen 5.0.1, BLAS -3.12.0, LAPACK 3.12.0 and TBB 2023.1 as host capabilities or transitive -facilities rather than current Lardon3D production dependencies. Ceres may use -CMake discovery, so pkg-config alone does not establish host availability. -Ceres 2.2.x is the implemented Gate E scientific API and is declared through -Meson CMake discovery. The measured machine has 16 logical CPUs, -`MemTotal=15597716 KiB`, `MemAvailable=8245288 KiB` at preflight, an 8 GiB -swapfile, 6 GiB zram and zero memory/IO PSI averages at the probe start. Gate E -uses one solver thread; future Gate G resource admission cannot change that -scientific setting. - -## Gate A unresolved boundaries - -The following remain deliberately deferred rather than hidden: Ceres -licensing/dependency integration, metric alignment, persistent orchestration -and durable SfM checkpoints. Gate E freezes its own robust loss, convergence, -ordering and acceptance policy here without introducing persistence or a -fingerprint. - -## Gate C — pure calibrated geometry - -**GATE C — PASS.** Pure calibrated geometry primitives, synthetic ground truth, -degeneracy rejection, determinism, normal suite and ASan/UBSan validation are -complete. Incremental orchestration, BA and persistent geometry integration -remain later gates. - -Gate C keeps geometry outside Project DB and exposes a C17-safe, synchronous -pure-primitive boundary. Inputs are binary64 calibrated pixels, fixed -world-to-camera poses, and caller-owned correspondence arrays; no primitive -opens SQLite, reads Feature Files, loads images or invokes the Task Runtime. -The v1 candidate uses OpenCV 5.0.0 `calib3d` operations with every scientific -parameter supplied by an explicit configuration structure. Public outputs use -row-major binary64 `R_cw` and `t_cw`; relative translation has unit norm and no -metric interpretation. - -The candidate contract requires deterministic caller ordering, finite inputs, -explicit robust-estimator thresholds/confidence/iteration limits and a local -seed. Essential hypotheses are accepted only after explicit positive-depth -support, rotation validation, parallax and reprojection checks. Pure rotation, -low parallax, weak conditioning and non-finite results are failures. Two-view -and multi-view points use normalized-coordinate linear DLT followed by bounded -point-only binary64 refinement; PnP returns world-to-camera pose with explicit -cheirality and inlier diagnostics. The tested v1 parameter set is frozen by the -Gate C ground-truth and degeneracy evidence; future orchestration may choose -other explicitly fingerprinted configurations. - -#### Public OpenCV signed-boundary contract - -The C17 parameter fields for relative pose and calibrated PnP remain -`uint32_t`, but OpenCV accepts signed `int` iteration and inlier arguments. -Consequently, both `max_iterations` and `minimum_inliers` must be at most -`INT_MAX`; `max_iterations == 0` retains its existing invalid semantics, while -`minimum_inliers == 0` retains the existing PnP effective minimum of four. -Values above `INT_MAX` return `INVALID_ARGUMENT` before allocation, OpenCV, -RNG work or mutation of the caller-owned result/mask. Conversion to `int` -occurs only after that check. - -This is an operational language/library boundary, not a scientific-policy -change. The public field widths, F0 `u32` encoding, FROZEN defaults, thresholds, -seeds, fingerprints and every representable run remain byte-identical. The -maintenance regression rejects `INT_MAX+1` and `UINT32_MAX` for both primitives -without output mutation, while a degenerate no-solver fixture proves -`INT_MAX` itself remains representable without attempting that many -iterations. Focused validation passed 1/1 plus 20 repeats, targeted -ASan/UBSan 1/1, GCC/Clang C17/C++17 inclusion and the application link. - -### Gate C tested threshold set - -The pure API has no hidden defaults; callers provide all acceptance settings. -The Gate C reference matrix uses the following reproducible set: - -| Parameter | Value | Unit/purpose | -|---|---:|---| -| Relative robust threshold | 1.0 px clean; 1.5 px matrix | pixel residual | -| Relative confidence | 0.999 | RANSAC confidence | -| Relative iterations | 1000 clean; 1500 matrix | iterations | -| Relative minimum inliers | 6 clean; 24 matrix | correspondences | -| Relative minimum ratio | 0.75 clean; 0.5 matrix | fraction | -| Minimum parallax | `1e-4` rad | seed geometry | -| Minimum cheirality ratio | 0.5 | positive depth | -| PnP threshold | 1.0 px clean; 1.5 px matrix | pixel residual | -| PnP confidence | 0.999 | RANSAC confidence | -| PnP iterations | 1000 | iterations | -| PnP minimum inliers | 6 clean; 12 matrix | correspondences | -| PnP minimum ratio | 0.75 clean; 0.5 matrix | fraction | -| Point refinement tolerance | `1e-12` | normalized residual | -| Point refinement iterations | 30 | iterations | - -Degeneracy checks use finite values, positive depth, rotation SO(3) residual -`1e-6`, depth epsilon `1e-9`, homogeneous scale epsilon `1e-12`, and -collinearity covariance determinant `1e-10`. These are pure-geometry -parameters and do not alter Project DB identity. - -## Gate D — incremental Sparse SfM core - -**GATE D — PASS / FROZEN.** Gate D is the first executable link -between the immutable Track/Calibration contracts and the Gate C primitives. -The reference implementation is synchronous, deterministic, CPU-only, -in-memory, bounded and independent of Project DB, Task Runtime, Resource -Governor and persistence publication. - -### Inputs - -Gate D consumes exactly one immutable Track Set, one immutable calibration -scope, finite calibration values for participating images, bounded keypoint -coordinates addressed by `(feature_set_id, feature_index)`, and explicit -parameters immutable during execution. Gate D neither computes nor carries the -parameter fingerprint materialized later at the Gate F persistent-identity -seam. The Track Set is never mutated. - -### Algorithm - -The core sorts image and Track identities, builds sparse connected components, -orders seed candidates by shared Track count and image IDs, and tries a bounded -number of seeds. Each candidate uses the Gate C relative-pose, cheirality, -parallax and two-view triangulation contracts. A valid seed establishes a -component-local unit gauge. - -Unregistered images are then ordered by visible accepted-landmark count and -image ID. Gate C calibrated PnP registers at most one selected image per -bounded round. Failed registration leaves the image explicitly unregistered. -New landmarks use all currently registered observations, Gate C multi-view DLT -and bounded point-only refinement. A landmark is accepted or rejected as a -whole; Track observations are never dropped or rewritten. - -After each successful camera registration, an existing landmark whose Track -has gained registered observations is reconsidered in canonical image-ID -order. Gate C multi-view triangulation and point refinement use the complete -eligible observation set. The replacement is published in memory only after -finite-value, positive-depth and reprojection validation; otherwise the prior -valid landmark and its observations remain unchanged. - -The Gate D reference bounds are 4096 input images, 250,000 Tracks, 1,000,000 -observations, 32 seed candidates, 32 registration rounds and 4096 new -landmarks per growth round. The defaults use 1.5 px relative-pose/PnP robust -thresholds, a 2.0 px landmark reprojection threshold, 0.5 minimum inlier -ratios, `1e-4` rad minimum parallax, 6 minimum seed/PnP inliers and 30 -point-refinement iterations. These are Gate D policy defaults; changing them -changes the explicit parameter configuration. - -### Output and failure semantics - -The in-memory result contains deterministic components, registered cameras, -accepted landmarks, landmark observations, reprojection diagnostics and -explicit unregistered images. Results are `COMPLETE`, `PARTIAL` or `FAILED`. -Invalid input fails before computation. A rejected seed, camera or landmark -does not corrupt an accepted model. No partial result is persisted. - -Growth stops immediately when a complete registration round cannot register -an image. It also stops exactly at the configured registration-round bound. -Both paths retain valid cameras and landmarks, list every remaining image as -unregistered and produce `PARTIAL` when usable geometry exists. - -Components with fewer than two registered cameras are not valid 3D components. -Disconnected valid components retain independent unit gauges and are never -globally aligned by Gate D. - -### Gate D limits - -Gate D does not implement BA, persistence adapters, Task Runtime, checkpoints, -Governor integration, a Resource System, GPU execution, dense reconstruction, -metric alignment, viewer integration or any Project DB change. BA remains the -separate PASS / FROZEN Gate E post-processing stage; project/task orchestration -remains Gate F and resource/freeze integration remains Gate G. - -### Canonical Gate D functional matrix - -This table freezes the complete numbered validation contract. Evidence is the -minimum dedicated observation required; an earlier rejection never substitutes -for the named path. - -| Case | Purpose | Required path and evidence | Expected result | -|---|---|---|---| -| 01 Minimal two-view | Smallest valid reconstruction | One seed, two cameras, finite landmarks | `COMPLETE` | -| 02 Deterministic seed | Canonical seed identity | Same selected pair and pose on repeat | `COMPLETE` | -| 03 Multiple seed candidates | Candidate ordering | Multiple eligible pairs, canonical first pair | `COMPLETE` | -| 04 Rejected first seed / later seed | Seed fallback | At least two attempts, later pair selected | `COMPLETE` | -| 05 Camera-addition order | Registration ordering | Highest support then image ID, one per round | `COMPLETE` | -| 06 Clean PnP | Nominal registration | PnP attempted and succeeds with clean support | `COMPLETE` | -| 07 Noisy PnP | Bounded noise | PnP succeeds with finite pose | `COMPLETE` | -| 08 Deterministic PnP outliers | Robust registration | Stable inlier count and pose | `COMPLETE` | -| 09 Failed PnP | Registration rejection | Failure counted and image listed | `PARTIAL` | -| 10 Insufficient PnP support | Eligibility bound | Solver not called and image listed | `PARTIAL` | -| 11 Low-parallax rejection | Seed guard | Gate C low-parallax/degenerate status | `FAILED` | -| 12 Pure rotation | Translation degeneracy | Relative pose rejected, no camera | `FAILED` | -| 13 Planar degeneracy | Ambiguous seed | Gate C degeneracy, no camera | `FAILED` | -| 14 Far scene | Finite distant geometry | Seed and finite landmarks accepted | `COMPLETE` | -| 15 Disconnected graph | Component discovery | Valid component plus explicit singleton | `PARTIAL` | -| 16 Multiple valid components | Isolation | Two reconstructed components | `COMPLETE` | -| 17 Independent gauges | Per-component gauge | Each seed camera is identity | `COMPLETE` | -| 18 Unregistered images | Explicit output | Remaining image and component key listed | `PARTIAL` | -| 19 Behind-camera landmark | Cheirality | Exact Gate C status and distinct counter | `COMPLETE` model | -| 20 High reprojection error | Residual policy | Finite triangulation then residual rejection | `COMPLETE` model | -| 21 Failed triangulation | Geometry failure | Finite input calls triangulation and fails | `COMPLETE` model | -| 22 Repeated observations | Track coherence | Duplicate image or feature reference rejected | `INVALID_ARGUMENT` | -| 23 Many-camera Track | Landmark lifecycle | One landmark, six ordered observations | `COMPLETE` | -| 24 New landmark after registration | Incremental growth | Ineligible Track accepted after PnP | `COMPLETE` | -| 25 Multi-view growth | All eligible views | New landmark uses at least three views | `COMPLETE` | -| 26 Point refinement | Bounded refinement | Attempt and finite accepted point | `COMPLETE` | -| 27 No-growth termination | Progress bound | One zero-progress round and diagnostic | `PARTIAL` | -| 28 All-images termination | Natural completion | All images registered, no stop diagnostic | `COMPLETE` | -| 29 Seed exhaustion | Candidate bound | Every available candidate attempted | `FAILED` | -| 30 Registration-round exhaustion | Round bound | Exact rounds and remaining images | `PARTIAL` | -| 31 Component ordering | Canonical components | Increasing component keys | success | -| 32 Camera ordering | Canonical cameras | Increasing image IDs | success | -| 33 Landmark ordering | Canonical landmarks | Increasing `(component_key, track_id)` | success | -| 34 In-process repeatability | Local determinism | Complete scientific result equality | same status | -| 35 Fresh-process repeatability | Process determinism | 20 runs emit one signature | same status | - -### Gate D validation responsibility - -`GATE_D_REQUIRED` covers pointer/count coherence, identities carried by this -API, finite calibration/keypoints, feature-index bounds, Track observation -coherence, geometry failures, atomic result ownership and cleanup. Store-level -Feature Set/File existence is `UPSTREAM_RESPONSIBILITY`: Gate D receives -flattened validated coordinates and never opens a store. Two separate Track -objects with the same ID are `UNREPRESENTABLE_BY_API` because rows are grouped -by `track_id`; duplicate image observations and feature references remain -representable and are rejected. Allocation-failure injection is -`NOT_APPLICABLE_WITH_PROOF`: no allocator injection boundary exists, production -catches allocation failure at the C ABI, and global test allocator state would -violate the architecture. - -| Condition | Classification | -|---|---| -| Null parameters, missing arrays, empty input | `GATE_D_REQUIRED` | -| Zero Track/calibration/image/feature identity | `GATE_D_REQUIRED` | -| Missing per-image calibration coverage | `GATE_D_REQUIRED` | -| Zero/non-finite focal or distortion, invalid principal point | `GATE_D_REQUIRED` | -| Invalid feature index or non-finite keypoint | `GATE_D_REQUIRED` | -| Duplicate image/feature observation or singleton Track | `GATE_D_REQUIRED` | -| Seed/PnP/landmark failures and update rollback | `GATE_D_REQUIRED` | -| Missing Feature Set/File in persistent storage | `UPSTREAM_RESPONSIBILITY` | -| Two distinct Track objects sharing one ID | `UNREPRESENTABLE_BY_API` | -| Deterministic allocation-failure injection | `NOT_APPLICABLE_WITH_PROOF` | - -The caller retains all input allocations for the synchronous call. The result -owns its arrays; `lardon3d_sparse_incremental_result_destroy()` releases them -and accepts an empty result or null pointer. No C++ exception crosses the C17 -boundary. - -Count-limit validation uses structurally sufficient fixtures at a lowered -explicit configured limit and proves `LIMIT-1`, `LIMIT`, and `LIMIT+1` without -materializing the public hard maxima. Scientific scale is validated separately -by the small, medium and large resource workloads. Policy tests prove exact -seed-candidate, registration-round and new-landmark-per-round admission; no -policy loop performs a `limit + 1` attempt. - -## Out of scope - -Beyond the Gate D incremental core, no BA, Project DB integration, metric -alignment, control-point scale, dense/MVS, mesh, texturing, Vulkan SfM, GPU BA, -network/distributed scheduling or UI workflow is implemented here. - -## Gate B — model and persistence contract - -This section is the **DECISION** contract for the v16 SQL/API work. -It preserves every Gate A decision and supplies only durable vocabulary; no -numeric geometry is introduced. - -### Calibration definition and identity - -A calibration is an immutable known pinhole model with `width`, `height`, -`fx`, `fy`, `cx`, `cy`, zero skew, `k1`, `k2`, `p1`, and `p2`, all binary64 and -finite. `width` and `height` belong to scientific identity. A calibration's -provenance is an explicit enum (`USER_EXPLICIT` or `IMPORTED_TRUSTED` in v1) -plus a 32-byte provenance fingerprint supplied by the caller. EXIF is never a -calibration origin. Two equal numeric models with different provenance -fingerprints are distinct scientific calibrations because their trust scope is -different; equal content and equal provenance are reused. - -The scientific calibration hash is SHA-256 over explicit little-endian fields: - -```text -ASCII "L3D3DCP1" (8 bytes) -format_version=1 (uint32) -model_kind (uint32), model_version (uint32) -width (uint32), height (uint32) -fx, fy, cx, cy, k1, k2, p1, p2 (8 canonical binary64 values) -provenance_kind (uint32), provenance_fingerprint (32 bytes) -``` - -NaN and infinities are rejected. Negative zero is canonicalized to positive -zero before hashing and storage. No native struct, padding or locale text is -serialized. SQLite row IDs remain DB-local references; the hash is the -scientific calibration identity. - -### Calibration scope - -A scope is immutable and assigns exactly one calibration to each relevant -image. Groups are allowed only when dimensions, crop/orientation coordinate -frame, model/version, numeric parameters and provenance identity are equal; -device or EXIF model names are insufficient. Scope identity is project-local -and content-addressed by SHA-256 over: - -```text -ASCII "L3D3DSC1" (8 bytes) -format_version=1 (uint32) -member_count (uint64) -for members sorted by image_id: - image_id (uint64), calibration_hash (32 bytes) -``` - -The member count is consistency metadata and is also encoded in the digest. -There is no latest-calibration lookup and no silent K rescaling. Feature File -dimensions must match the calibration dimensions exactly. - -### Reconstruction identity and components - -The immutable reconstruction identity is: - -```text -(track_set_id, - calibration_scope_id, - sfm_kind=INCREMENTAL, - sfm_version=1, - parameter_fingerprint[32]) -``` - -`track_set_id` and `calibration_scope_id` are project-local immutable database -references, consistent with the existing Track Model identity convention. The -parameter fingerprint is the raw 32-byte SHA-256 digest of the 372-byte Sparse -SfM parameter record v1 defined above. Gate F materializes it at its -persistent-identity seam; Project DB stores it without owning or recomputing -it. Runtime IDs, timestamps, worker count, resource state and metrics are -excluded. - -Component identity is the minimum registered `image_id` in that component. It -is deterministic, project-local, unique because an image belongs to at most one -component, independent of DFS/hash order, and compact. A component always has -at least one registered image. Its coordinates use an independent unit-baseline -gauge and are never comparable to another component without later alignment. - -### Persisted model - -The minimum v16 model is: - -- `sparse_calibrations`: immutable calibration content and hash; -- `sparse_calibration_scopes`: immutable scope hash/member count; -- `sparse_calibration_scope_images`: one image-to-calibration assignment; -- `sparse_reconstructions`: immutable identity and pixel reprojection metrics; -- `sparse_reconstruction_components`: component key and counts; -- `sparse_registered_images`: one world-to-camera pose per image; -- `sparse_landmarks`: one component-local binary64 point per Track; -- `sparse_landmark_observations`: minimal references to the upstream Feature Set - and feature index, without duplicated descriptors or x/y coordinates. - -Track ID is globally unique in the existing Track Model table, so one landmark -per reconstruction is uniquely keyed by `(reconstruction_id, track_id)`; -component key remains an attribute/consistency relation rather than redundant -landmark identity. Landmark publication validates that the Track belongs to the -reconstruction's exact Track Set and that every observation belongs to that -Track and its component. Track splitting is impossible in v1. - -Observation references are persisted because future BA needs bounded indexed -access from landmark to registered observations without repeatedly reopening -Feature Files. Only `feature_set_id`, `feature_index` and canonical track -position are stored; image ID and x/y remain derivable from immutable upstream -models. This is a deliberate normalization/resource trade-off, not a second -copy of Feature data. - -Persisted reprojection metrics use explicit pixel units and names: -`reprojection_rmse_px` and `reprojection_median_px`. They are diagnostics, not -identity. No metric scale or `_mm` field exists. - -### Constraints and publication - -Publication requires at least two registered images, one component and one -landmark. A result with no usable geometry is rejected rather than represented -by a meaningless empty scientific row. Two-camera reconstruction is valid. -There are no READY/FAILED/PARTIAL scientific states: row existence means a -complete immutable publication. A failed transaction leaves no visible row. - -SQL enforces one pose per image, one component per registered image, one -landmark per Track, exact reconstruction uniqueness, scope member uniqueness, -and child foreign keys. The API additionally validates Track Set ownership, -component consistency, calibration coverage, finite values and rotation -orthonormality/determinant. Rotation matrices are never repaired. - -The publication transaction inserts the reconstruction and all children using -prepared statements in bounded loops. It contains no Feature File I/O, Track -paging, solver work or Governor wait. Child pages use cursor order and bounded -capacity (64 cameras, 64 landmarks, 64 observations); total scientific counts -have no arbitrary cap beyond checked 64-bit/SQLite limits. - -### v16 migration intent - -Project DB v15 remains immutable. Gate B adds one transactional v15→v16 -migration containing only the eight Sparse SfM model tables and their required -indexes. A true historical v15 fixture, injected rollback, retry, fresh-schema -equivalence and close/reopen are mandatory. No Task, Governor, triangulation, -PnP, BA, GPU or Project DB v17 is introduced. diff --git a/docs/architecture/tracks.md b/docs/architecture/tracks.md index a0c277d..47a9dcb 100644 --- a/docs/architecture/tracks.md +++ b/docs/architecture/tracks.md @@ -1,526 +1,450 @@ # Track Model v1 -## Scope +## Status -Track Model v1 est le contrat persistant qui transforme les correspondances -géométriquement vérifiées en structures multi-view cohérentes. Il stocke des -ensembles d'observations 2D liées à un même point physique supposé. Il ne -calcule rien, ne triangule pas, ne contient aucune coordonnée 3D et ne résout -aucun conflit. Le Track Builder, la triangulation, le Sparse SfM et le Bundle -Adjustment sont des étapes séparées ; Gate E a gelé le Builder v1 sans -implémenter ces étapes 3D. +```text +TRACK_MODEL_V1=FROZEN +TRACK_BUILDER_V1=PASS/FROZEN + +CURRENT_PRODUCTION_VERIFIER=FUNDAMENTAL_V3 +SPARSE_SFM_CAPABILITY=IMPLEMENTED_THROUGH_GATE_G +REAL_S21_SPARSE_SFM=NOT_EXECUTED +REAL_A6000_SPARSE_SFM=NOT_EXECUTED + +REAL_S21_TRACKS=PASS/FROZEN +REAL_A6000_PRE_SFM=PASS/FROZEN +``` + +Track Model v1 is the persistent scientific contract for coherent multi-view 2D observation sets. + +A Track is **not** a 3D point. + +It contains no camera pose, triangulated coordinate, reprojection error or Bundle Adjustment state. + +Track Builder v1 constructs Tracks from verified Geometric Verification Results. Sparse SfM consumes an +immutable Track Set later. + +## Pipeline position + +Current pipeline: + +```text +Feature Set +-> Candidate Pair +-> Match Result +-> Geometric Verifier v3 +-> Geometric Verification Result +-> Track Builder v1 +-> Track Model v1 +-> Sparse SfM capability +``` + +Sparse SfM Gates C through G are implemented and frozen. + +The retained S21 and A6000 historical campaigns stop before real Sparse SfM because known calibration +data is unavailable for those campaigns. + +Older Track Model text that called Sparse SfM "future" describes historical lifecycle, not current +implementation status. ## Track definition -Un **Track** est un ensemble d'observations 2D cohérentes d'un même point -physique supposé, observé à travers plusieurs images. Chaque observation est -identifiée par `(feature_set_id, feature_index)`. +A Track is a coherent set of 2D observations believed to correspond to the same physical scene point +across multiple images. -Un Track n'est **pas** un point 3D. Il ne contient aucune coordonnée 3D, -aucune erreur de reprojection, aucun statut de triangulation. La -triangulation appartient à une étape ultérieure. - -La chaîne scientifique correcte est : +Observation identity is exactly: ```text -Matcher → Match Result → Geometric Verification → Track Builder v1 -→ Track Model → Sparse SfM (futur) -``` - -Le Matcher ne produit pas les Tracks. Le Track Builder v1 les assemble à partir -des Geometric Verification Results. - -## Observation identity - -Une observation est identifiée par : - -``` (feature_set_id, feature_index) ``` -- `feature_set_id` : identifiant SQLite AUTOINCREMENT du Feature Set. Le - Feature Set porte directement `image_id` comme colonne NOT NULL FK. L'image - est dérivable par `SELECT image_id FROM feature_sets WHERE feature_set_id=?`. -- `feature_index` : ordinal zero-based dans le tableau de keypoints du Feature - File, stable tant que le Feature Set existe. Un Feature Set publié est - immutable : aucune API de production ne modifie ses colonnes après INSERT. +`feature_set_id` identifies one immutable Feature Set. -L'identité `(feature_set_id, feature_index)` est suffisante. Il est inutile -de porter `image_id` dans la table d'observations car il est dérivable via -`feature_sets.image_id`. +`feature_index` is the zero-based keypoint ordinal inside that immutable Feature File. -Note : `feature_sets` ne possède pas de colonne d'état. L'existence d'une -ligne publiée dans la table constitue le contrat réel de disponibilité du -Feature Set. +The Feature Set directly owns `image_id`; image identity is therefore derivable and is not duplicated +in Track observation identity. -## Scientific inputs +## Scientific input -Les Tracks sont construits exclusivement à partir de : +Track Builder consumes only completed verified geometric results selected by one exact verifier +identity. -``` -Geometric Verification Result - status == GEOMETRIC_VERIFIED (2) -``` - -correspondant exactement au VERIFICATION_SELECTOR du Track Set. - -Pour chaque résultat vérifié, les entrées du Match File dont le bit -correspondant dans le masque d'inliers vaut 1 fournissent les correspondances -valides. La chaîne de dérivation est : +Current production lineage: ```text -GVR → match_result_id - → candidate_pair + feature_set_id_a + feature_set_id_b - → Match File entry[i] = (feature_index_a, feature_index_b, distance) - → bit i du masque d'inliers = 1 - → observation A: (feature_set_id_a, feature_index_a) - → observation B: (feature_set_id_b, feature_index_b) +verifier_kind = FUNDAMENTAL +verifier_version = 3 +verifier_fingerprint = +6944a471d611d8ffc59dac7cf15a5b79b97e2371d4c51785c477d68c1577f74c ``` -Un `GEOMETRIC_REJECTED` ne produit aucun track. Un Match Result non vérifié -géométriquement ne suffit pas. +Historical Track Sets created from Fundamental verifier v1 or v2 remain valid historical scientific +objects. -## VERIFICATION_SELECTOR +They must not be relabelled as v3. -Le VERIFICATION_SELECTOR définit la configuration de Geometric Verification -éligible pour un Track Set. Il est stocké sur le Track Set et fait partie de -son identité de reuse. +For each selected `GEOMETRIC_VERIFIED` result, only Match File entries whose corresponding inlier-mask +bit is one contribute observation edges. -``` -( - verifier_kind INTEGER, -- ex: 1 = FUNDAMENTAL - verifier_version INTEGER, - parameter_fingerprint BLOB(32) -) +A rejected GVR contributes no Track edge. + +## Verification selector + +A Track Set stores the exact verifier selector: + +```text +verifier_kind +verifier_version +verifier_fingerprint ``` -Le Track Builder ne consomme que les GVR avec `status == GEOMETRIC_VERIFIED` -correspondant exactement à ce tuple. Aucune sélection par timestamp, "latest" -ou ordre d'insertion n'est permise. +The builder never selects verification evidence using: -Valeur production : `(1, 1, SHA-256 de l'encodage canonique 84 octets)`. +- timestamp; +- "latest"; +- insertion order; +- approximate fingerprint match. -## INPUT_SCOPE +The current default producer is v3, but the Track Model remains version-independent and can store valid +sets from explicitly supported historical selectors. -L'INPUT_SCOPE représente l'ensemble scientifique réel des entrées consommées -par une Track Generation donnée. Il est distinct du VERIFICATION_SELECTOR : -le selector dit quels GVR sont admissibles, le scope dit quels GVR ont -effectivement été consommés. +## Input scope -``` -input_scope_hash BLOB(32) -- SHA-256 canonique -gvr_count INTEGER -- nombre de GVR consommés +A Track Set also records the exact consumed GVR scope. + +Canonical scope identity uses: + +```text +domain: L3DTSIS1 +items: geometric_verification_result_id +order: strictly increasing +encoding: uint64 little-endian +digest: SHA-256 ``` -### INPUT_SCOPE_HASH +Conceptually: -| Propriété | Valeur | -|-----------|--------| -| Domain/version | `L3DTSIS1` (8 octets ASCII) | -| Items | `geometric_verification_result_id` des GVR consommés | -| Canonical ordering | IDs triés par ordre croissant | -| Serialization | Chaque ID : 8 octets little-endian | -| Digest | SHA-256 | -| DB-local IDs | OUI — le reuse est scoped à une DB projet | -| Duplicate handling | Inutile — les IDs sont uniques par construction | -| Empty scope | Interdit — un Track Set sans GVR n'a pas de sens | +```text +SHA-256(L3DTSIS1 || id_0 || id_1 || ... || id_N) +``` -Le digest est calculé sur `L3DTSIS1` (8 octets) suivi des IDs sérialisés : -`SHA-256(L3DTSIS1 || id_0 || id_1 || ... || id_N)` où chaque `id_i` est -8 octets little-endian et les IDs sont triés par ordre croissant. +The scope is Project-DB-local because SQLite GVR IDs participate directly. -Le `gvr_count` est stocké comme métadonnée de validation. Il permet de -détecter un scope incomplet sans re-hasher. Il ne fait pas partie du hash -lui-même. +`gvr_count` is retained as validation metadata. -Le scope_hash est DB-local : il utilise les `geometric_verification_result_id` -SQLite. Deux DB distinctes avec les mêmes données produiront des IDs -différents. Le reuse est donc scoped à une seule DB projet. +An empty scope is invalid. ## Track membership invariants -1. **Minimum structurel** : un Track contient au moins 2 observations. - Une seule observation ne constitue aucune relation multi-view. Le futur - Track Builder v1, la triangulation ou le Sparse SfM pourront appliquer des - critères plus stricts. Le Model ne fixe pas de plafond de reconstruction. +### Minimum size -2. **One observation per image** : un Track ne contient pas deux observations - issues de la même image. Cette contrainte est validée par l'API lors de la - création. Le schéma v1 ne dénormalise pas `image_id` dans - `track_observations` ; l'API vérifie déterministement la relation via - `feature_sets.image_id` avant publication sous `BEGIN IMMEDIATE`. +A Track has at least two observations. - **SQL** : non protégé (pas de colonne `image_id` dans `track_observations`). - **API** : validation par jointure `feature_sets.image_id` avant INSERT. +### At most one observation per image -3. **Observation unique across tracks** : dans un même Track Set, une - observation `(feature_set_id, feature_index)` n'appartient qu'à un seul - Track. +One Track cannot contain two observations derived from the same image. - **SQL** : `PRIMARY KEY(track_set_id, feature_set_id, feature_index)` sur - `track_observations`. Le `track_set_id` est dénormalisé depuis `tracks`. - **API** : validation que `track_set_id` correspond au `track_set_id` du - `track_id` parent. +This is validated through `feature_sets.image_id`. -4. **Feature Set existence** : chaque `feature_set_id` référencé existe dans - la table `feature_sets`. La FK SQLite garantit la référence. +### Observation uniqueness inside one Track Set - **SQL** : `REFERENCES feature_sets(feature_set_id)`. +Within one Track Set: -5. **Feature index bounds** : `feature_index < feature_sets.feature_count` - pour l'observation correspondante. +```text +(feature_set_id, feature_index) +``` - **SQL** : `CHECK(feature_index >= 0)`. - **API** : validation de la borne supérieure via `feature_sets.feature_count` - (SQLite CHECK ne peut pas référencer une autre table). +belongs to at most one Track. + +The persistence schema enforces this using the Track Set-scoped primary key. + +### Feature Set existence + +Every referenced Feature Set must exist. + +### Feature index bound + +For every observation: + +```text +0 <= feature_index < feature_count +``` + +The upper bound is validated against the referenced Feature Set. + +### Parent consistency + +The denormalized Track Set ID carried by an observation must equal the Track Set of its parent Track. ## Track identity -Un Track persistant possède un identifiant opaque : +Persistent Track identity is the opaque SQLite: -``` -track_id INTEGER PRIMARY KEY AUTOINCREMENT CHECK(track_id > 0) +```text +track_id ``` -Il n'a pas d'identité scientifique dérivée de son contenu en v1. Les raisons : +Track Model v1 does not define a content-derived Track hash. -- un hash de membership rendrait les INSERTs dépendants de l'ordre ; -- le contenu d'un track peut être reconstruit depuis les GVR sources ; -- un `track_id` opaque suffit pour la persistance, le référencement et la - pagination ; -- la corruption est détectée par cohérence interne (doublons, images - manquantes, index hors bornes) plutôt que par re-hash. +Reproducibility and reuse are owned by the Track Set identity, builder configuration and exact input +scope. -La reproductibilité est assurée au niveau du Track Set (parent), pas du Track -individuel. +## Track Set identity -## Track Set / Generation +A Track Set is one complete immutable generation. -Un **Track Set** est le parent obligatoire de tout Track persistant. Il -représente une génération complète de Track Building. +Its reuse identity contains: -Champs : - -``` -track_set_id INTEGER PK AUTOINCREMENT -builder_kind TEXT(1..64) -builder_version INTEGER > 0 -parameter_fingerprint BLOB(32) -verifier_kind INTEGER -- VERIFICATION_SELECTOR -verifier_version INTEGER -verifier_fingerprint BLOB(32) -input_scope_hash BLOB(32) -gvr_count INTEGER >= 1 -track_count INTEGER >= 0 -created_at INTEGER >= 0 +```text +builder_kind +builder_version +builder_parameter_fingerprint +verifier_kind +verifier_version +verifier_fingerprint +input_scope_hash ``` -### Identité de reuse +`gvr_count` validates the scope metadata but is not an independent reuse discriminator. -``` -( - builder_kind, - builder_version, - parameter_fingerprint, - verifier_kind, - verifier_version, - verifier_fingerprint, - input_scope_hash -) -``` +`INSERT OR REPLACE` is forbidden. -`gvr_count` est stocké comme métadonnée de validation mais ne fait pas -partie de l'identité de reuse. Le même `input_scope_hash` avec un `gvr_count` -différent indiquerait une corruption (hash cohérent mais nombre de sources -incohérent). +An exact existing immutable set is reused. -Un set existant avec cette identité exacte est réutilisé. `INSERT OR REPLACE` -est interdit. +A scientifically different scope/configuration creates a new Track Set. -### Immutabilité +## Immutability -Un Track Set publié est **immutable**. Aucune opération d'append, remove ou -merge n'est permise sur un track ou un set existant. +A published Track Set is immutable. -L'invalidation scientifique (nouvelle entrée, nouveau scope, nouvelle -configuration) produit un nouveau Track Set. Le set précédent reste intact. +No production operation: -La suppression référentielle utilise `ON DELETE CASCADE` : supprimer un -Track Set supprime ses tracks et observations. +- appends to it; +- removes observations; +- merges existing Tracks; +- rewrites memberships; +- updates it to a newer verifier version. -### Justification +New evidence creates a new generation. -- chaque rebuild crée un nouveau set, les anciens restent intacts ; -- plusieurs configurations peuvent coexister (expérimentation) ; -- l'invalidation est simple : supprimer un set supprime ses tracks via - CASCADE ; -- la reproductibilité est portée par le fingerprint et le scope_hash ; -- pas d'UPDATE/INSERT/MERGE sur des tracks existants ; -- cohérent avec tous les résultats publiés existants (Feature Sets, Match - Results, GVRs) qui sont immutables après publication. - -Le Track Builder v1 construit en mémoire, puis publie un set complet -dans une transaction. Aucun track n'est visible avant que le set entier soit -validé. - -## Immutability / incrementality - -Un Track publié dans un set est **immutable**. - -L'incrémentalité est gérée par création de nouveaux sets : - -1. nouvelles images → nouveaux Match Results → nouveaux GVR → nouveau - Track Set ; -2. le set précédent reste valide et consultable ; -3. le futur Sparse SfM choisira quel set consommer. - -Cette approche est cohérente avec la philosophie Lardon3D : - -- résultats atomiques ; -- pas de destruction silencieuse ; -- reprise à frontière connue ; -- conservation de l'historique. +Historical generations remain queryable until explicitly deleted. ## Persistence -### Conceptual schema +Track storage was introduced by Project DB v14. -```sql -CREATE TABLE track_sets( - track_set_id INTEGER PRIMARY KEY AUTOINCREMENT - CHECK(track_set_id > 0), - builder_kind TEXT NOT NULL - CHECK(length(builder_kind) > 0 AND length(builder_kind) <= 64), - builder_version INTEGER NOT NULL CHECK(builder_version > 0), - parameter_fingerprint BLOB NOT NULL - CHECK(length(parameter_fingerprint) = 32), - verifier_kind INTEGER NOT NULL CHECK(verifier_kind > 0), - verifier_version INTEGER NOT NULL CHECK(verifier_version > 0), - verifier_fingerprint BLOB NOT NULL - CHECK(length(verifier_fingerprint) = 32), - input_scope_hash BLOB NOT NULL - CHECK(length(input_scope_hash) = 32), - gvr_count INTEGER NOT NULL CHECK(gvr_count >= 1), - track_count INTEGER NOT NULL CHECK(track_count >= 0), - created_at INTEGER NOT NULL CHECK(created_at >= 0), - UNIQUE(builder_kind, builder_version, parameter_fingerprint, - verifier_kind, verifier_version, verifier_fingerprint, - input_scope_hash) -); +Durable Track Builder Task payload persistence was added in Project DB v15. -CREATE TABLE tracks( - track_id INTEGER PRIMARY KEY AUTOINCREMENT CHECK(track_id > 0), - track_set_id INTEGER NOT NULL - REFERENCES track_sets(track_set_id) ON DELETE CASCADE, - observation_count INTEGER NOT NULL CHECK(observation_count >= 2) -); +Later schema migrations through v25 do not reinterpret Track Model v1. -CREATE INDEX tracks_set_idx - ON tracks(track_set_id, track_id); +Conceptual tables: -CREATE TABLE track_observations( - track_set_id INTEGER NOT NULL, - track_id INTEGER NOT NULL - REFERENCES tracks(track_id) ON DELETE CASCADE, - feature_set_id INTEGER NOT NULL - REFERENCES feature_sets(feature_set_id), - feature_index INTEGER NOT NULL CHECK(feature_index >= 0), - position_in_track INTEGER NOT NULL CHECK(position_in_track >= 0), - PRIMARY KEY(track_set_id, feature_set_id, feature_index), - UNIQUE(track_id, position_in_track) -); - -CREATE INDEX track_observations_lookup_idx - ON track_observations(feature_set_id, feature_index, track_set_id); +```text +track_sets +tracks +track_observations ``` -### Schema invariants +Publication is atomic for the complete Track Set under one transaction. -**SQL-enforced :** +No Track from that set becomes visible before the complete generation validates and commits. -- `track_observations.PRIMARY KEY(track_set_id, feature_set_id, feature_index)` - : dans un Track Set donné, une observation n'apparaît qu'une fois. Cela - garantit qu'une observation scientifique appartient à au plus un Track dans - ce set. -- `REFERENCES tracks(track_id) ON DELETE CASCADE` : l'observation appartient - à un track existant ; supprimer le track supprime l'observation. -- `REFERENCES feature_sets(feature_set_id)` : le Feature Set existe. -- `CHECK(observation_count >= 2)` : minimum structurel. -- `UNIQUE(builder_kind, builder_version, parameter_fingerprint, - verifier_kind, verifier_version, verifier_fingerprint, - input_scope_hash)` sur `track_sets` : identité de reuse, empêche les - doublons de set pour une même configuration et un même scope. -- `ON DELETE CASCADE` depuis `track_sets` : supprimer un set supprime tout. -- `CHECK(feature_index >= 0)` : borne inférieure de l'index. -- `UNIQUE(track_id, position_in_track)` : chaque position dans un track est - unique. L'ordre est déterminé par le Track Builder lors de la publication. +Rollback leaves no partial Track Set. -**API-enforced :** +## Ordering -- `track_set_id` dans `track_observations` correspond au `track_set_id` du - `track_id` parent. Le schéma ne comporte pas de FK composite (aucun - précédent dans le codebase). L'API valide cette cohérence avant INSERT sous - `BEGIN IMMEDIATE`. -- Une seule observation par image par track. L'API valide via jointure à - `feature_sets.image_id`. -- `feature_index < feature_sets.feature_count`. L'API valide la borne - supérieure. -- `observation_count` cohérent avec le nombre réel d'observations insérées. -- `track_count` cohérent avec le nombre réel de tracks insérés. -- `position_in_track` contigu à partir de 0 pour chaque track. +Track Builder publishes deterministic canonical order. -### Note sur la dénormalisation +`position_in_track` is contiguous from zero. -`track_set_id` dans `track_observations` dénormalise une clé grandparent, -après le même pattern utilisé par `visual_index_memberships.visual_index_id`. -Le pattern parent-key-in-UNIQUE est déjà répandu dans le codebase. La cohérence -repose sur le chemin d'écriture unique du Track Builder et la validation API -sous transaction. +The exact builder contract owns edge ordering and conflict resolution; Track Model only persists the +validated result. -`track_observations.track_set_id` n'a pas de FK directe vers `track_sets` -pour éviter un second chemin CASCADE depuis `track_sets` vers -`track_observations` (le premier chemin passe par `tracks`). La cohérence -est garantie par l'API sous `BEGIN IMMEDIATE`. +No hash-table iteration order may define persistent scientific ordering. -## Provenance +## Deletion -### Track Set provenance +Deleting a Track Set cascades to its Tracks and observations. -Chaque Track Set conserve : +A Feature Set referenced by a Track observation cannot be silently removed while the reference remains +valid. -- `builder_kind`, `builder_version`, `parameter_fingerprint` : configuration - du Track Builder ; -- `verifier_kind`, `verifier_version`, `verifier_fingerprint` : configuration - du Geometric Verifier consommé ; -- `input_scope_hash`, `gvr_count` : ensemble réel des GVR consommés. +Deletion semantics do not mutate other immutable Track Sets. -Ces champs suffisent pour identifier la configuration scientifique complète -ayant produit le set. +## Pagination and resource bounds -### Edge provenance (deferred) +Track Model storage APIs are paged. -En v1, la provenance détaillée (quels GVR spécifiques ont contribué à quel -track individuel) n'est pas persistée. Les raisons : +The model does not impose an arbitrary scientific maximum Track length below the number of images that +could legitimately observe the same point. -- elle peut être reconstruite en comparant les memberships du set aux GVR - disponibles ; -- une table `track_set_sources` volumineuse complexifie la DB sans bénéfice - immédiat ; -- une future version du Track Builder pourra l'ajouter dans une migration - ultérieure. +It does not materialize a dense image-by-image covisibility matrix. -## Invalidation +Loading one Track loads that Track's observations; project-wide traversal remains paged. -### Invalidation scientifique - -Un nouveau scope, une nouvelle configuration de verifier ou un nouveau -builder produit un **nouveau** Track Set avec une identité différente. Le set - précédent reste intact et consultable. Aucune mutation silencieuse n'est -permise. - -### Suppression référentielle - -`ON DELETE CASCADE` s'applique : - -- `track_sets` → `tracks` → `track_observations` : supprimer un set supprime - tous ses tracks et observations ; -- `feature_sets` → (pas de CASCADE vers `track_observations`) : la FK utilise - le comportement par défaut (NO ACTION). Supprimer un Feature Set référencé - par une observation est interdit tant que l'observation existe. - -## Atomic publication - -L'unité persistante est le Track Set complet. La publication est une seule -transaction `BEGIN IMMEDIATE` contenant l'INSERT du set, de tous ses tracks -et de toutes ses observations. - -- aucun track n'est visible avant le COMMIT du set entier ; -- un rollback ne laisse aucune ligne partielle ; -- le `created_at` du set est le timestamp de la transaction ; -- le `track_count` et `gvr_count` sont validés contre les INSERTs réels. - -Le Track Builder v1 utilise le Task Runtime pour le checkpoint/reprise et le -Resource Governor pour l'admission. Le Model ne contient aucune -logique d'exécution. - -## Resource bounds - -- **Pas de plafond de longueur arbitraire** : le Model ne fixe pas de - maximum sur le nombre d'observations par Track. Un projet avec N images - peut produire des tracks de longueur jusqu'à N. -- **Lecture paginée** : `list_tracks` et `list_track_sets` utilisent une - page de 64 entrées avec curseur. -- **Chargement borné** : load track by id charge les observations du track ; - la taille est bornée naturellement par le nombre d'images dans le scope. -- **Pas de chargement complet du graphe** : aucune API ne charge tous les - tracks et toutes les observations d'un projet en une seule fois. -- **Pas de matrice dense** : aucune matrice de co-visibilité N×N n'est - matérialisée par le Model. +Execution-memory strategy belongs to Track Builder, not Track Model. ## Corruption handling -Le loader doit détecter : +A loader returns corruption rather than partial best-effort data when it detects conditions such as: -- track absent (`track_id` référencé mais inexistant) ; -- observation invalide (`feature_set_id` inexistant) ; -- duplicate observation dans un même track set ; -- deux observations de la même image dans un même track ; -- `feature_index` hors bornes du Feature Set ; -- `observation_count` incohérent avec le nombre réel d'observations ; -- `track_set_id` dans `track_observations` ne correspondant pas au - `track_set_id` du `track_id` parent ; -- `track_set` parent absent. +- missing parent Track or Track Set; +- missing Feature Set; +- duplicate observation in one Track Set; +- repeated image inside one Track; +- out-of-range feature index; +- inconsistent observation count; +- inconsistent Track count; +- inconsistent denormalized Track Set ID; +- invalid/non-contiguous position ordering. -Toute corruption retourne `CORRUPT` sans résultat partiel. +No loader repairs scientific identity in place. -## API +## Provenance -L'API publique implémente : +Track Set provenance includes: -- `lardon3d_project_db_create_track_set()` — INSERT set + ses tracks + - observations dans une seule transaction `BEGIN IMMEDIATE`. -- `lardon3d_project_db_load_track_set()` — SELECT par ID. -- `lardon3d_project_db_find_track_set()` — SELECT par identité exacte. -- `lardon3d_project_db_list_track_sets()` — SELECT paginé ORDER BY id, - page 64. -- `lardon3d_project_db_load_track()` — SELECT par ID avec observations. -- `lardon3d_project_db_list_tracks()` — SELECT par set, paginé ORDER BY - id, page 64. -- `lardon3d_project_db_find_track_by_observation()` — recherche par - `(feature_set_id, feature_index)` dans un set donné. +```text +builder identity +verifier selector +input scope hash +gvr count +``` -La création valide en C : existence des Feature Sets, bornes des -`feature_index`, unicité des observations, unicité image par track, -`observation_count` cohérent, `track_set_id` cohérent. L'INSERT est -transactionnel. +Detailed per-edge provenance is not persisted by Track Model v1. -## Explicitly out of scope +Adding such provenance later requires an explicit version/schema decision if persistent representation +changes. -- Track Builder algorithmique (union-find, connected components) ; -- triangulation ; -- coordonnées 3D ; -- Essential matrix ; -- camera pose ; -- bundle adjustment ; -- sparse reconstruction / Sparse SfM ; -- reprojection error ; -- dense reconstruction ; -- Track optimization ou merge ; -- mutation de tracks existants ; -- co-visibilité (matrice ou calcul) ; -- sélection par timestamp ou "latest". +## Current production verifier lineage -## Track rejected state +Fundamental verifier v1 and v2 are historical scientific identities. -Le Model v1 ne persiste pas d'état Track rejected. Le Model représente des -Tracks structurellement valides (≥ 2 observations, cohérents). Le Track Builder -v1 décide quels candidats publier. Les candidats non publiés n'existent pas dans -le Model ; cette séparation reste la frontière scientifique figée. +Current new production verification uses Fundamental v3. -## Versioning +V3 adds bounded preflight rejection before the unchanged scientific estimator path and has its own +fingerprint. -Project DB v14 introduced the Track storage and v15 adds only durable Track -Builder task payload persistence. `builder_version` et `verifier_version` -décrivent indépendamment les contrats scientifiques. -Changer un algorithme n'impose une migration DB que si la représentation -persistante change. +Track Builder consumes only exact matching GVR identities. + +Therefore: + +```text +HISTORICAL_TRACK_SET_VERIFIER_V1=VALID +HISTORICAL_TRACK_SET_VERIFIER_V2=VALID +CURRENT_TRACK_SET_VERIFIER_V3=PRODUCTION +``` + +No historical Track Set is upgraded in place. + +## Real S21 evidence + +The retained S21 Track proof is: + +```text +REAL_S21_TRACKS=PASS/FROZEN + +Track Set observations = 2,495,768 +Tracks = 912,447 +minimum Track length = 2 +maximum Track length = 42 +mean Track length = 2.7352470883240341 +``` + +Retained digest: + +```text +c30eba192627bf73eaf21ff30d81038d8cc6bbf36a69226f88cdc8c37f7d74a1 +``` + +The compact memory model supersedes the older historical 18.204 GiB envelope. + +That checkpoint did not execute real Sparse SfM. + +## Real A6000 evidence + +The current retained A6000 checkpoint is: + +```text +real-a6000-pre-sfm-2026-09-02 +REAL_A6000_PRE_SFM=PASS/FROZEN +``` + +Track output: + +```text +Track Set 1 +Tracks 130,714 +Track observations 318,944 +duplicate obs 0 +repeated-image 0 +orphan obs 0 +``` + +The upstream v3 GV scope contained: + +```text +Applicable GVRs 37,805 +Verified GVRs 10,952 +Rejected GVRs 26,853 +``` + +Restart traversed the retained scope and reused the same Track Set without creating a duplicate +scientific generation. + +No Sparse SfM Task or Sparse Reconstruction was created. + +## Sparse SfM relationship + +Track Model does not perform Sparse SfM. + +Sparse SfM capability is nevertheless implemented through Gate G. + +Correct current statement: + +```text +TRACK_MODEL_OUTPUT=AVAILABLE +SPARSE_SFM_IMPLEMENTATION=AVAILABLE +REAL_HISTORICAL_CAMPAIGN_SPARSE_SFM=BLOCKED_BY_KNOWN_CALIBRATION_DATA +``` + +These are separate lifecycle facts. + +## Out of scope + +Track Model v1 does not own: + +- Track Builder union/find algorithm; +- Fundamental estimation; +- Essential estimation; +- camera pose; +- triangulation; +- 3D coordinates; +- reprojection error; +- Bundle Adjustment; +- dense reconstruction; +- metric scale; +- Track mutation/merge; +- selection by "latest". + +## Summary + +```text +TRACK_MODEL_V1=FROZEN +TRACK_BUILDER_V1=PASS/FROZEN + +CURRENT_PRODUCTION_VERIFIER=FUNDAMENTAL_V3 +CURRENT_VERIFIER_FINGERPRINT=6944a471d611d8ffc59dac7cf15a5b79b97e2371d4c51785c477d68c1577f74c + +PROJECT_DB_TRACK_MODEL=v14 +PROJECT_DB_TRACK_TASK=v15 +CURRENT_PROJECT_DB_SCHEMA=v25 + +REAL_S21_TRACKS=PASS/FROZEN +REAL_A6000_PRE_SFM=PASS/FROZEN + +SPARSE_SFM_CAPABILITY=IMPLEMENTED_THROUGH_GATE_G +REAL_S21_SPARSE_SFM=NOT_EXECUTED +REAL_A6000_SPARSE_SFM=NOT_EXECUTED +``` diff --git a/docs/architecture/visual_index.md b/docs/architecture/visual_index.md index a11df68..bbd481a 100644 --- a/docs/architecture/visual_index.md +++ b/docs/architecture/visual_index.md @@ -1,207 +1,430 @@ # Visual Index v1 -## Problème et frontière +## Status -Le Visual Index transforme une collection homogène de `FeatureSet` READY en -candidats de recherche. Il consomme exclusivement `feature_set_id` et les -descripteurs ORB lus par le Feature Reader. Il ne fait ni matching final, ni -ratio test, ni vérification géométrique. +```text +VISUAL_INDEX_V1=IMPLEMENTED +VISUAL_INDEX_KIND=orb-lsh +VISUAL_INDEX_VERSION=1 -## Choix algorithmique +CANDIDATE_PAIR=IMPLEMENTED +MATCHER=IMPLEMENTED -La v1 utilise un LSH binaire déterministe à six tables. Chaque table extrait -24 positions distinctes des 256 bits ORB. La position v1 est -`(41*table + 11*bit) mod 256`; 11 étant premier avec 256, les 24 positions -d'une table sont distinctes. Une clé est `(table_id, key24)`. Des descripteurs proches en -Hamming ont une probabilité élevée de collision dans au moins une table, sans -conversion flottante. +VISUAL_INDEX_GPU=REJECTED_WITH_MEASURED_REASON +CURRENT_PROJECT_DB_SCHEMA=v25 +REAL_A6000_PRE_SFM=PASS/FROZEN +``` -Alternatives évaluées : +Visual Index turns a homogeneous collection of immutable Feature Sets into bounded image-retrieval +candidates. -- le hash exact est très compact et déterministe, mais son rappel s'effondre - dès qu'un descriptor varie d'un bit ; -- le multi-index hashing avec sous-chaînes et multiprobes offre des garanties - Hamming intéressantes, mais le nombre de postings/probes nécessaire au - rappel utile d'ORB est trop élevé pour une v1 bornée ; -- FLANN-LSH masque son format, ses allocations et sa stabilité de - sérialisation, ce qui nuit à la reprise et à l'audit ; -- BoW/IVF donne un bon retrieval image, mais impose vocabulaire, entraînement, - identité et politique de mise à jour avant de pouvoir être incrémental ; -- HNSW et FAISS ajoutent une dépendance et un état mutable complexes sans - avantage décisif à quelques milliers d'images. +It is a retrieval stage, not a Matcher and not a geometric verifier. -Ce LSH n'est pas un matcher. Il privilégie une base déterministe, segmentable -et contrôlable. Une évolution de la sélection de bits exige une nouvelle -`visual_index_version`. +Current downstream consumers are implemented: -## Identité et configuration +```text +Feature Store +-> Visual Index +-> Candidate Pair Generator +-> Matcher +-> Geometric Verification +-> Tracks +``` -Le kind est `orb-lsh`, version 1. Un index contient exclusivement des Feature -Sets de même `descriptor_type`, dimension, `extractor_kind`, version et -`parameter_fingerprint`. Sa configuration v1 contient : +Older text describing Candidate Pair or Matcher as future consumers is historical design context and is +not current status. -- `table_count=6` ; -- `key_bits=24` ; -- `max_features_per_set` entre 1 et 1024, défaut 512 ; -- `max_bucket_postings` entre 1 et 4096, défaut 256 ; -- `max_segments=256` ; -- `max_feature_sets_per_segment=16`. +## Algorithm -Le fingerprint de paramètres est SHA-256 des 32 octets canoniques -`L3DVICF1`, version et cinq entiers little-endian. Aucun padding, JSON, locale -ou endianness hôte n'intervient. `visual_index_id` est une identité SQLite -`AUTOINCREMENT`, jamais réutilisée après publication. +Visual Index v1 uses deterministic binary LSH over ORB descriptors. -## Échantillonnage +It uses six tables. -Au plus `max_features_per_set` features sont indexées. La sélection v1 retient -le préfixe de `feature_index` croissant. Les postings conservent l'indice -original. Un Feature Set vide est membre valide sans posting. Le build Task -peut lire en parallèle jusqu'à douze Feature Files, avec un reader et une -tranche de 256 descripteurs privés par participant effectivement admis. Chaque -Feature Set écrit dans une tranche privée de la capacité de postings déjà -réservée pour le segment ; le propriétaire compacte ensuite les tranches dans -l'ordre de sélection et applique seul l'ordre total persistant. +Each table selects 24 distinct positions from the 256 ORB bits. -## Segments introduits en Project Database v6, conservés en v7 +The frozen v1 position rule is: -Un index logique possède des segments immuables READY. Chaque update publie un -segment de un à seize nouveaux Feature Sets, puis ajoute atomiquement segments -et memberships. `UNIQUE(visual_index_id,feature_set_id)` assure l'idempotence. -Une recherche copie au début la liste bornée des segments READY, relâche le -mutex DB, puis lit ce snapshot. Un segment commité au milieu sera visible à la -requête suivante. +```text +position = (41 * table + 11 * bit) mod 256 +``` -SQLite conserve les tables `visual_indexes`, `visual_index_segments`, -`visual_index_memberships` et `visual_index_update_tasks`. Les gros postings -restent hors DB. La configuration et chaque membership sont immuables. La -compaction est `NOT_YET_WIRED`; au-delà de 256 segments une update est refusée avec -`LARDON3D_VISUAL_INDEX_LIMIT`. Avec seize membres par segment, la capacité v1 est donc -exactement 4096 Feature Sets par index. Le refus ne publie ni segment ni membership et -l'index existant reste requêtable. +A posting key is: -Le DDL v6 exact est `schema_visual_v6` dans `src/project_db.c`. Il impose -`AUTOINCREMENT` aux index/segments, les uniques -`(visual_index_id,generation)`, `(visual_index_id,sha256)` et -`(visual_index_id,feature_set_id)`, ainsi que les FKs vers index, Feature Set, -segment et tâche. Les CHECKS bornent tables 1..32, bits 8..32, sampling -1..1024, bucket 1..4096, membres segment 1..16 et durabilité 0..1. La migration -entière reste sous `BEGIN IMMEDIATE` et possède une injection de rollback v6. +```text +(table_id, key24) +``` + +Changing this bit-selection policy requires a new Visual Index scientific version. + +## Identity and configuration + +Current kind/version: + +```text +orb-lsh / 1 +``` + +One index contains Feature Sets with homogeneous: + +- descriptor type; +- descriptor dimension; +- extractor kind; +- extractor version; +- extractor parameter fingerprint. + +Frozen v1 configuration contains: + +```text +table_count = 6 +key_bits = 24 +max_features_per_set = 1..1024, default 512 +max_bucket_postings = 1..4096, default 256 +max_segments = 256 +max_feature_sets_per_segment = 16 +``` + +The canonical parameter fingerprint uses domain: + +```text +L3DVICF1 +``` + +with explicit little-endian fields. + +No C struct padding, locale or host endianness enters the fingerprint. + +## Sampling + +At most `max_features_per_set` Feature entries are indexed. + +V1 selects the increasing `feature_index` prefix. + +Postings retain the original Feature index. + +An empty Feature Set is a valid member and contributes no posting. + +## Segment persistence + +Project DB v6 introduced Visual Index persistence. v7 retained the model. + +Later schema versions through v25 do not reinterpret Visual Index v1. + +A logical index owns immutable READY segments. + +One update publishes one segment containing between one and sixteen new Feature Sets. + +Membership uniqueness is enforced on: + +```text +(visual_index_id, feature_set_id) +``` + +A query snapshots the bounded READY segment list before asset reads. + +A segment committed after that snapshot is visible to the next query, not retroactively injected into +the running query. + +## Capacity + +V1 currently allows: + +```text +max_segments = 256 +max_feature_sets_per_segment = 16 +``` + +Therefore one v1 index can contain exactly up to: + +```text +4096 Feature Sets +``` + +before another update returns the Visual Index limit. + +This is an index-v1 capacity bound, not a project-wide image-count limit. + +Compaction/base-delta redesign remains deferred. ## Segment File v1 -Le fichier est little-endian et ne sérialise aucune structure C. Layout : +Segment File v1 is explicitly little-endian and does not serialize C structs. -| Offset | Taille | Champ | -|---:|---:|---| -| 0 | 8 | magic `L3DVIDX\0` | -| 8 | 4 | format version 1 | -| 12 | 4 | header size 128 | -| 16 | 4 | table count | -| 20 | 4 | key bits | -| 24 | 8 | posting count | -| 32 | 8 | member count | -| 40 | 8 | postings offset, 128 | -| 48 | 8 | total size | -| 56 | 32 | index parameter fingerprint | -| 88 | 32 | feature parameter fingerprint | -| 120 | 8 | réservés, zéro | +Magic: -Chaque posting fait 24 octets : `table_id:u32`, `key24:u32`, -`feature_set_id:u64`, `feature_index:u32`, réservé zéro `u32`. L'ordre est -`table_id`, clé, Feature Set, feature index. Le fichier exact est SHA-256 et -vit sous `assets/visual-index/<2 hex>/`. +```text +L3DVIDX\0 +``` -Publication : temporaire local, écriture, `fsync`, hash, `link` sans -écrasement, validation d'une adoption concurrente, `fsync` du répertoire, puis -transaction DB. Un échec après publication peut laisser un orphelin mais jamais -un segment READY partiel. La durabilité distingue `DURABLE` et -`PUBLISHED_NOT_DURABLE`. +A posting contains: -Le reader vérifie le SHA avant le parsing. Un fichier au SHA et aux métadonnées cohérents -mais portant une version future produit `UNSUPPORTED_VERSION`; les comptes et produits -d'offset invalides produisent `CORRUPT` avant allocation, conversion ou lecture de posting. +```text +table_id:u32 +key24:u32 +feature_set_id:u64 +feature_index:u32 +reserved_zero:u32 +``` -## Recherche, score et bornes +Canonical persistent ordering is: -L'API est centrée sur `(visual_index_id, query_feature_set_id)`. Elle accepte -`ANY_SCANSET`, `SAME_SCANSET` ou `OTHER_SCANSETS`, l'exclusion du même asset, -un minimum de preuves et `top_k` entre 1 et 256. Elle ne retourne jamais le -Feature Set ni l'image de requête. +```text +table_id +key24 +feature_set_id +feature_index +``` -Une preuve est un `feature_index` de requête distinct ayant au moins une -collision avec le candidat. Plusieurs tables, postings ou descriptors du -candidat ne multiplient pas cette preuve. Le score final vaut -`evidence_count / sampled_query_feature_count` dans `[0,1]`. Le volume du -candidat ne peut donc pas augmenter le score sans preuve distincte. L'ordre est -score décroissant, preuves décroissantes, `image_id`, puis `feature_set_id`. +The complete Segment File is content-addressed by SHA-256 under the Visual Index asset tree. -La burstiness est bornée par une contribution maximum par feature de requête -et candidat. Une première passe additionne la fréquence d'un bucket sur tous -les segments du snapshot. Au-delà de `max_bucket_postings`, il est ignoré : un motif -très commun ne peut ni allouer une liste géante ni dominer le score. Le reader -lit au plus 256 postings par appel. L'accumulateur contient au plus 4096 -candidats ; les nouveaux candidats sont ignorés après saturation, de manière -déterministe par l'ordre des postings. Aucun cache global n'existe et un seul -segment est ouvert à la fois. +## Publication -Deux updates concurrentes peuvent sélectionner le même lot et construire le même asset. -La transaction SQLite et les contraintes uniques ne laissent publier qu'un segment et -un membership par Feature Set; l'autre update échoue/rejoue en no-op. Une query prend son -snapshot de métadonnées avant les lectures et ouvre/ferme un seul segment à la fois, y -compris avec 250 à 256 segments : le nombre de descripteurs de fichier reste borné. +Publication follows the normal immutable-asset pattern: -## Tâche, reprise et ressources +```text +local temp +-> write +-> fsync +-> hash +-> no-overwrite publication/adoption validation +-> fsync directory +-> short Project DB transaction +``` -`visual_index.update`, version 1, persiste `visual_index_id` et un curseur -`after_feature_set_id`. Une séquence traite au plus seize Feature Sets non -indexés, publie et commit un segment, puis checkpoint. Pause et annulation sont -coopératives entre lectures et avant publication ; un segment déjà READY reste -valide. La reprise recommence au dernier curseur commité et l'unicité des -memberships rend le rejeu idempotent. +A physical file may remain orphaned if DB publication fails after the file is published. -**IMPLEMENTED — parallélisme interne borné.** La Queue exécute toujours un seul -callback. L'estimation demande jusqu'à seize threads CPU, un slot I/O, GPU zéro, -8 Mio fixes et 2 Mio par Feature Set, lot 1..16. Le callback compte comme un -participant et crée au plus `cpu_threads - 1` enfants. Chaque enfant lit -exclusivement des Feature Files immuables et écrit une tranche privée ; il ne -touche ni au handle Project DB partagé, ni au fichier de segment, ni au curseur. -Tous les enfants sont joints avant tri, sérialisation, publication asset et -transaction SQLite. +No partially committed READY segment is invented. -La réduction emploie l'ordre total v1 -`table_id,key24,feature_set_id,feature_index`. Le fichier, son SHA-256, le -chemin, les memberships, la génération, le fingerprint et les résultats de -requête sont donc exactement identiques à un build avec un participant. Une -erreur de lecture dans une tranche interdit toute publication ; une création -de thread refusée est remplacée par le calcul de cette tranche sur le callback, -sans changer la réduction. Le curseur n'avance qu'après la publication -transactionnelle du segment, puis le checkpoint existant reste le seul point -de reprise Task. `record_batch` reçoit le nombre de Feature Sets réellement -commités, la durée réelle et `peak_memory_bytes=0` (inconnue). +Durability distinguishes: -## Complexité et limites +```text +DURABLE +PUBLISHED_NOT_DURABLE +``` -Pour `D` descriptors échantillonnés, construction et disque sont `O(6D)`. -Une requête effectue `O(6Q log P + H)` par segment (`Q<=1024`, `H` hits bornés), -pas `O(images²)`. La mémoire build est bornée par les métadonnées, les tranches -de 256 descripteurs privées des participants et les postings d'un segment ; les -tranches privées partitionnent le buffer de postings existant et ne le -dupliquent pas. Chaque participant garde au plus un reader/FD de Feature File. -La mémoire query est bornée par 4096 candidats, 256 postings et 256 résultats. -À 3700 images et 512 features, environ 11,4 -millions de postings sont produits. Un test structurel persiste 50 000 Feature Sets puis -confirme la pagination par 16 et le refus propre après 4096 memberships. Un index unique -ne couvre donc pas encore 50 000 images : le risque principal est le nombre de segments -et les seeks. Une compaction/base+delta ou une évolution v2 sera nécessaire, sans changer -les identités durables; elle est `NOT_YET_WIRED`. +## Reader validation -Les fixtures de validation incluent un Feature Set vide, un crop réel, une rotation de -8 degrés, deux campagnes, un asset source partagé et une attaque de motif répétitif. Ces -tests valident le classement de candidats LSH, jamais une compatibilité géométrique. +The reader validates the asset SHA before trusting the format. -## Frontière future +It rejects: -Le Candidate Pair Generator pourra filtrer sur score et `evidence_count`, puis -transmettre `feature_set_id + feature_index` au futur matcher. Le score Visual -Index ne constitue jamais une preuve géométrique. +- invalid counts; +- invalid offsets; +- overflow; +- malformed reserved fields; +- inconsistent DB metadata; +- unsupported future version. + +A coherent future format version is `UNSUPPORTED_VERSION`, not generic corruption. + +## Query + +Query identity is centered on: + +```text +(visual_index_id, query_feature_set_id) +``` + +Options include: + +- ScanSet filter; +- same/other ScanSet policy; +- source-asset exclusion; +- minimum evidence count; +- `top_k` in `1..256`. + +The source Feature Set/image is never returned as its own candidate. + +## Evidence and score + +One evidence unit is one distinct query `feature_index` that collides with the candidate. + +Multiple tables or candidate postings do not multiply the same query-feature evidence. + +Score: + +```text +evidence_count / sampled_query_feature_count +``` + +Range: + +```text +0..1 +``` + +Canonical result order: + +```text +score descending +evidence_count descending +image_id ascending +feature_set_id ascending +``` + +Visual Index score is retrieval evidence only. + +It is not descriptor-match evidence and not geometric evidence. + +## Burstiness bound + +Bucket frequency is bounded across the retained query snapshot. + +A bucket above `max_bucket_postings` is ignored. + +This prevents common patterns from dominating score or creating unbounded posting accumulation. + +The query accumulator is bounded to 4096 candidates and 256 returned results. + +No global query cache is required. + +## Durable Task + +Task Kind: + +```text +visual_index.update/1 +``` + +Durable cursor: + +```text +after_feature_set_id +``` + +One sequence handles a bounded admitted set of new Feature Sets, publishes a complete segment, commits +memberships, advances the cursor/checkpoint and returns through `sequence_break()` if more work remains. + +Restart resumes from the durable cursor and membership uniqueness makes replay idempotent. + +## Internal parallelism + +The Queue owns one active heavy callback. + +Visual Index may use bounded internal CPU participants inside that callback. + +Current validated shape: + +```text +CPU up to 16 +batch/window 1..16 +GPU 0 +fixed RAM approximately 8 MiB +per-item RAM approximately 2 MiB +``` + +Each participant reads immutable Feature data into private work. + +Participants do not publish the segment. + +After join, the owner performs canonical total ordering, serialization, asset publication and Project DB +commit. + +Thread-creation failure may fall back to owner computation of that slice without changing output. + +## Determinism + +The following must match the serial scientific result: + +- posting set; +- posting order; +- Segment File bytes; +- SHA-256; +- membership set; +- generation ordering; +- query results. + +Operational CPU width does not enter scientific identity. + +## GPU policy + +Current GPU classification: + +```text +VISUAL_INDEX_GPU=REJECTED_WITH_MEASURED_REASON +``` + +The stage is dominated by posting construction, total ordering, hashing and deterministic publication, +and there is no validated production GPU seam that preserves the full contract with useful measured +benefit. + +This does not authorize avoidable CPU serialism. + +```text +RESOURCE_UTILIZATION_POLICY=MAXIMUM_SAFE_USEFUL_THROUGHPUT +SERIALISM_REQUIRES_PROOF=CANONICAL +``` + +## Current downstream relationship + +Candidate Pair Generator is implemented and consumes Visual Index queries. + +Matcher is implemented and consumes persisted Candidate Pairs. + +Therefore current relationship is: + +```text +Visual Index +-> Candidate Pair Generator +-> Candidate Pair persistence +-> Matcher +``` + +Visual Index does not pass raw `feature_set_id + feature_index` pairs directly into a hypothetical +future Matcher. + +The persisted Candidate Pair boundary remains explicit. + +## Real A6000 evidence + +The retained A6000 proof contains: + +```text +Feature Sets 689 +Candidate Pairs 38,420 +Match Results 38,420 +``` + +Final continuation replayed no new Visual Index work. + +Checkpoint: + +```text +real-a6000-pre-sfm-2026-09-02 +REAL_A6000_PRE_SFM=PASS/FROZEN +``` + +This confirms the current Visual Index path was already durably reusable before downstream GV/Tracks +continuation. + +## Limits + +Current v1 limits/non-goals include: + +- no segment compaction; +- one index limited to 4096 Feature Sets; +- no GPU backend; +- no geometric meaning assigned to retrieval score; +- no dense project-wide pair matrix. + +A future index version may change capacity or data structure only through an explicit versioned +scientific/persistence decision. + +## Summary + +```text +VISUAL_INDEX_V1=IMPLEMENTED +VISUAL_INDEX_KIND=orb-lsh +VISUAL_INDEX_VERSION=1 +VISUAL_INDEX_CAPACITY=4096_FEATURE_SETS +VISUAL_INDEX_SEGMENT_MEMBERS=16 +VISUAL_INDEX_TOP_K_MAX=256 + +VISUAL_INDEX_TASK=visual_index.update/1 +VISUAL_INDEX_GPU=REJECTED_WITH_MEASURED_REASON + +CANDIDATE_PAIR=IMPLEMENTED +MATCHER=IMPLEMENTED + +CURRENT_PROJECT_DB_SCHEMA=v25 +REAL_A6000_PRE_SFM=PASS/FROZEN +```