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:
2026-08-05 17:33:12 +02:00
parent 7c7d4934ae
commit 88c42573a5
9 changed files with 163 additions and 64 deletions
+25 -4
View File
@@ -27,15 +27,35 @@
class SceneBoundaries {
public:
/// Peaks closer than this are one boundary. Matches the dedup scenes.json
/// applies, so the two views agree.
static constexpr double kMergeSec = 0.04;
/// TRACES: AR-011 | SR-002
/// Peaks closer than this are one boundary.
///
/// Supplied by the detector, derived from the cadence it was actually fed
/// (SceneDetectorFunc::dedup_window_sec), NOT assumed. It used to be a hard
/// 0.04 here, and AR-011 is recorded as having replaced that literal --
/// which it did, but only for scenes.json. This path, the one that feeds
/// is_scene_boundary into the tracker, kept the constant while the comment
/// above it claimed "matches the dedup scenes.json applies, so the two
/// views agree". They did not agree. 0.04 s 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.
///
/// Zero until the detector sets it, which makes the pre-cadence state a
/// no-op dedup rather than a wrong one -- adjacent peaks stay separate
/// until there is evidence about how far apart frames are, and is_boundary
/// absorbs duplicates in its tolerance anyway.
void set_merge_window(double sec) {
std::lock_guard<std::mutex> g(mu_);
merge_sec_ = sec;
}
/// Called by the scene detector as each window is scored. `through` is the
/// timestamp up to which its verdict is now final.
void publish(const std::vector<double>& ts, double through) {
{
std::lock_guard<std::mutex> g(mu_);
const double merge = merge_sec_;
// Dedup on insert, matching what scenes.json does at write time. A run
// of adjacent high-scoring frames is one boundary, not several, and
// leaving them raw made this view report 357 where the file said 13 —
@@ -45,7 +65,7 @@ public:
bounds_.insert(bounds_.end(), ts.begin(), ts.end());
std::sort(bounds_.begin(), bounds_.end());
bounds_.erase(std::unique(bounds_.begin(), bounds_.end(),
[](double a, double b) { return b - a < kMergeSec; }),
[merge](double a, double b) { return b - a < merge; }),
bounds_.end());
scored_through_ = std::max(scored_through_, through);
}
@@ -120,6 +140,7 @@ private:
mutable std::mutex mu_;
mutable std::condition_variable cv_;
bool finished_{false};
double merge_sec_{0.0}; ///< set by the detector; see set_merge_window
std::vector<double> bounds_;
double scored_through_{-1.0};
mutable std::size_t outran_{0};