fix(pipeline): finish three changes that had only been half applied
Each of these was recorded as done and was done in one place out of two. AR-011 -- the TransNetV2 dedup window. The derived window (dedup_window_sec, median observed interval halved) reached scenes.json and nothing else. SceneBoundaries, the path that actually feeds is_scene_boundary to the tracker, kept the literal 0.04 s under a comment claiming it "matches the dedup scenes.json applies, so the two views agree". They did not agree. 0.04 is one frame at 25 fps and wider than a frame at 30, so two cuts on consecutive frames merged into one and the loss was invisible: the pipeline simply saw fewer boundaries. The detector now supplies the window it derived. AR-019 -- ownership. The register says ownership "comes from the registry, not a second local tally". Both existed: promotion fired on a local accepted-frame count and fell back to a local per-actor plurality when the registry had not yet claimed the track. That fallback was reachable in the live pipeline, not just in tests -- three accepted frames arrive well before a posterior crosses the ownership threshold -- so in practice the plurality usually decided, and it could not see the AR-025 correlation discounting it was meant to defer to. The tally is gone; promotion now requires the registry's verdict, with the accepted -frame count demoted to an explicit evidence floor. AR-017 -- the route. DeadTrack carried belief but no route, and the sink wrote the literal string "live", so a field the schema publishes could not distinguish anything. AR-017's own verification asks for "deferred and pooled routes distinguishable". Route is now an enum on the claim. Only `live` occurs today; `deferred` exists so AR-020's pass has somewhere to write instead of a serialisation change to make. Also: TrackGallery::forget had no callers, under a comment asserting the matcher called it "on a cut or track disappearance". The cut half was true by another route; the disappearance half was not, so a track that died quietly kept its diversity buffer until the next cut cleared everything. Replaced with prune_dead against the registry's own liveness, the same shape as the tracker's prune_boxes -- a second opinion about which tracks exist is a second thing that can be wrong. Removes dead logistic/logit helpers and fixes five TRACES tags that used a comma where a pipe separates requirement types, which the gate had been reporting as diagnostics. TRACES: AR-011, AR-017, AR-019 | IR-002 | SR-002, SR-005
This commit is contained in:
+36
-11
@@ -44,12 +44,39 @@
|
||||
// A finished presence claim, emitted exactly once when a track is reaped or
|
||||
// flushed. Immutable by construction: it carries everything needed to justify
|
||||
// itself (AR-017), with no back-reference into registry state.
|
||||
/// TRACES: AR-017 | IR-002 | SR-002, SR-003
|
||||
/// How an actor came to be attached to a track.
|
||||
///
|
||||
/// AR-017 requires every presence claim to carry its identification route, and
|
||||
/// IR-002 publishes it per window. Until now the sink wrote the string "live"
|
||||
/// unconditionally, so the field existed but could not distinguish anything --
|
||||
/// and AR-017's own verification asks for "deferred and pooled routes
|
||||
/// distinguishable".
|
||||
///
|
||||
/// Only `live` occurs today. `deferred` is what AR-020's pass will set when it
|
||||
/// resolves a track that failed during streaming and was identified against the
|
||||
/// final expanded gallery; the value exists now so that pass has somewhere to
|
||||
/// write rather than a serialisation change to make.
|
||||
enum class Route {
|
||||
live, ///< identified while streaming, from accumulated per-frame evidence
|
||||
deferred, ///< resolved after EOF against the expanded gallery (AR-020)
|
||||
};
|
||||
|
||||
inline const char* route_name(Route r) {
|
||||
switch (r) {
|
||||
case Route::deferred: return "deferred";
|
||||
case Route::live: break;
|
||||
}
|
||||
return "live";
|
||||
}
|
||||
|
||||
struct DeadTrack {
|
||||
int track_id{-1};
|
||||
double first_seen{0.0};
|
||||
double last_seen{0.0}; ///< always the last sighting, never the death time
|
||||
int actor_idx{-1}; ///< -1 when the track was never owned
|
||||
float belief{0.0f}; ///< accumulated posterior for actor_idx
|
||||
Route route{Route::live}; ///< how the actor was attached (AR-017)
|
||||
int observations{0}; ///< evidence updates that landed on this track
|
||||
float effective_obs{0.f}; ///< sum of discounted weights — the evidence that counted
|
||||
};
|
||||
@@ -215,6 +242,15 @@ public:
|
||||
// ── Diagnostics ──────────────────────────────────────────────────────────
|
||||
// These measure how often tracking is silently wrong, which nothing in the
|
||||
// pipeline currently reveals.
|
||||
/// Whether the registry still holds this track. The authority on which
|
||||
/// tracks exist, so annotating structures elsewhere (spatial boxes in the
|
||||
/// tracker, diversity buffers in the expansion store) can prune against it
|
||||
/// rather than keeping a second opinion.
|
||||
bool is_live(int track_id) const {
|
||||
std::lock_guard g(mu_);
|
||||
return tracks_.count(track_id) != 0;
|
||||
}
|
||||
|
||||
int dropped_votes() const { std::lock_guard g(mu_); return dropped_votes_; }
|
||||
int belief_swaps() const { std::lock_guard g(mu_); return belief_swaps_; }
|
||||
int actor_conflicts() const { std::lock_guard g(mu_); return actor_conflicts_; }
|
||||
@@ -340,17 +376,6 @@ private:
|
||||
for (int i = 0; i < 512; ++i) t.mean[i] = static_cast<float>(t.mean[i] / norm);
|
||||
}
|
||||
|
||||
static float logistic(float z) {
|
||||
return z >= 0 ? 1.f / (1.f + std::exp(-z))
|
||||
: std::exp(z) / (1.f + std::exp(z));
|
||||
}
|
||||
|
||||
static float logit(float p) {
|
||||
const float eps = 1e-6f;
|
||||
p = std::min(1.f - eps, std::max(eps, p));
|
||||
return std::log(p / (1.f - p));
|
||||
}
|
||||
|
||||
Config cfg_;
|
||||
EvidenceDiscounter discounter_;
|
||||
mutable std::mutex mu_;
|
||||
|
||||
Reference in New Issue
Block a user