// Replay tests — the real tracker and registry driven from committed fixtures. // // TRACES: AR-004, AR-012, AR-013 | VR-001, VR-002 | IT-001 // // Tier T2: composition, not units. The registry tests construct awkward states // directly; these check that the pieces behave when wired together and fed real // footage — 480x360 public-domain clips at 5 fps, with the cuts, gaps and // crowded frames that actual film produces and synthetic input does not. // // No GPU and no model: the fixtures are HDF5 dumps taken after embedding, so // everything here is CPU maths. That is what lets this run on the CI host at // all (see docs/requirements.md, "CI never calls a model"). // // Driving the node functors directly rather than through a KPN network is // deliberate: functors are plain objects, so there are no threads, no channels // and no scheduling — the same input gives the same output every time, which is // exactly what a fixture-based test needs. #include #include "config.hpp" #include "evidence_discount.hpp" #include "nodes/face_tracker_node.hpp" #include "track_registry.hpp" #include "types.hpp" #include #include #include #include #include #include namespace { // ── Fixture reader ─────────────────────────────────────────────────────────── // The flat/ragged layout of scripts/optimizer/SCHEMA.md: per-face arrays // concatenated, with a per-frame index table pointing into them. struct Dump { std::vector ts; std::vector is_cut; std::vector face_offset; std::vector face_count; std::vector emb; std::vector bbox; // 4 per face std::string embedder; std::size_t frames() const { return ts.size(); } std::size_t faces() const { return emb.size(); } }; template std::vector read1d(H5::Group& g, const char* name, const H5::DataType& dt) { H5::DataSet ds = g.openDataSet(name); hsize_t n = 0; ds.getSpace().getSimpleExtentDims(&n, nullptr); std::vector out(n); if (n) ds.read(out.data(), dt); return out; } Dump load(const std::string& path) { H5::H5File f(path, H5F_ACC_RDONLY); H5::Group frames = f.openGroup("frames"); H5::Group faces = f.openGroup("faces"); Dump d; d.ts = read1d(frames, "timestamp_sec", H5::PredType::NATIVE_DOUBLE); d.is_cut = read1d(frames, "is_cut", H5::PredType::NATIVE_UINT8); d.face_offset = read1d(frames, "face_offset", H5::PredType::NATIVE_INT64); d.face_count = read1d(frames, "face_count", H5::PredType::NATIVE_INT32); H5::DataSet e = faces.openDataSet("embedding"); hsize_t dims[2]{0, 0}; e.getSpace().getSimpleExtentDims(dims, nullptr); std::vector flat(dims[0] * dims[1]); if (!flat.empty()) e.read(flat.data(), H5::PredType::NATIVE_FLOAT); d.emb.resize(dims[0]); for (hsize_t i = 0; i < dims[0]; ++i) std::copy_n(flat.begin() + i * dims[1], 512, d.emb[i].begin()); // bbox is 2-D [N,4]; reading it with the 1-D helper would size the buffer // from the first extent only and then read four times that many floats. { H5::DataSet bs = faces.openDataSet("bbox"); hsize_t bd[2]{0, 0}; bs.getSpace().getSimpleExtentDims(bd, nullptr); d.bbox.resize(bd[0] * bd[1]); if (!d.bbox.empty()) bs.read(d.bbox.data(), H5::PredType::NATIVE_FLOAT); } // GR-004: the dump records which embedder produced it, so a replay cannot // be silently scored against a gallery from a different model. if (f.attrExists("embedder_model")) { // Written as a variable-length string (embedding_dump_node.hpp:99), so // the read must name the same type explicitly. H5::StrType vlen(H5::PredType::C_S1, H5T_VARIABLE); f.openAttribute("embedder_model").read(vlen, d.embedder); } return d; } std::string fixture(const char* name) { return std::string(SAE_TEST_FIXTURES_DIR) + "/dumps/" + name; } // ── Harness ────────────────────────────────────────────────────────────────── struct Replay { std::vector claims; std::vector track_ids; // per face, in fixture order std::size_t faces_seen{0}; }; Replay run(const Dump& d, double extinction = 10.0) { Replay r; TrackRegistry::Config rc; rc.extinction_sec = extinction; auto cal = [](float cos) { return std::max(0.f, cos); }; auto reg = std::make_shared(rc, EvidenceDiscounter(cal)); reg->on_track_dead([&r](const DeadTrack& t) { r.claims.push_back(t); }); Config cfg; cfg.track_assoc_min_prob = 0.5f; FaceTrackerFunc ft(cfg, reg, cal); for (std::size_t i = 0; i < d.frames(); ++i) { EmbeddedSceneFrame ef; ef.source.timestamp_sec = d.ts[i]; ef.source.is_cut = d.is_cut[i] != 0; const int64_t off = d.face_offset[i]; const int32_t n = d.face_count[i]; for (int32_t k = 0; k < n; ++k) { DetectedFace face; const float* b = &d.bbox[(off + k) * 4]; face.bbox = cv::Rect2f(b[0], b[1], b[2], b[3]); face.confidence = 1.0f; ef.faces.push_back(face); ef.crops.push_back(cv::Mat()); ef.embeddings.push_back(d.emb[off + k]); } r.faces_seen += static_cast(n); auto out = ft(std::move(ef)); for (int id : out.track_ids) r.track_ids.push_back(id); } reg->flush(d.ts.empty() ? 0.0 : d.ts.back()); return r; } } // namespace // ── AR-004 / VR-001 — the fixtures are intact and self-describing ──────────── TEST_CASE("superhero fixture is complete", "[replay][VR-001]") { Dump d = load(fixture("superhero.h5")); CHECK(d.frames() == 5128); CHECK(d.faces() == 4307); CHECK(d.embedder == "LVFace-B_Glint360K.onnx"); int64_t running = 0; for (std::size_t i = 0; i < d.frames(); ++i) { REQUIRE(d.face_offset[i] == running); running += d.face_count[i]; } CHECK(static_cast(running) == d.faces()); } TEST_CASE("replaying the superhero fixture twice gives identical tracks", "[replay][VR-002]") { Dump d = load(fixture("superhero.h5")); Replay a = run(d); Replay b = run(d); REQUIRE(a.track_ids.size() == b.track_ids.size()); CHECK(a.track_ids == b.track_ids); REQUIRE(a.claims.size() == b.claims.size()); } TEST_CASE("every face is assigned a track and every track closes", "[replay][AR-012]") { Dump d = load(fixture("superhero.h5")); Replay r = run(d); CHECK(r.track_ids.size() == r.faces_seen); for (int id : r.track_ids) CHECK(id >= 0); // nothing silently unassigned // flush() must leave nothing behind: a track still open at EOF would be a // window that never reaches the output. CHECK(r.claims.size() > 0); } TEST_CASE("windows are well-formed and inside the film", "[replay][AR-013]") { for (const char* f : {"superhero.h5", "superhero.h5", "superhero.h5", "superhero.h5", "superhero.h5"}) { INFO(f); Dump d = load(fixture(f)); Replay r = run(d); const double t0 = d.ts.front(), t1 = d.ts.back(); for (const auto& c : r.claims) { // A window ends at the last sighting, never after it — so it can // never extend past the footage that produced it. CHECK(c.first_seen <= c.last_seen); CHECK(c.first_seen >= t0); CHECK(c.last_seen <= t1); } } }