docs: reconcile current reconstruction architecture

This commit is contained in:
fy59 2026-09-02 22:30:33 +02:00
parent 7574362ba9
commit a3a005b38e
6 changed files with 2407 additions and 2723 deletions

View file

@ -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
```

View file

@ -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
```

View file

@ -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
```

File diff suppressed because it is too large Load diff

View file

@ -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
```
### Identité de reuse
```
(
builder_kind,
builder_version,
parameter_fingerprint,
verifier_kind,
verifier_version,
verifier_fingerprint,
```text
builder_kind
builder_version
builder_parameter_fingerprint
verifier_kind
verifier_version
verifier_fingerprint
input_scope_hash
)
```
`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).
`gvr_count` validates the scope metadata but is not an independent reuse discriminator.
Un set existant avec cette identité exacte est réutilisé. `INSERT OR REPLACE`
est interdit.
`INSERT OR REPLACE` is forbidden.
### Immutabilité
An exact existing immutable set is reused.
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 scientifically different scope/configuration creates a new Track Set.
L'invalidation scientifique (nouvelle entrée, nouveau scope, nouvelle
configuration) produit un nouveau Track Set. Le set précédent reste intact.
## Immutability
La suppression référentielle utilise `ON DELETE CASCADE` : supprimer un
Track Set supprime ses tracks et observations.
A published Track Set is immutable.
### Justification
No production operation:
- 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.
- appends to it;
- removes observations;
- merges existing Tracks;
- rewrites memberships;
- updates it to a newer verifier version.
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é.
New evidence creates a new generation.
## 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 ;
## Current production verifier lineage
Fundamental verifier v1 and v2 are historical scientific identities.
Current new production verification uses Fundamental v3.
V3 adds bounded preflight rejection before the unchanged scientific estimator path and has its own
fingerprint.
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;
- bundle adjustment ;
- sparse reconstruction / Sparse SfM ;
- triangulation;
- 3D coordinates;
- reprojection error;
- Bundle Adjustment;
- dense reconstruction;
- Track optimization ou merge ;
- mutation de tracks existants ;
- co-visibilité (matrice ou calcul) ;
- sélection par timestamp ou "latest".
- metric scale;
- Track mutation/merge;
- selection by "latest".
## Track rejected state
## Summary
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.
```text
TRACK_MODEL_V1=FROZEN
TRACK_BUILDER_V1=PASS/FROZEN
## Versioning
CURRENT_PRODUCTION_VERIFIER=FUNDAMENTAL_V3
CURRENT_VERIFIER_FINGERPRINT=6944a471d611d8ffc59dac7cf15a5b79b97e2371d4c51785c477d68c1577f74c
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.
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
```

View file

@ -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>/<sha256 lowercase>`.
```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
```