From fe29d014da62e4c3a2e72f8a054344c63a8c5412 Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Fri, 31 Jul 2026 10:05:59 +0200 Subject: [PATCH] feat: identity evidence reaches the registry Closes the link that made AR-012 inert: the tracker was maintaining registry state, but nothing called observe(), so no belief accumulated, no track was ever owned, and no presence claim could be emitted. Tracking worked and presence did not. The matcher now feeds every scored face to the registry as a calibrated posterior plus its embedding. Deliberately every scored face, not only the ones clearing prob_threshold: a run of near-misses for one actor is evidence, and discarding it would leave ownership depending on the per-frame threshold this redesign exists to stop relying on. The registry discounts for correlation and decides ownership from the accumulated posterior (AR-025). The registry is an optional dependency of the matcher. Without one it behaves exactly as before, which keeps the replay harness and the unit tests working unchanged rather than forcing every caller to construct a registry it does not need. Co-Authored-By: Claude Opus 5 TRACES: AR-012, AR-025 | SR-002 --- src/main.cpp | 2 ++ src/nodes/identity_matcher_node.hpp | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index 6711154..13f2131 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -206,6 +206,8 @@ int main(int argc, char** argv) { auto registry = std::make_shared( reg_cfg, EvidenceDiscounter(same_person)); + matcher_fn.set_registry(registry); + FaceTrackerFunc ftracker_fn{cfg, registry, same_person}; SceneTrackerFunc tracker_fn {cfg}; ResultSinkFunc sink_fn {cfg, done}; diff --git a/src/nodes/identity_matcher_node.hpp b/src/nodes/identity_matcher_node.hpp index e72233c..0f82004 100644 --- a/src/nodes/identity_matcher_node.hpp +++ b/src/nodes/identity_matcher_node.hpp @@ -5,6 +5,7 @@ #include "gallery/gallery_store.hpp" #include "gallery/gallery_calibration.hpp" #include "gallery/track_gallery.hpp" +#include "track_registry.hpp" #include #include @@ -115,6 +116,12 @@ struct IdentityMatcherFunc { /// mean different things. See `same_person_probability`. const GalleryCalibration& calibration() const { return cal_; } + /// TRACES: AR-012, AR-025 | SR-002 + /// Where per-frame identity evidence reaches the registry. Optional: with no + /// registry attached the matcher behaves exactly as before, which keeps the + /// replay harness and the unit tests working unchanged. + void set_registry(std::shared_ptr r) { registry_ = std::move(r); } + // Runtime setter — lets a persistent pipeline be reused across a threshold sweep // without rebuilding the (expensive, gallery-resident) matcher. The gallery, // calibration and GPU sim-engine stay put; only the accept threshold changes. @@ -232,6 +239,19 @@ struct IdentityMatcherFunc { // face (annex already folded in above); the track's diversity buffer // keeps the gallery-far views and promotes them once the track is // confirmed. No-op unless --expand-gallery is set. + // TRACES: AR-012, AR-025 | SR-002 + // Every scored face is evidence, not only the accepted ones: a run of + // near-misses for one actor is itself informative, and discarding it + // would make ownership depend on a per-frame threshold the redesign + // exists to stop relying on. The registry discounts for correlation + // and decides ownership from the accumulated posterior (AR-025). + if (registry_ && best_actor >= 0 && tf.track_ids[fi] >= 0) { + const float p = cal_.valid + ? cal_.probability(best_s, log_prior_odds_) + : std::max(0.f, best_s); + registry_->observe(tf.track_ids[fi], best_actor, p, tf.embeddings[fi]); + } + track_gallery_.observe(tf.track_ids[fi], tf.embeddings[fi], best_actor, best_s, accept, tf.crops[fi]); @@ -255,4 +275,5 @@ private: std::unique_ptr sim_engine_; TrackGallery track_gallery_; + std::shared_ptr registry_; };