// Unit tests for TrackGallery (gallery/track_gallery.hpp): per-film gallery // expansion driven by track continuity. Pure, GPU-free, model-free — exercises // the diversity-buffer eviction policy, the novelty/spread safety gates, // plurality ownership, and idempotent promotion via the public interface. #include #include #include "config.hpp" #include "gallery/track_gallery.hpp" #include "types.hpp" #include #include namespace { // Unit-norm embedding pointing along one axis (cosine sim to another one-hot is // 0, to itself 1) — lets tests dial gallery similarity precisely. Embedding one_hot(int slot) { Embedding e{}; e[slot] = 1.0f; return e; } // Unit-norm embedding in the plane of axes i,j at angle t from i. Cosine sim to // one_hot(i) is cos(t) — used to place a view at a chosen gallery similarity. Embedding at_sim(int i, int j, float cos_t) { Embedding e{}; float s = std::sqrt(std::max(0.f, 1.f - cos_t * cos_t)); e[i] = cos_t; e[j] = s; return e; } Config expand_cfg() { Config cfg; cfg.expand_gallery = true; cfg.expand_buffer_size = 3; cfg.expand_novelty_sim = 0.55f; cfg.expand_track_spread_max = 0.60f; cfg.expand_min_anchor_frames = 3; return cfg; } const cv::Mat kNoCrop; // debug dumping off → crop unused } // namespace TEST_CASE("disabled: no annex growth when expand_gallery is off", "[track_gallery]") { // expand_gallery defaults to true (see config.hpp) as of the rep4 bake-off — // set it explicitly false here since this test exercises the disabled path, // not whatever the struct's current default happens to be. Config cfg; cfg.expand_gallery = false; TrackGallery tg(cfg); REQUIRE_FALSE(tg.enabled()); for (int f = 0; f < 10; ++f) tg.observe(1, one_hot(1), /*actor*/ 0, /*sim*/ 0.2f, /*accept*/ true, kNoCrop); CHECK(tg.annex().empty()); } TEST_CASE("confirmed track promotes gallery-far views", "[track_gallery]") { TrackGallery tg(expand_cfg()); REQUIRE(tg.enabled()); // A track owned by actor 0. Every frame is accepted as actor 0, but each // view is gallery-far (sim 0.30 < novelty 0.55) yet mutually self-similar // enough to pass the spread gate. for (int f = 0; f < 3; ++f) tg.observe(7, at_sim(0, 1, 0.30f + 0.001f * f), 0, 0.30f + 0.001f * f, true, kNoCrop); // 3 accepted frames == min_anchor_frames → confirmed and promoted. CHECK_FALSE(tg.annex().empty()); for (const auto& ae : tg.annex()) CHECK(ae.actor_idx == 0); } TEST_CASE("novelty gate skips views the gallery already covers", "[track_gallery]") { TrackGallery tg(expand_cfg()); // All views are recognised well (sim 0.90 ≥ novelty 0.55): nothing worth // promoting even though the track is confirmed. for (int f = 0; f < 3; ++f) tg.observe(2, one_hot(0), 0, 0.90f, true, kNoCrop); CHECK(tg.annex().empty()); } TEST_CASE("spread gate rejects a two-person track", "[track_gallery]") { TrackGallery tg(expand_cfg()); // Two orthogonal identities under one track ID: pairwise sim 0 → spread 1.0 // > spread_max 0.60. Whole track rejected, annex stays empty even though // frames are accepted and gallery-far. tg.observe(3, at_sim(0, 1, 0.30f), 0, 0.30f, true, kNoCrop); tg.observe(3, at_sim(0, 1, 0.30f), 0, 0.30f, true, kNoCrop); tg.observe(3, one_hot(400), 0, 0.30f, true, kNoCrop); // orthogonal outlier CHECK(tg.annex().empty()); } TEST_CASE("unconfirmed track (too few accepts) does not promote", "[track_gallery]") { TrackGallery tg(expand_cfg()); // Only 2 accepted frames < min_anchor_frames 3; extra non-accepted frames // fill the buffer but don't count toward ownership. tg.observe(4, at_sim(0, 1, 0.30f), 0, 0.30f, true, kNoCrop); tg.observe(4, at_sim(0, 1, 0.31f), 0, 0.31f, true, kNoCrop); tg.observe(4, at_sim(0, 1, 0.32f), 0, 0.32f, false, kNoCrop); CHECK(tg.annex().empty()); } TEST_CASE("plurality actor wins a mixed-vote track", "[track_gallery]") { Config cfg = expand_cfg(); cfg.expand_min_anchor_frames = 3; TrackGallery tg(cfg); // Actor 5 accepted twice, actor 6 once → plurality is 5. All views novel. tg.observe(8, at_sim(0, 1, 0.30f), 5, 0.30f, true, kNoCrop); tg.observe(8, at_sim(0, 1, 0.31f), 5, 0.31f, true, kNoCrop); tg.observe(8, at_sim(0, 1, 0.32f), 6, 0.32f, true, kNoCrop); REQUIRE_FALSE(tg.annex().empty()); for (const auto& ae : tg.annex()) CHECK(ae.actor_idx == 5); } TEST_CASE("promotion is idempotent across a long track", "[track_gallery]") { TrackGallery tg(expand_cfg()); for (int f = 0; f < 3; ++f) tg.observe(9, at_sim(0, 1, 0.30f + 0.001f * f), 0, 0.30f + 0.001f * f, true, kNoCrop); size_t after_confirm = tg.annex().size(); REQUIRE(after_confirm > 0); // Keep feeding the confirmed track: annex must not grow again. for (int f = 0; f < 10; ++f) tg.observe(9, at_sim(0, 1, 0.30f), 0, 0.30f, true, kNoCrop); CHECK(tg.annex().size() == after_confirm); } TEST_CASE("clear_tracks drops buffers before confirmation", "[track_gallery]") { TrackGallery tg(expand_cfg()); // Two accepts, then a cut clears buffers; the third accept starts fresh and // can't reach the anchor threshold on its own. tg.observe(1, at_sim(0, 1, 0.30f), 0, 0.30f, true, kNoCrop); tg.observe(1, at_sim(0, 1, 0.31f), 0, 0.31f, true, kNoCrop); tg.clear_tracks(); tg.observe(1, at_sim(0, 1, 0.32f), 0, 0.32f, true, kNoCrop); CHECK(tg.annex().empty()); }