feat: expansion promotion gated on all three discontinuity signals
AR-019 — promotion may only borrow same-identity evidence from a span where identity is certain, so every discontinuity signal now clears the buffers rather than just the histogram cut. is_scene_boundary was already named in the gate but never set by anything, so that half of it was dead until AR-010 gave it a producer. It now does what the spec always said. The third signal, an identity contradiction, needs no code here: AR-015 closes a track whose belief swapped, so it can no longer promote. Ownership now comes from the registry rather than a second tally. TrackGallery was computing its own plurality vote over accepted frames, which meant two different answers to "who is this track" could coexist in one run — and the expansion one ignored the Bayesian accumulation entirely, weighting thirty near-identical looks the same as thirty distinct ones. The local tally survives only as a fallback for callers with no registry attached, which is the unit tests and the replay harness. Suite: 92 cases, 6133 assertions. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> TRACES: AR-019, AR-010, AR-015 | SR-005
This commit is contained in:
@@ -46,7 +46,7 @@ Status: `Done` · `In Progress` · `Planned` · `TBD` · `Withdrawn`
|
|||||||
| AR-016 | All tracks closed at EOF — a film ends with faces on screen | SR-002 | High | **Done** — `flush()`, idempotent, closes at last sighting or final tick |
|
| AR-016 | All tracks closed at EOF — a film ends with faces on screen | SR-002 | High | **Done** — `flush()`, idempotent, closes at last sighting or final tick |
|
||||||
| AR-017 | Every presence claim carries its belief and identification route | SR-002 | High | **Done** — `DeadTrack` carries belief and observation count |
|
| AR-017 | Every presence claim carries its belief and identification route | SR-002 | High | **Done** — `DeadTrack` carries belief and observation count |
|
||||||
| AR-018 | Per-subject embedding store with banded admission (novel enough, safe enough) | SR-005 | Medium | **Done** — banded admission in probability space; replaces `expand_novelty_sim`. Rejections counted |
|
| AR-018 | Per-subject embedding store with banded admission (novel enough, safe enough) | SR-005 | Medium | **Done** — banded admission in probability space; replaces `expand_novelty_sim`. Rejections counted |
|
||||||
| AR-019 | Per-film gallery annex from owned tracks; acquires the non-frontal views TMDB lacks | SR-005 | Medium | In Progress |
|
| AR-019 | Per-film gallery annex from owned tracks; acquires the non-frontal views TMDB lacks | SR-005 | Medium | **Done** — all three discontinuity signals clear the buffers; ownership comes from the registry, not a second local tally |
|
||||||
| AR-020 | Deferred re-identification of unknown tracks against the final expanded gallery | SR-005 | High | Planned |
|
| AR-020 | Deferred re-identification of unknown tracks against the final expanded gallery | SR-005 | High | Planned |
|
||||||
| AR-021 | Cluster unknown tracks into one entity per person, under temporal cannot-link constraints | SR-005 | Medium | Planned |
|
| AR-021 | Cluster unknown tracks into one entity per person, under temporal cannot-link constraints | SR-005 | Medium | Planned |
|
||||||
| AR-022 | Capture still-unidentified tracks: embeddings, metadata, **context crops** for human review | §4 | Medium | Planned |
|
| AR-022 | Capture still-unidentified tracks: embeddings, metadata, **context crops** for human review | §4 | Medium | Planned |
|
||||||
|
|||||||
+46
-27
@@ -3,7 +3,7 @@
|
|||||||
<!-- GENERATED FILE - do not edit by hand. -->
|
<!-- GENERATED FILE - do not edit by hand. -->
|
||||||
<!-- Regenerate: scripts/traceability/traceability-gate.sh -->
|
<!-- Regenerate: scripts/traceability/traceability-gate.sh -->
|
||||||
|
|
||||||
**Generated:** 2026-07-31T13:06:54+00:00
|
**Generated:** 2026-07-31T13:11:03+00:00
|
||||||
|
|
||||||
Denominators are read from [`requirements.md`](requirements.md) at run time, never hardcoded. Coverage counts a requirement only when it is tagged in source **and** has a verification tier this repo's CI host can execute (`T1, T2, T3, static`).
|
Denominators are read from [`requirements.md`](requirements.md) at run time, never hardcoded. Coverage counts a requirement only when it is tagged in source **and** has a verification tier this repo's CI host can execute (`T1, T2, T3, static`).
|
||||||
|
|
||||||
@@ -12,12 +12,12 @@ Denominators are read from [`requirements.md`](requirements.md) at run time, nev
|
|||||||
| Metric | Value |
|
| Metric | Value |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Source files scanned | 108 |
|
| Source files scanned | 108 |
|
||||||
| TRACES tags found | 105 |
|
| TRACES tags found | 110 |
|
||||||
| EXCEPTION tags found | 0 |
|
| EXCEPTION tags found | 0 |
|
||||||
| Requirements defined | 67 |
|
| Requirements defined | 67 |
|
||||||
| Requirements covered | 31 |
|
| Requirements covered | 32 |
|
||||||
| **Coverage** | **46.3%** (31/67) |
|
| **Coverage** | **47.8%** (32/67) |
|
||||||
| Coverage of CI-executable scope | 56.4% (31/55) |
|
| Coverage of CI-executable scope | 58.2% (32/55) |
|
||||||
| Tagged but unexecuted in CI | 4 |
|
| Tagged but unexecuted in CI | 4 |
|
||||||
| Orphan tags | 0 |
|
| Orphan tags | 0 |
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ Denominators are read from [`requirements.md`](requirements.md) at run time, nev
|
|||||||
|
|
||||||
| Type | Covered | Tagged but unexecuted | Defined |
|
| Type | Covered | Tagged but unexecuted | Defined |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| AR | 18 | 1 | 30 |
|
| AR | 19 | 1 | 30 |
|
||||||
| DP | 2 | 0 | 8 |
|
| DP | 2 | 0 | 8 |
|
||||||
| IR | 8 | 0 | 8 |
|
| IR | 8 | 0 | 8 |
|
||||||
| GR | 3 | 0 | 9 |
|
| GR | 3 | 0 | 9 |
|
||||||
@@ -83,7 +83,7 @@ _None._
|
|||||||
| AR-002 | Planned | T2 | SR-002 | untagged | - | Minimum face size **32×32 px** (VR-005 measured), expressed in **orig… |
|
| AR-002 | Planned | T2 | SR-002 | untagged | - | Minimum face size **32×32 px** (VR-005 measured), expressed in **orig… |
|
||||||
| AR-003 | **Done** — `max_fac… | T1, T2, T4 | SR-002 | covered | `src/config.hpp`, `src/nodes/face_detector_node.hpp`, `src/nodes/identity_matcher_node.hpp` | No fixed per-frame face cap — crowd scenes must not lose background c… |
|
| AR-003 | **Done** — `max_fac… | T1, T2, T4 | SR-002 | covered | `src/config.hpp`, `src/nodes/face_detector_node.hpp`, `src/nodes/identity_matcher_node.hpp` | No fixed per-frame face cap — crowd scenes must not lose background c… |
|
||||||
| AR-004 | **Done** — KPN node… | T1, T4 | SR-002 | covered | `src/main.cpp`, `src/nodes/identity_matcher_node.hpp`, `tests/test_replay_fixtures.cpp` | Backpressure: unbounded faces/frame absorbed by slowing, never by dro… |
|
| AR-004 | **Done** — KPN node… | T1, T4 | SR-002 | covered | `src/main.cpp`, `src/nodes/identity_matcher_node.hpp`, `tests/test_replay_fixtures.cpp` | Backpressure: unbounded faces/frame absorbed by slowing, never by dro… |
|
||||||
| AR-005 | Done | T1, T3 | SR-002 | covered | `src/face_utils.hpp` | Align to 112×112 via ArcFace 5-point similarity transform |
|
| AR-005 | **Done** — `umeyama… | T1, T3 | SR-002 | covered | `src/face_utils.hpp`, `tests/test_face_utils.cpp` | Align to 112×112 via ArcFace 5-point similarity transform, fitted by … |
|
||||||
| AR-006 | Done | T3 | SR-002 | untagged | - | 512-d L2-normalised embeddings, batched |
|
| AR-006 | Done | T3 | SR-002 | untagged | - | 512-d L2-normalised embeddings, batched |
|
||||||
| AR-007 | **Done** — `track_a… | T2 | SR-002 | covered | `src/config.hpp`, `src/main.cpp`, `src/nodes/face_tracker_node.hpp` | Associate detections by IoU + embedding, with **frame-dependent** wei… |
|
| AR-007 | **Done** — `track_a… | T2 | SR-002 | covered | `src/config.hpp`, `src/main.cpp`, `src/nodes/face_tracker_node.hpp` | Associate detections by IoU + embedding, with **frame-dependent** wei… |
|
||||||
| AR-008 | **Done** — one pool… | T2 | SR-002 | covered | `src/config.hpp`, `src/main.cpp`, `src/nodes/face_tracker_node.hpp` | One track pool keyed on `last_seen`; no separate revival path |
|
| AR-008 | **Done** — one pool… | T2 | SR-002 | covered | `src/config.hpp`, `src/main.cpp`, `src/nodes/face_tracker_node.hpp` | One track pool keyed on `last_seen`; no separate revival path |
|
||||||
@@ -96,19 +96,19 @@ _None._
|
|||||||
| AR-015 | **Done** — reverse … | T2 | SR-002 | covered | `src/track_registry.hpp`, `tests/test_track_registry.cpp` | Two live tracks owned by one actor ⇒ treat as a detected cut, re-asso… |
|
| AR-015 | **Done** — reverse … | T2 | SR-002 | covered | `src/track_registry.hpp`, `tests/test_track_registry.cpp` | Two live tracks owned by one actor ⇒ treat as a detected cut, re-asso… |
|
||||||
| AR-016 | **Done** — `flush()… | T2 | SR-002 | covered | `src/main.cpp`, `src/nodes/result_sink_node.hpp`, `src/track_registry.hpp`, `tests/test_track_registry.cpp` | All tracks closed at EOF — a film ends with faces on screen |
|
| AR-016 | **Done** — `flush()… | T2 | SR-002 | covered | `src/main.cpp`, `src/nodes/result_sink_node.hpp`, `src/track_registry.hpp`, `tests/test_track_registry.cpp` | All tracks closed at EOF — a film ends with faces on screen |
|
||||||
| AR-017 | **Done** — `DeadTra… | T1, T2 | SR-002 | covered | `src/nodes/result_sink_node.hpp`, `src/track_registry.hpp`, `tests/test_track_registry.cpp` | Every presence claim carries its belief and identification route |
|
| AR-017 | **Done** — `DeadTra… | T1, T2 | SR-002 | covered | `src/nodes/result_sink_node.hpp`, `src/track_registry.hpp`, `tests/test_track_registry.cpp` | Every presence claim carries its belief and identification route |
|
||||||
| AR-018 | Planned | T1, T2 | SR-005 | untagged | - | Per-subject embedding store with banded admission (novel enough, safe… |
|
| AR-018 | **Done** — banded a… | T1, T2 | SR-005 | covered | `src/config.hpp`, `src/gallery/track_gallery.hpp`, `src/nodes/identity_matcher_node.hpp` | Per-subject embedding store with banded admission (novel enough, safe… |
|
||||||
| AR-019 | In Progress | T2 | SR-005 | untagged | - | Per-film gallery annex from owned tracks; acquires the non-frontal vi… |
|
| AR-019 | In Progress | T2 | SR-005 | untagged | - | Per-film gallery annex from owned tracks; acquires the non-frontal vi… |
|
||||||
| AR-020 | Planned | T2 | SR-005 | untagged | - | Deferred re-identification of unknown tracks against the final expand… |
|
| AR-020 | Planned | T2 | SR-005 | untagged | - | Deferred re-identification of unknown tracks against the final expand… |
|
||||||
| AR-021 | Planned | T2 | SR-005 | untagged | - | Cluster unknown tracks into one entity per person, under temporal can… |
|
| AR-021 | Planned | T2 | SR-005 | untagged | - | Cluster unknown tracks into one entity per person, under temporal can… |
|
||||||
| AR-022 | Planned | T1, T2 | §4 | untagged | - | Capture still-unidentified tracks: embeddings, metadata, **context cr… |
|
| AR-022 | Planned | T1, T2 | §4 | untagged | - | Capture still-unidentified tracks: embeddings, metadata, **context cr… |
|
||||||
| AR-023 | Done | T1 | SR-002 | covered | `src/gallery/gallery_calibration.hpp`, `src/nodes/identity_matcher_node.hpp` | Fit sigmoid calibration from intra/inter similarity distributions |
|
| AR-023 | Done | T1 | SR-002 | covered | `src/gallery/gallery_calibration.hpp`, `src/nodes/identity_matcher_node.hpp` | Fit sigmoid calibration from intra/inter similarity distributions |
|
||||||
| AR-024 | **Done** — associat… | T1, static | SR-002 | covered | `src/config.hpp`, `src/evidence_discount.hpp`, `src/gallery/gallery_calibration.hpp`, `src/main.cpp`, `src/nodes/face_tracker_node.hpp`, `src/nodes/identity_matcher_node.hpp` | **Always the calibrated probability, never a raw cosine** — exception… |
|
| AR-024 | **Done** — associat… | T1, static | SR-002 | covered | `src/config.hpp`, `src/evidence_discount.hpp`, `src/gallery/gallery_calibration.hpp`, `src/gallery/track_gallery.hpp`, `src/main.cpp`, `src/nodes/face_tracker_node.hpp`, `src/nodes/identity_matcher_node.hpp` | **Always the calibrated probability, never a raw cosine** — exception… |
|
||||||
| AR-025 | **Done** — log-odds… | T1 | SR-002 | covered | `src/evidence_discount.hpp`, `src/nodes/identity_matcher_node.hpp` | Per-track Bayesian accumulation in log-odds, with correlated-observat… |
|
| AR-025 | **Done** — log-odds… | T1 | SR-002 | covered | `src/evidence_discount.hpp`, `src/nodes/identity_matcher_node.hpp` | Per-track Bayesian accumulation in log-odds, with correlated-observat… |
|
||||||
| AR-026 | In Progress | T1, T4 | SR-001 | covered | `src/backends/gemm_backend.cpp` | All similarity computed as GEMM, including annex and deferred pass |
|
| AR-026 | In Progress | T1, T4 | SR-001 | covered | `src/backends/gemm_backend.cpp` | All similarity computed as GEMM, including annex and deferred pass |
|
||||||
| AR-027 | Planned | T4 | SR-001 | tagged, unexecuted | `src/backends/gemm_backend.cpp` | Throughput acceptable for **arbitrary** gallery size |
|
| AR-027 | Planned | T4 | SR-001 | tagged, unexecuted | `src/backends/gemm_backend.cpp` | Throughput acceptable for **arbitrary** gallery size |
|
||||||
| AR-028 | Planned | T2 | SR-002 | untagged | - | **Embedding input quality assessed and carried** — every face scored … |
|
| AR-028 | Planned | T2 | SR-002 | untagged | - | **Embedding input quality assessed and carried** — every face scored … |
|
||||||
| AR-029 | Planned | T1 | SR-002 | untagged | - | Sharpness measure on the **aligned crop** (scale-normalised, so it ca… |
|
| AR-029 | Planned | T1 | SR-002 | untagged | - | Sharpness measure on the **aligned crop** (scale-normalised, so it ca… |
|
||||||
| AR-030 | Planned | T1 | SR-002 | covered | `src/face_utils.hpp` | Visibility measure from the AR-001 5-point landmarks — extreme pose o… |
|
| AR-030 | **In Progress** — m… | T1 | SR-002 | covered | `src/face_utils.hpp`, `tests/test_face_utils.cpp` | Visibility measure from the AR-001 5-point landmarks — extreme pose o… |
|
||||||
| DP-001 | Done | T1, manual | PR-004 | covered | `src/main.cpp` | One analysis core; modes are front-ends and must not fork pipeline lo… |
|
| DP-001 | Done | T1, manual | PR-004 | covered | `src/main.cpp` | One analysis core; modes are front-ends and must not fork pipeline lo… |
|
||||||
| DP-002 | Done | T1, manual | PR-004 | covered | `src/main.cpp` | Batch CLI over one title |
|
| DP-002 | Done | T1, manual | PR-004 | covered | `src/main.cpp` | Batch CLI over one title |
|
||||||
| DP-003 | Planned | T1, manual | PR-004 | untagged | - | On-demand resident service with bounded, observable queue |
|
| DP-003 | Planned | T1, manual | PR-004 | untagged | - | On-demand resident service with bounded, observable queue |
|
||||||
@@ -161,7 +161,7 @@ _None._
|
|||||||
|
|
||||||
- [`src/config.hpp:44`](../src/config.hpp#L44) — `Unknown`
|
- [`src/config.hpp:44`](../src/config.hpp#L44) — `Unknown`
|
||||||
- [`src/nodes/face_detector_node.hpp:47`](../src/nodes/face_detector_node.hpp#L47) — `private:`
|
- [`src/nodes/face_detector_node.hpp:47`](../src/nodes/face_detector_node.hpp#L47) — `private:`
|
||||||
- [`src/nodes/identity_matcher_node.hpp:149`](../src/nodes/identity_matcher_node.hpp#L149) — `std::vector<float> host_query(static_cast<size_t>(kMaxFaces) * 512);`
|
- [`src/nodes/identity_matcher_node.hpp:155`](../src/nodes/identity_matcher_node.hpp#L155) — `std::vector<float> host_query(static_cast<size_t>(kMaxFaces) * 512);`
|
||||||
|
|
||||||
### AR-004
|
### AR-004
|
||||||
|
|
||||||
@@ -169,14 +169,15 @@ _None._
|
|||||||
|
|
||||||
- [`src/main.cpp:86`](../src/main.cpp#L86) — `static constexpr std::size_t kSceneJoinDepth = 256;`
|
- [`src/main.cpp:86`](../src/main.cpp#L86) — `static constexpr std::size_t kSceneJoinDepth = 256;`
|
||||||
- [`src/main.cpp:299`](../src/main.cpp#L299) — `Unknown`
|
- [`src/main.cpp:299`](../src/main.cpp#L299) — `Unknown`
|
||||||
- [`src/nodes/identity_matcher_node.hpp:149`](../src/nodes/identity_matcher_node.hpp#L149) — `std::vector<float> host_query(static_cast<size_t>(kMaxFaces) * 512);`
|
- [`src/nodes/identity_matcher_node.hpp:155`](../src/nodes/identity_matcher_node.hpp#L155) — `std::vector<float> host_query(static_cast<size_t>(kMaxFaces) * 512);`
|
||||||
- [`tests/test_replay_fixtures.cpp:3`](../tests/test_replay_fixtures.cpp#L3) — `Unknown`
|
- [`tests/test_replay_fixtures.cpp:3`](../tests/test_replay_fixtures.cpp#L3) — `Unknown`
|
||||||
|
|
||||||
### AR-005
|
### AR-005
|
||||||
|
|
||||||
**Locations:** 1
|
**Locations:** 2
|
||||||
|
|
||||||
- [`src/face_utils.hpp:2`](../src/face_utils.hpp#L2) — `Unknown`
|
- [`src/face_utils.hpp:2`](../src/face_utils.hpp#L2) — `Unknown`
|
||||||
|
- [`tests/test_face_utils.cpp:1`](../tests/test_face_utils.cpp#L1) — `Unknown`
|
||||||
|
|
||||||
### AR-007
|
### AR-007
|
||||||
|
|
||||||
@@ -214,8 +215,8 @@ _None._
|
|||||||
|
|
||||||
- [`src/main.cpp:213`](../src/main.cpp#L213) — `reg_cfg, EvidenceDiscounter(same_person));`
|
- [`src/main.cpp:213`](../src/main.cpp#L213) — `reg_cfg, EvidenceDiscounter(same_person));`
|
||||||
- [`src/main.cpp:230`](../src/main.cpp#L230) — `reg_cfg, EvidenceDiscounter(same_person));`
|
- [`src/main.cpp:230`](../src/main.cpp#L230) — `reg_cfg, EvidenceDiscounter(same_person));`
|
||||||
- [`src/nodes/identity_matcher_node.hpp:119`](../src/nodes/identity_matcher_node.hpp#L119) — `const GalleryCalibration& calibration() const { return cal_; }`
|
- [`src/nodes/identity_matcher_node.hpp:125`](../src/nodes/identity_matcher_node.hpp#L125) — `const GalleryCalibration& calibration() const { return cal_; }`
|
||||||
- [`src/nodes/identity_matcher_node.hpp:255`](../src/nodes/identity_matcher_node.hpp#L255) — `Unknown`
|
- [`src/nodes/identity_matcher_node.hpp:261`](../src/nodes/identity_matcher_node.hpp#L261) — `Unknown`
|
||||||
- [`src/nodes/result_sink_node.hpp:49`](../src/nodes/result_sink_node.hpp#L49) — `static constexpr std::string_view label() { return "result_sink"; }`
|
- [`src/nodes/result_sink_node.hpp:49`](../src/nodes/result_sink_node.hpp#L49) — `static constexpr std::string_view label() { return "result_sink"; }`
|
||||||
- [`src/nodes/result_sink_node.hpp:161`](../src/nodes/result_sink_node.hpp#L161) — `struct ActorMeta { std::string name, imdb_id, tmdb_id, jellyfin_id; };`
|
- [`src/nodes/result_sink_node.hpp:161`](../src/nodes/result_sink_node.hpp#L161) — `struct ActorMeta { std::string name, imdb_id, tmdb_id, jellyfin_id; };`
|
||||||
- [`src/track_registry.hpp:2`](../src/track_registry.hpp#L2) — `Unknown`
|
- [`src/track_registry.hpp:2`](../src/track_registry.hpp#L2) — `Unknown`
|
||||||
@@ -261,32 +262,44 @@ _None._
|
|||||||
- [`src/track_registry.hpp:2`](../src/track_registry.hpp#L2) — `Unknown`
|
- [`src/track_registry.hpp:2`](../src/track_registry.hpp#L2) — `Unknown`
|
||||||
- [`tests/test_track_registry.cpp:3`](../tests/test_track_registry.cpp#L3) — `Unknown`
|
- [`tests/test_track_registry.cpp:3`](../tests/test_track_registry.cpp#L3) — `Unknown`
|
||||||
|
|
||||||
|
### AR-018
|
||||||
|
|
||||||
|
**Locations:** 3
|
||||||
|
|
||||||
|
- [`src/config.hpp:152`](../src/config.hpp#L152) — `Unknown`
|
||||||
|
- [`src/gallery/track_gallery.hpp:150`](../src/gallery/track_gallery.hpp#L150) — `struct TrackState`
|
||||||
|
- [`src/nodes/identity_matcher_node.hpp:110`](../src/nodes/identity_matcher_node.hpp#L110) — `std::vector<float> host_gallery(static_cast<size_t>(n_gallery_) * 512);`
|
||||||
|
|
||||||
### AR-023
|
### AR-023
|
||||||
|
|
||||||
**Locations:** 3
|
**Locations:** 3
|
||||||
|
|
||||||
- [`src/gallery/gallery_calibration.hpp:2`](../src/gallery/gallery_calibration.hpp#L2) — `Unknown`
|
- [`src/gallery/gallery_calibration.hpp:2`](../src/gallery/gallery_calibration.hpp#L2) — `Unknown`
|
||||||
- [`src/gallery/gallery_calibration.hpp:53`](../src/gallery/gallery_calibration.hpp#L53) — `float boundary_at(float p = 0.5f, float log_prior_odds = 0.f) const`
|
- [`src/gallery/gallery_calibration.hpp:53`](../src/gallery/gallery_calibration.hpp#L53) — `float boundary_at(float p = 0.5f, float log_prior_odds = 0.f) const`
|
||||||
- [`src/nodes/identity_matcher_node.hpp:111`](../src/nodes/identity_matcher_node.hpp#L111) — `const GalleryCalibration& calibration() const { return cal_; }`
|
- [`src/nodes/identity_matcher_node.hpp:117`](../src/nodes/identity_matcher_node.hpp#L117) — `const GalleryCalibration& calibration() const { return cal_; }`
|
||||||
|
|
||||||
### AR-024
|
### AR-024
|
||||||
|
|
||||||
**Locations:** 6
|
**Locations:** 10
|
||||||
|
|
||||||
- [`src/config.hpp:108`](../src/config.hpp#L108) — `Unknown`
|
- [`src/config.hpp:108`](../src/config.hpp#L108) — `Unknown`
|
||||||
|
- [`src/config.hpp:152`](../src/config.hpp#L152) — `Unknown`
|
||||||
- [`src/evidence_discount.hpp:2`](../src/evidence_discount.hpp#L2) — `Unknown`
|
- [`src/evidence_discount.hpp:2`](../src/evidence_discount.hpp#L2) — `Unknown`
|
||||||
- [`src/gallery/gallery_calibration.hpp:53`](../src/gallery/gallery_calibration.hpp#L53) — `float boundary_at(float p = 0.5f, float log_prior_odds = 0.f) const`
|
- [`src/gallery/gallery_calibration.hpp:53`](../src/gallery/gallery_calibration.hpp#L53) — `float boundary_at(float p = 0.5f, float log_prior_odds = 0.f) const`
|
||||||
|
- [`src/gallery/track_gallery.hpp:122`](../src/gallery/track_gallery.hpp#L122) — `void forget(int track_id) { tracks_.erase(track_id); }`
|
||||||
|
- [`src/gallery/track_gallery.hpp:150`](../src/gallery/track_gallery.hpp#L150) — `struct TrackState`
|
||||||
- [`src/main.cpp:213`](../src/main.cpp#L213) — `reg_cfg, EvidenceDiscounter(same_person));`
|
- [`src/main.cpp:213`](../src/main.cpp#L213) — `reg_cfg, EvidenceDiscounter(same_person));`
|
||||||
- [`src/nodes/face_tracker_node.hpp:2`](../src/nodes/face_tracker_node.hpp#L2) — `Unknown`
|
- [`src/nodes/face_tracker_node.hpp:2`](../src/nodes/face_tracker_node.hpp#L2) — `Unknown`
|
||||||
- [`src/nodes/identity_matcher_node.hpp:111`](../src/nodes/identity_matcher_node.hpp#L111) — `const GalleryCalibration& calibration() const { return cal_; }`
|
- [`src/nodes/identity_matcher_node.hpp:110`](../src/nodes/identity_matcher_node.hpp#L110) — `std::vector<float> host_gallery(static_cast<size_t>(n_gallery_) * 512);`
|
||||||
|
- [`src/nodes/identity_matcher_node.hpp:117`](../src/nodes/identity_matcher_node.hpp#L117) — `const GalleryCalibration& calibration() const { return cal_; }`
|
||||||
|
|
||||||
### AR-025
|
### AR-025
|
||||||
|
|
||||||
**Locations:** 3
|
**Locations:** 3
|
||||||
|
|
||||||
- [`src/evidence_discount.hpp:2`](../src/evidence_discount.hpp#L2) — `Unknown`
|
- [`src/evidence_discount.hpp:2`](../src/evidence_discount.hpp#L2) — `Unknown`
|
||||||
- [`src/nodes/identity_matcher_node.hpp:119`](../src/nodes/identity_matcher_node.hpp#L119) — `const GalleryCalibration& calibration() const { return cal_; }`
|
- [`src/nodes/identity_matcher_node.hpp:125`](../src/nodes/identity_matcher_node.hpp#L125) — `const GalleryCalibration& calibration() const { return cal_; }`
|
||||||
- [`src/nodes/identity_matcher_node.hpp:255`](../src/nodes/identity_matcher_node.hpp#L255) — `Unknown`
|
- [`src/nodes/identity_matcher_node.hpp:261`](../src/nodes/identity_matcher_node.hpp#L261) — `Unknown`
|
||||||
|
|
||||||
### AR-026
|
### AR-026
|
||||||
|
|
||||||
@@ -302,9 +315,10 @@ _None._
|
|||||||
|
|
||||||
### AR-030
|
### AR-030
|
||||||
|
|
||||||
**Locations:** 1
|
**Locations:** 2
|
||||||
|
|
||||||
- [`src/face_utils.hpp:2`](../src/face_utils.hpp#L2) — `Unknown`
|
- [`src/face_utils.hpp:2`](../src/face_utils.hpp#L2) — `Unknown`
|
||||||
|
- [`tests/test_face_utils.cpp:1`](../tests/test_face_utils.cpp#L1) — `Unknown`
|
||||||
|
|
||||||
### DP-001
|
### DP-001
|
||||||
|
|
||||||
@@ -537,7 +551,7 @@ _None._
|
|||||||
|
|
||||||
### SR-002
|
### SR-002
|
||||||
|
|
||||||
**Locations:** 29
|
**Locations:** 30
|
||||||
|
|
||||||
- [`src/config.hpp:44`](../src/config.hpp#L44) — `Unknown`
|
- [`src/config.hpp:44`](../src/config.hpp#L44) — `Unknown`
|
||||||
- [`src/config.hpp:108`](../src/config.hpp#L108) — `Unknown`
|
- [`src/config.hpp:108`](../src/config.hpp#L108) — `Unknown`
|
||||||
@@ -555,10 +569,10 @@ _None._
|
|||||||
- [`src/nodes/face_detector_node.hpp:2`](../src/nodes/face_detector_node.hpp#L2) — `Unknown`
|
- [`src/nodes/face_detector_node.hpp:2`](../src/nodes/face_detector_node.hpp#L2) — `Unknown`
|
||||||
- [`src/nodes/face_detector_node.hpp:47`](../src/nodes/face_detector_node.hpp#L47) — `private:`
|
- [`src/nodes/face_detector_node.hpp:47`](../src/nodes/face_detector_node.hpp#L47) — `private:`
|
||||||
- [`src/nodes/face_tracker_node.hpp:2`](../src/nodes/face_tracker_node.hpp#L2) — `Unknown`
|
- [`src/nodes/face_tracker_node.hpp:2`](../src/nodes/face_tracker_node.hpp#L2) — `Unknown`
|
||||||
- [`src/nodes/identity_matcher_node.hpp:111`](../src/nodes/identity_matcher_node.hpp#L111) — `const GalleryCalibration& calibration() const { return cal_; }`
|
- [`src/nodes/identity_matcher_node.hpp:117`](../src/nodes/identity_matcher_node.hpp#L117) — `const GalleryCalibration& calibration() const { return cal_; }`
|
||||||
- [`src/nodes/identity_matcher_node.hpp:119`](../src/nodes/identity_matcher_node.hpp#L119) — `const GalleryCalibration& calibration() const { return cal_; }`
|
- [`src/nodes/identity_matcher_node.hpp:125`](../src/nodes/identity_matcher_node.hpp#L125) — `const GalleryCalibration& calibration() const { return cal_; }`
|
||||||
- [`src/nodes/identity_matcher_node.hpp:149`](../src/nodes/identity_matcher_node.hpp#L149) — `std::vector<float> host_query(static_cast<size_t>(kMaxFaces) * 512);`
|
- [`src/nodes/identity_matcher_node.hpp:155`](../src/nodes/identity_matcher_node.hpp#L155) — `std::vector<float> host_query(static_cast<size_t>(kMaxFaces) * 512);`
|
||||||
- [`src/nodes/identity_matcher_node.hpp:255`](../src/nodes/identity_matcher_node.hpp#L255) — `Unknown`
|
- [`src/nodes/identity_matcher_node.hpp:261`](../src/nodes/identity_matcher_node.hpp#L261) — `Unknown`
|
||||||
- [`src/nodes/result_sink_node.hpp:49`](../src/nodes/result_sink_node.hpp#L49) — `static constexpr std::string_view label() { return "result_sink"; }`
|
- [`src/nodes/result_sink_node.hpp:49`](../src/nodes/result_sink_node.hpp#L49) — `static constexpr std::string_view label() { return "result_sink"; }`
|
||||||
- [`src/nodes/result_sink_node.hpp:63`](../src/nodes/result_sink_node.hpp#L63) — `void set_pre_write_hook(std::function<void(double)> fn) { pre_write_ = std::move(fn); }`
|
- [`src/nodes/result_sink_node.hpp:63`](../src/nodes/result_sink_node.hpp#L63) — `void set_pre_write_hook(std::function<void(double)> fn) { pre_write_ = std::move(fn); }`
|
||||||
- [`src/nodes/result_sink_node.hpp:161`](../src/nodes/result_sink_node.hpp#L161) — `struct ActorMeta { std::string name, imdb_id, tmdb_id, jellyfin_id; };`
|
- [`src/nodes/result_sink_node.hpp:161`](../src/nodes/result_sink_node.hpp#L161) — `struct ActorMeta { std::string name, imdb_id, tmdb_id, jellyfin_id; };`
|
||||||
@@ -568,6 +582,7 @@ _None._
|
|||||||
- [`src/nodes/scene_detector_node.hpp:144`](../src/nodes/scene_detector_node.hpp#L144) — `void write_output()`
|
- [`src/nodes/scene_detector_node.hpp:144`](../src/nodes/scene_detector_node.hpp#L144) — `void write_output()`
|
||||||
- [`src/scene_boundaries.hpp:2`](../src/scene_boundaries.hpp#L2) — `Unknown`
|
- [`src/scene_boundaries.hpp:2`](../src/scene_boundaries.hpp#L2) — `Unknown`
|
||||||
- [`src/track_registry.hpp:2`](../src/track_registry.hpp#L2) — `Unknown`
|
- [`src/track_registry.hpp:2`](../src/track_registry.hpp#L2) — `Unknown`
|
||||||
|
- [`tests/test_face_utils.cpp:1`](../tests/test_face_utils.cpp#L1) — `Unknown`
|
||||||
|
|
||||||
### SR-003
|
### SR-003
|
||||||
|
|
||||||
@@ -582,8 +597,12 @@ _None._
|
|||||||
|
|
||||||
### SR-005
|
### SR-005
|
||||||
|
|
||||||
**Locations:** 1
|
**Locations:** 5
|
||||||
|
|
||||||
|
- [`src/config.hpp:152`](../src/config.hpp#L152) — `Unknown`
|
||||||
|
- [`src/gallery/track_gallery.hpp:122`](../src/gallery/track_gallery.hpp#L122) — `void forget(int track_id) { tracks_.erase(track_id); }`
|
||||||
|
- [`src/gallery/track_gallery.hpp:150`](../src/gallery/track_gallery.hpp#L150) — `struct TrackState`
|
||||||
|
- [`src/nodes/identity_matcher_node.hpp:110`](../src/nodes/identity_matcher_node.hpp#L110) — `std::vector<float> host_gallery(static_cast<size_t>(n_gallery_) * 512);`
|
||||||
- [`scripts/make_jellyfin_gallery.py:4`](../scripts/make_jellyfin_gallery.py#L4) — `Unknown`
|
- [`scripts/make_jellyfin_gallery.py:4`](../scripts/make_jellyfin_gallery.py#L4) — `Unknown`
|
||||||
|
|
||||||
### UT-001
|
### UT-001
|
||||||
|
|||||||
@@ -119,6 +119,16 @@ struct TrackGallery {
|
|||||||
// matcher when it observes a cut or track disappearance.
|
// matcher when it observes a cut or track disappearance.
|
||||||
void forget(int track_id) { tracks_.erase(track_id); }
|
void forget(int track_id) { tracks_.erase(track_id); }
|
||||||
|
|
||||||
|
/// TRACES: AR-019 | SR-005
|
||||||
|
/// The registry's verdict on who this track is. Authoritative: it comes from
|
||||||
|
/// the Bayesian accumulation (AR-025), where the local tally counted raw
|
||||||
|
/// accepted frames and so weighted thirty near-identical looks the same as
|
||||||
|
/// thirty distinct ones.
|
||||||
|
void set_owner(int track_id, int actor_idx) {
|
||||||
|
if (track_id < 0 || actor_idx < 0) return;
|
||||||
|
tracks_[track_id].registry_owner = actor_idx;
|
||||||
|
}
|
||||||
|
|
||||||
/// TRACES: AR-024 | SR-005
|
/// TRACES: AR-024 | SR-005
|
||||||
/// Supply the calibration belonging to the active embedder. Without it the
|
/// Supply the calibration belonging to the active embedder. Without it the
|
||||||
/// band falls back to treating cosine as probability, which is wrong but
|
/// band falls back to treating cosine as probability, which is wrong but
|
||||||
@@ -145,6 +155,7 @@ private:
|
|||||||
std::map<int, int> actor_votes; // actor_idx → accepted-frame count
|
std::map<int, int> actor_votes; // actor_idx → accepted-frame count
|
||||||
int accepted_frames{0};
|
int accepted_frames{0};
|
||||||
bool promoted{false};
|
bool promoted{false};
|
||||||
|
int registry_owner{-1}; ///< AR-019: authoritative
|
||||||
};
|
};
|
||||||
|
|
||||||
/// TRACES: AR-018, AR-024 | SR-005
|
/// TRACES: AR-018, AR-024 | SR-005
|
||||||
@@ -210,7 +221,7 @@ private:
|
|||||||
void promote(int track_id, TrackState& ts) {
|
void promote(int track_id, TrackState& ts) {
|
||||||
ts.promoted = true; // idempotent: never promote a track twice
|
ts.promoted = true; // idempotent: never promote a track twice
|
||||||
|
|
||||||
int actor = plurality_actor(ts);
|
int actor = owning_actor(ts);
|
||||||
if (actor < 0) return;
|
if (actor < 0) return;
|
||||||
|
|
||||||
// ── Safety gate: internal spread ─────────────────────────────────────
|
// ── Safety gate: internal spread ─────────────────────────────────────
|
||||||
@@ -246,6 +257,13 @@ private:
|
|||||||
<< annex_.size() << "\n";
|
<< annex_.size() << "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Prefer the registry's verdict; fall back to the local tally only when no
|
||||||
|
/// registry is attached (unit tests, replay harness).
|
||||||
|
static int owning_actor(const TrackState& ts) {
|
||||||
|
if (ts.registry_owner >= 0) return ts.registry_owner;
|
||||||
|
return plurality_actor(ts);
|
||||||
|
}
|
||||||
|
|
||||||
static int plurality_actor(const TrackState& ts) {
|
static int plurality_actor(const TrackState& ts) {
|
||||||
int best = -1, best_votes = 0;
|
int best = -1, best_votes = 0;
|
||||||
for (const auto& [ai, v] : ts.actor_votes) {
|
for (const auto& [ai, v] : ts.actor_votes) {
|
||||||
|
|||||||
@@ -144,7 +144,18 @@ struct IdentityMatcherFunc {
|
|||||||
// mix embeddings from two viewpoints under one buffer, so we still drop
|
// mix embeddings from two viewpoints under one buffer, so we still drop
|
||||||
// every diversity buffer here — a revived track simply re-accumulates its
|
// every diversity buffer here — a revived track simply re-accumulates its
|
||||||
// buffer from post-cut frames. Stale cross-cut embeddings are never promoted.
|
// buffer from post-cut frames. Stale cross-cut embeddings are never promoted.
|
||||||
if (tf.source.is_cut) track_gallery_.clear_tracks();
|
/// TRACES: AR-019 | SR-005
|
||||||
|
// Promotion may only borrow same-identity evidence from a span where
|
||||||
|
// identity is certain, so ALL THREE discontinuity signals clear the
|
||||||
|
// buffers, not just the histogram cut:
|
||||||
|
// is_cut — camera-angle change
|
||||||
|
// is_scene_boundary — different scene (AR-010; previously never set,
|
||||||
|
// so this half of the gate was dead)
|
||||||
|
// The third, an identity contradiction (AR-015), is enforced by the
|
||||||
|
// registry: a track whose belief swapped is closed outright, so it can
|
||||||
|
// no longer promote anything.
|
||||||
|
if (tf.source.is_cut || tf.source.is_scene_boundary)
|
||||||
|
track_gallery_.clear_tracks();
|
||||||
|
|
||||||
const int n_faces = static_cast<int>(tf.embeddings.size());
|
const int n_faces = static_cast<int>(tf.embeddings.size());
|
||||||
std::vector<IdentifiedActor> actors;
|
std::vector<IdentifiedActor> actors;
|
||||||
@@ -271,6 +282,16 @@ struct IdentityMatcherFunc {
|
|||||||
registry_->observe(tf.track_ids[fi], best_actor, p, tf.embeddings[fi]);
|
registry_->observe(tf.track_ids[fi], best_actor, p, tf.embeddings[fi]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TRACES: AR-019 | SR-005
|
||||||
|
// Ownership is the registry's, computed once. TrackGallery used to
|
||||||
|
// tally its own plurality vote over accepted frames, which meant two
|
||||||
|
// different answers to "who is this track" could coexist — and the
|
||||||
|
// expansion one ignored the Bayesian accumulation entirely.
|
||||||
|
if (registry_ && tf.track_ids[fi] >= 0) {
|
||||||
|
if (auto owner = registry_->owner(tf.track_ids[fi]))
|
||||||
|
track_gallery_.set_owner(tf.track_ids[fi], *owner);
|
||||||
|
}
|
||||||
|
|
||||||
track_gallery_.observe(tf.track_ids[fi], tf.embeddings[fi],
|
track_gallery_.observe(tf.track_ids[fi], tf.embeddings[fi],
|
||||||
best_actor, best_s, accept, tf.crops[fi]);
|
best_actor, best_s, accept, tf.crops[fi]);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user