refactor(config): give the tuned constants a real provenance, and make them reachable

Two problems, both of which made a number look more settled than it is.

The provenance was a dead link. config.hpp cited
docs/rep4-optimizer-results.md for prob_threshold, extinction_sec,
anneal_sec and the expansion default. That file was renamed to
model-bakeoff.md and then rewritten; the comments were never repointed,
so the most consequential constant in the pipeline appeared to have no
source at all.

Following it up produced something worse than a broken link.
prob_threshold=0.754 comes from the ORIGINAL rep4 document (still
readable at `git show d340da7:docs/rep4-optimizer-results.md`). The
rewrite that replaced it reports finding "a real scoring bug in
optimize.py: a candidate whose hardest film's replay timed out was
averaged over survivors instead of penalized, silently rewarding partial
coverage. Affected 3 of 16 training combos". So 0.754 was fitted under
scoring that was later found wrong, the corrected sweep converged
elsewhere, and no corrected prob_threshold is recorded anywhere. The
comment now says that, along with the surviving document's own verdict
that the optimum "generalizes unevenly -- strong on 3 of 5 held-out
films, badly broken on 2".

Four constants were unreachable. ownership_logodds lived on
TrackRegistry::Config, and max_views/admit_below/rho_max on
EvidenceDiscounter::Config, which main built with the one-argument
constructor -- so nothing short of a recompile could move any of them.
rho_max's own comment defers to "the sweep (VR-007)" for where it
belongs, and that sweep could not reach it.

They now live in Config with CLI flags and are exposed to the replay
harness. ownership_logodds is worth singling out: below it a track makes
no presence claim at all, so it decides whether an actor is reported
rather than how confidently -- arguably the most consequential constant
after prob_threshold, and until now unswept and unsettable.

No behaviour change: every default is the value that was compiled in.

TRACES: AR-025, AR-017 | SR-002
This commit is contained in:
2026-08-05 17:43:51 +02:00
parent 88c42573a5
commit 06373817a2
5 changed files with 106 additions and 12 deletions
+67 -8
View File
@@ -72,11 +72,38 @@ struct Config {
std::string arcface_engine; // optional path to a pre-built TRT engine; bypasses ORT
int embed_batch_size{4}; // max faces per ORT Run() call — bounds per-call latency
float match_prior{0.5f}; // base-rate prior; 0.5 = use calibrated sigmoid directly
// prob_threshold tuned by Differential Evolution against Amazon X-Ray per-scene
// presence over 4 films, per-second metric (see docs/rep4-optimizer-results.md).
// Best model+mode: LVFace-B_Glint360K, full gallery, expansion on. Supersedes the
// earlier 9-film scene-union-metric tuning (0.76) that metric is now known to
// have hidden out-of-cast false positives (see docs/optimizer-experiments.md).
// Tuned by Differential Evolution against Amazon X-Ray per-second presence
// over the 4-film rep4 matrix. Best model+mode: LVFace-B_Glint360K, full
// gallery, expansion on. Supersedes an earlier 9-film scene-union tuning
// (0.76); that metric hid out-of-cast false positives.
//
// **Read the provenance before trusting the value.** Two things about it:
//
// 1. The document it came from no longer exists under that name. It was
// docs/rep4-optimizer-results.md, renamed to docs/model-bakeoff.md and
// then rewritten (0bd2747). This comment pointed at the dead path for
// long enough that the number looked unsourced. The original is still
// readable at `git show d340da7:docs/rep4-optimizer-results.md`, where
// the shipped triple appears as
// `prob_threshold=0.754, anneal_sec=35.5`.
//
// 2. **0.754 predates a scoring bug fix and was never re-derived.** That
// same rewrite reports finding "a real scoring bug in optimize.py: a
// candidate whose hardest film's replay timed out was averaged over
// survivors instead of penalized, silently rewarding partial coverage.
// Affected 3 of 16 training combos". The corrected sweep converged
// somewhere else — the surviving document records anneal_sec=59.2,
// extinction_sec=59.2 against the 35.5/57.4 shipped alongside this
// threshold — and no corrected prob_threshold is recorded anywhere.
// (The other two constants are now withdrawn outright, which is why
// only this one still matters.)
//
// The doc is also candid that the optimum "generalizes unevenly — strong on
// 3 of 5 held-out films, badly broken on 2 (one with a 974-count misID
// blowup)", and that it is shipped anyway because it still beats the old
// defaults on average. That is a defensible call and not a settled,
// film-agnostic optimum; it should be visible here rather than only in a
// document this comment used to point at incorrectly.
float prob_threshold{0.754f}; // posterior P(match | sim, prior) threshold
// TRACES: AR-024 | SR-002
// match_threshold (0.45), match_ratio (0.80) and match_ratio_ceil (0.65) are
@@ -150,6 +177,35 @@ struct Config {
// ordinary miss, once for a cut). Seconds mean one thing at any sample rate.
double track_extinction_sec{5.0};
// ── Ownership and evidence accumulation (AR-025) ──────────────────────────
// TRACES: AR-025, AR-017 | SR-002
// These four decided how presence is claimed and were unreachable: they
// lived as in-class initialisers on TrackRegistry::Config and
// EvidenceDiscounter::Config, and main constructed the discounter with the
// one-argument constructor, so nothing short of a recompile could move
// them. rho_max's own comment defers to "the sweep (VR-007)" for where it
// belongs — a sweep that could not reach it.
//
// ownership_logodds is arguably the most consequential constant in the
// pipeline after prob_threshold: below it a track produces no presence
// claim at all, so it decides whether an actor is reported rather than how
// confidently. 2.0 is a posterior of ~0.88. Unswept.
float ownership_logodds{2.0f};
// How much a single observation may move a track's belief. n_eff =
// n / (1 + (n-1)·rho), so rho_max caps what a repeated view can ever be
// worth: 0.5 caps it at two independent observations however long the shot
// runs. It is deliberately below 1 — a held pose still yields a fresh
// detection, alignment and noise realisation, so a little independent
// evidence survives. Setting it to 1 freezes belief after the first frame,
// which is the bug this replaced.
float evidence_rho_max{0.5f};
// P(same view) below this and the observation counts as a genuinely new
// look, so it joins the per-track view set.
float evidence_admit_below{0.6f};
// Distinct views remembered per track, which bounds the novelty comparison.
int evidence_max_views{8};
// ── Scene tracking ────────────────────────────────────────────────────────
// TRACES: AR-012, AR-013 | SR-002
// extinction_sec (57.4) and anneal_sec (35.5) are GONE, along with
@@ -178,9 +234,12 @@ struct Config {
// new reference views; they are promoted into a per-film, in-memory annex so
// later frames/tracks of that actor at similar poses recognise. See
// gallery/track_gallery.hpp.
// Default ON: rep4 matrix (docs/rep4-optimizer-results.md) found expansion helps
// recall on the full (unrestricted) gallery for the winning model/mode — the
// opposite of the earlier assumption that it only helps restricted galleries.
// Default ON: the rep4 matrix (docs/model-bakeoff.md, "Two effects in
// isolation") found expansion helps recall on the full (unrestricted)
// gallery for the winning model/mode — the opposite of the earlier
// assumption that it only helps restricted galleries. The same section is
// explicit that on the full gallery it buys +2.1pp F1 and +3.9pp recall
// "at a real cost" in misIDs, where in restricted mode it is a clean win.
bool expand_gallery{true}; // master switch
int expand_buffer_size{20}; // per-track diversity buffer capacity
// TRACES: AR-018, AR-024 | SR-005