# Handover Notes Stand: 2026-05-03 (Beat-20-Reparatur abgeschlossen). ## Zustand - `pytest tests/ -q` → 52/52 grün. - `python cli.py match --beat 20 --vision` läuft erfolgreich durch und schreibt einen confirmed Match (Score 0.6632, scene 613, in=5284.706s, dur=0.88s). - Vorheriger Cache wurde nach `.cache/match_results.json.bak` gesichert. - Kein offener PR; lokale Änderungen sind committed (siehe letzter Commit). ## Was zuletzt geändert wurde und warum ### 1. `cli.py` — `realign_window` wählt das Action-Window pro Segment In `_filter_semantically_invalid_vision_matches.realign_window`: - **Vorher:** `find_action_window_in_scene(action_beat or check_beat, …)` — bei segmentierten Beats wurde immer der ganze Beat als semantischer Kontext benutzt. Das hat für Beat 20 die Source-Position auf die Kuss-Phase (5270 s) gelegt, obwohl das *sichtbare* Segment nur "approaching and pulling apart" zeigt — diese Phase liegt im Source erst um 5284 s. - **Jetzt:** Es werden zwei Fenster gesucht (Segment-Beschreibung *und* Beat- Beschreibung). Der Beat-Kontext gewinnt nur bei deutlichem (>0.06) Score- Vorsprung. Der Trailer-Offset-Shift (`visible_content_offset`) wird nur angewendet, wenn tatsächlich der Beat-Kontext benutzt wurde — sonst zeigt das Segment-Fenster bereits auf die richtige Phase. Effekt für Beat 20: 5270.118 → 5284.706, Score 0.6449 (provisional) → 0.6632 (confirmed). ### 2. `cli.py` — Filter-/Repair-Stufe ist crash-tolerant `_filter_semantically_invalid_vision_matches` hat den Per-Result-Body in eine lokale Funktion `_filter_repair_one` herausgezogen und in einen try/except verpackt. Wenn die Reparatur abbricht (z. B. weil Vision-API mitten in der Antwort wegfällt), wird der bisher gecachte Treffer behalten statt komplett verworfen. ### 3. `src/llm/vision_cache.py` — Vision-Retry für Lesefehler `_call_vision_model` fängt jetzt zusätzlich `TimeoutError`, `socket.timeout`, `ConnectionError` und `OSError` während des Antwort-Lesens und retryt mit demselben Backoff wie HTTP-/URL-Fehler. Die Auslöse-Bedingung war ein 24-h-DSL-Disconnect mitten im Lauf; davor wurde der Match-Lauf hart abgebrochen und der Cache stand auf "kein Match". ### 4. `README.md` Zwei kurze Absätze ergänzt, die (1) die Segment-vs-Beat-Window-Auswahl und (2) das neue Crash-/Netzfehler-Verhalten beschreiben. ## Nicht angefasst, aber relevant für die Übergabe - Der **vollständige FFmpeg-Vollscan** liefert für Beat 20 weiterhin keinen bestätigten Treffer (final score 0.419 < provisional 0.430). Den Confirmed-Match liefert die Action-Window-Reparatur. Das ist erwartet: das sichtbare Segment ist visuell sehr generisch (Two-Shot Profil mit unscharfem Hintergrund), die korrekte Phase fällt erst durch die semantische Aktionsbeschreibung auf. - Die `candidate_points`-Schleife in `realign_window` (lines ~700–765) sucht nur ±~2 s um `start_s` herum. Solange `start_s` jetzt aus dem Segment- Fenster kommt, liegt der korrekte Source-Punkt in diesem Bereich. Wenn künftig Beats mit längeren visiblen Inseln auftauchen, kann diese Range zu eng werden — dann den Suchradius erweitern statt das Window-Picking rückgängig machen. - Es gibt **keine Tests** für `_filter_semantically_invalid_vision_matches` oder `realign_window`. Wer das anfasst, sollte Beat 20 als Live-Smoke-Test benutzen (siehe unten). ## Reproduktion / Smoke-Test ```powershell .\.venv\Scripts\Activate.ps1 python cli.py match --beat 20 --vision ``` Erwartet: `Beat 20: realigned semantically valid long scene by motion/action windows`, danach `is_confirmed: true` für Beat 20 in `.cache/match_results.json` mit `in_point_s ≈ 5284.7` und `match_score ≥ 0.65`. Wenn das fehlschlägt: 1. `python -m pytest tests/ -q` — falls rot, ist die Codebasis selbst kaputt. 2. `.cache/vision_descriptions.json` prüfen — die Schlüssel `beat:20:73.560:74.680:…` und `action_window:613:5282.390:5285.430:…` müssen existieren, sonst ruft Vision live ab (kostet Credits; braucht Netz). 3. `match_results.json.bak` zurückspielen, falls der Cache zerschossen ist. ## Aktuelle Coverage (vor neuestem Lauf) ``` total beats: 25 matched: 20 (5 confirmed, 15 provisional) unmatched: beats 0, 2, 21, 23, 24 ``` Beat 0 ist das SHO-Logo (kein Source-Match möglich, korrekt). Beats 22/23/24 haben keine sichtbaren Inseln (Endcredits/Title) — auch korrekt unmatched. Beat 2 und Beat 21 sind die echten Recovery-Kandidaten; die neue Recovery-Stufe versucht sie beim nächsten `match`-Lauf nachzuziehen. ## Offene Risiken / Bekannte Schwächen - Die Schwelle `0.06` für "Beat-Kontext gewinnt" in `realign_window` ist kalibriert an Beat 20. Andere Beats sollten auch durchlaufen werden, bevor weitere Beats angefasst werden — am besten ein voller `python cli.py match` ohne `--beat` und Diff der `match_results.json` gegen `.bak`. - Die Filter-/Repair-Stufe kann durch Vision-Calls minutenlang laufen. Das ist nicht neu, aber bei Netzproblemen sehr sichtbar. - Die `_filter_repair_one`-Funktion bekommt viele Argumente durchgereicht (closure-Variablen aus dem Parent). Bei einer nächsten Iteration könnte das in eine kleine Klasse umgebaut werden. ## Useful greps - `find_action_window_in_scene` — semantische Action-Window-Suche (Vision). - `_reference_scoreable_segments` — bestimmt die sichtbaren Inseln eines Beats. - `estimate_usable_source_duration` — kürzt Match-Clips, wenn die Source vor Beat-Ende in eine andere Phase wechselt. - `_filter_semantically_invalid_vision_matches` — Eintrittspunkt der Repair-Stufe in `cli.py`.