# Embedding-dump HDF5 schema (v1) One file per analysed title. Captures the pipeline state at the `EmbeddedSceneFrame` channel — i.e. after decode → detect → align → embed, but **before** tracking and identity matching. Everything downstream (face tracker, identity matcher, scene tracker/anneal) is cheap CPU math, so replaying from this file lets a parameter sweep re-run the whole downstream tail thousands of times with no GPU and no video. Written by the C++ dump sink (`--dump-embeddings out.h5`); read by `scripts/optimizer/replay.py`. ## Layout The dump is **flat/ragged**: all faces across all frames are concatenated into per-face arrays, with a per-frame index table pointing into them. This avoids variable-length HDF5 types and reads straight into numpy. ``` / (root) attrs: schema_version : int = 1 movie : str (source video path) sample_fps : float embed_dim : int = 512 embedder_model : str basename of the embedding model (GR-004) embedder_sha256: str SHA-256 of that model file (GR-004) frames/ group — one row per sampled frame timestamp_sec : float64 [F] frame_idx : int64 [F] is_cut : uint8 [F] (histogram intra-scene cut) is_scene_boundary : uint8 [F] (TransNetV2 boundary; 0 if scene_detect off) face_offset : int64 [F] start index into faces/* for this frame face_count : int32 [F] number of faces in this frame faces/ group — one row per detected face, concatenated embedding : float32 [N, 512] L2-normalised ArcFace embedding bbox : float32 [N, 4] x, y, w, h in original video pixels landmarks : float32 [N, 10] 5 (x,y) pairs, SCRFD/ArcFace order confidence : float32 [N] detector confidence ``` `F` = number of sampled frames, `N` = total faces (= sum of face_count). Frame *i*'s faces are `faces/*[ face_offset[i] : face_offset[i]+face_count[i] ]`. ## Model binding (GR-004) `embedder_model` / `embedder_sha256` record which embedder produced every vector in `faces/embedding`. A replay has no live embedder, so **the dump is the embedder as far as the gallery is concerned**: `replay.py` checks these two attributes against the gallery's own `/embedder` stamp and refuses to run on a mismatch, naming both sides. Cross-model cosines are meaningless but look plausible. The attributes are additive, not a format break — `schema_version` stays 1. Dumps written before GR-004 simply lack them, which reports as *unverifiable* (a loud warning, or a hard error under `SAE_REQUIRE_GALLERY_STAMP=1`) rather than as a pass. Re-dump to bind an old dump; there is no in-place migration, because unlike a gallery nobody can assert after the fact which model produced a vector. ## Invariants - `embedding` rows are unit-norm (cosine == dot product against the gallery). - `face_offset[0] == 0`; `face_offset[i+1] == face_offset[i] + face_count[i]`. - `bbox` is already mapped to original resolution (bbox_upscale applied at dump time), matching what the identity matcher would emit. - A frame with no faces has `face_count == 0` (still gets a row, so timestamps stay dense). - EOF sentinel frames are NOT written.