# Algorithmus-Notizen

Detaillierte Verhaltens­beschreibung und Designentscheidungen des Matchers.
Für die normale Bedienung reicht die [README](../README.md) — dieses Dokument
ist die Referenz für den Tool-Verantwortlichen, wenn etwas im Verhalten
unklar wird oder ein Match systematisch falsch landet.

## HTML-Report und Vorschauclips

Der HTML-Report regeneriert seine Preview-Clips bei jedem Lauf mit genauer
FFmpeg-Nachsuche und synchronisiert die beiden Video-Player pro Beat. Dadurch
ist der Report zur Frame-Prüfung geeignet und zeigt keine alten gecachten
Preview-Clips. Source-Previews bekommen bei Trailer-only-Tails denselben
schwarzen Tail wie der Export, damit der Browser nicht einen zu kurzen
Source-Clip gegen den längeren Referenzbeat weiterspult oder loopt.

Zur Synchronprüfung rendert der Report ein einzelnes Frame-Locked-Compare-Video
mit Referenz und Source in demselben MP4-Stream. Dieses Compare-Video ist
maßgeblich, weil zwei getrennte Browser-Videoelemente nie zuverlässig
framegenau synchron bleiben.

## Trailer-Tails ohne Source-Pendant

Wenn ein Trailer-Beat am Ende eine Blende, Schwarzfläche oder Textkarte
enthält, die im Source-Film nicht als normaler Shot vorhanden ist, endet der
Source-Match am letzten stabil passenden Frame. Exportierte Timelines behalten
trotzdem die volle Beat-Länge und fügen danach automatisch einen schwarzen
Trailer-Tail mit Marker für Fade/Dissolve ein.

## Targeted single-beat re-matches

Gezielte Ein-Beat-Matches nutzen zusätzlich vorhandene automatische Nachbarbeats
aus dem Cache als zeitliche Suchanker. Das hilft bei aufeinanderfolgenden
Shots, ohne manuelle Szenen oder Timecodes zu kuratieren. Bei `match --beat N`
wird ein alter Cache-Treffer für genau diesen Beat entfernt und nur ein neu
gefundener automatischer Treffer wieder eingetragen. Ein fehlgeschlagener
neuer Lauf kann dadurch keinen alten falschen Report-Treffer stehen lassen.

## Bildvergleich auf Luma + Kanten

Der globale Bildvergleich arbeitet auf kontrast-normalisierten Luma- und
Kantenfeatures statt auf rohen Farb-Pixeln. Dadurch bleiben Schwarzweiß-
oder anders gegradete Trailerbilder mit dem Source-Material vergleichbar,
während unähnliche Farbshots schlechter ranken.

Die Inpoint-Feinjustage bestimmt den Versatz lokal aus dem Bildinhalt: um
einen groben Treffer herum werden mehrere Referenzframes gegen mehrere
Source-Offsets verglichen, und der beste gemeinsame Offset wird übernommen.
Das ist schneller als ein erneuter globaler Scan und vermeidet pauschale
Frame-Prerolls.

## Bewegungsphasen-Vergleich

Zusätzlich wird die Bewegungsphase über Frame-zu-Frame-Differenzen verglichen.
Dadurch kann der Matcher innerhalb derselben Source-Szene unterscheiden, ob
zwei Figuren noch sprechen, sich annähern, bereits im Kontakt sind oder sich
wieder voneinander lösen. Ein optisch ähnlicher Standbild-Treffer reicht damit
nicht mehr aus, wenn der Bewegungsverlauf nicht zur Referenz passt.

Schwarze Referenzframes aus Blenden oder Titel-Tails werden für diese
Offset-Messung ausgelassen, damit echte Bildbewegung und nicht die Blende
selbst den Inpoint bestimmt. `rematch --refine` nutzt denselben lokalen
FFmpeg/Pillow-Aligner und schreibt den korrigierten Inpoint direkt zurück in
`.cache/match_results.json`.

## Vision-Layer (optional)

Optional kann `python cli.py match --beat N --vision` einen Vision-Layer
zuschalten. Dann werden pro Trailer-Beat und pro wenigen Scene-Level-
Kandidaten je drei Frames (Anfang, Mitte, Ende) von einem visionfähigen
OpenAI-kompatiblen Modell beschrieben. Die Beschreibungen liegen in
`.cache/vision_descriptions.json` und werden wiederverwendet. Vision erzeugt
nur zusätzliche Suchanker; der eigentliche Match muss weiterhin durch CV,
Content-Reranking, Timing und Duration-Coverage bestätigt werden.

Gecachte Szenenbeschreibungen zählen nur, wenn sie vom aktuell konfigurierten
Vision-Modell stammen. Bei langen semantisch passenden Source-Szenen
beschreibt der Vision-Layer zusätzlich wenige lokale Zeitfenster und cached
auch diese Fenster, damit eine grob ähnliche Szene nicht automatisch mit dem
falschen Bewegungs- oder Dialogmoment gleichgesetzt wird. Dieser lokale
Fenster-Probe ist bewusst breiter als die finale Seed-Auswahl: eine lange
Dialogszene kann in der Gesamtbeschreibung nur als Gespräch erscheinen, aber
an einer späteren Stelle trotzdem genau die gesuchte Aktionsphase enthalten.

Für diese Probe wird die grobe Szenenähnlichkeit ohne harte Aktionsstrafe
gerankt; die harte Aktionsprüfung greift erst auf den lokalen Fenstern und
dem finalen Source-Zeitbereich.

## Aktionsphase-Verifikation nach dem CV-Match

Nach dem CV-Match kann derselbe Vision-Layer den konkreten finalen Source-
Zeitbereich nochmals gegen den Trailer-Beat prüfen. Starke Aktionsphasen wie
Annäherung, Kuss/Stirnkontakt, Handbewegungen oder Schneiden müssen dann auch
im Source-Fenster beschrieben sein; fehlt diese Aktionsphase, wird der
Treffer nicht gespeichert, selbst wenn der Low-Level-CV-Score hoch ist.

Wenn die Szene selbst plausibel ist, aber der konkrete Source-Zeitpunkt
diese Aktionsphase verfehlt, sucht der Matcher automatisch dichter innerhalb
derselben Source-Szene nach lokalen Vision-Fenstern mit der passenden Aktion
und richtet den Inpoint mit der Motion-Phase-Prüfung darauf neu aus. Erst
wenn auch diese In-Scene-Reparatur scheitert, wird der Treffer verworfen.
Diese In-Scene-Reparatur läuft auch für semantisch gültige Treffer aus langen
Source-Szenen.

Die Kandidatenbewertung dieser Reparatur vergleicht zwei Kontexte: den ganzen
Beat als semantischen Handlungsrahmen und das konkret sichtbare Beat-Segment
als Phasenprüfung. Ein Source-Zeitpunkt muss nicht nur „die Szene mit dem
Kuss" enthalten, sondern auch zur aktuellen Bewegungsphase des sichtbaren
Trailerabschnitts passen. Pro Kandidat fließt zusätzlich ein lokaler
Content-/Motion-Frame-Score ein, damit cached Vision-Beschreibungen keinen
sichtbar versetzten Bewegungsmoment überstimmen.

## Segmentierte Beats (Beats mit Blenden / Inseln)

Bei blendigen oder segmentierten Beats nutzt die semantische Action-Suche den
ganzen Trailerbeat als Kontext. Die eigentliche Frame-Ausrichtung bleibt auf
das sichtbare Segment begrenzt; der gefundene Source-Inpoint wird um den
Trailer-Offset des Segments verschoben. So geht die globale Aktionsbeschreibung
eines Beats nicht verloren, nur weil der scorebare Teil erst nach einer Blende
beginnt.

Die Suche nach diesem Action-Window prüft pro Segment zwei Beschreibungen:
zuerst die des konkret sichtbaren Segments (so trifft die Phasensuche genau
die gerade gezeigte Bewegung), als Rückfall die des gesamten Beats. Der
Beat-Kontext gewinnt nur, wenn er deutlich (>0.06) besser scort; sonst bleibt
das Segment-Fenster die Wahl, weil die Beat-Beschreibung Aktionen aus
Fade-Bildern mit aufnehmen kann, die im sichtbaren Segment nicht stattfinden.
Der Trailer-Offset-Shift wird nur angewendet, wenn tatsächlich der Beat-
Kontext benutzt wurde; bei segmentbasierter Wahl ist das gefundene Fenster
bereits auf die sichtbare Aktionsphase ausgerichtet.

Der Segment-Offset zählt nur über vorherige scorebare Bildinseln, nicht über
schwarze oder blendige Lücken. Nach dem Retiming wird die nutzbare Source-
Dauer erneut geschätzt; läuft die Source am Ende in eine sichtbar andere
Aktionsphase, wird der Treffer im Cutter-Report klar als phasenkritisch
markiert. Schwarz/Placeholder wird nur für wirklich ungematchte Trailer-
Bereiche oder Fades verwendet, nicht um sichtbare Kandidatenbewegung im Review
zu verstecken.

Diese Span-Schätzung ist strenger als der grobe Suchscore: Ein fast stehender
Anfang darf einen Match nicht retten, wenn spätere Frames sichtbar in eine
andere Gestik, Körperposition oder eintretende Figur driften. Stabile
Score-Plateaus dürfen nur verlängern, wenn sie noch nah genug am Anfangsniveau
liegen; sonst bleibt der Treffer vorläufig und muss neu gesucht oder visuell
geprüft werden. Der Review-Clip zeigt den Kandidaten weiterhin sichtbar, damit
Phasenfehler nicht durch Schwarz verdeckt werden.

Für Multi-Shot-Beats gilt zusätzlich eine Segment-Schwelle pro sichtbarer
Einstellung. Ein gutes erstes Segment darf kein zweites Segment mit schwachem
Score mitziehen. Segmente unter `multi_shot_segment_threshold` werden nicht als
stabile Wahrheit behandelt, sondern innerhalb derselben plausiblen Source-Scene
nachjustiert. Die Nachjustierung nutzt eine saliency-gewichtete Mehrframe-Prüfung:
Timecodes und statische Randbereiche werden entwertet, kontrastreiche und über
mehrere Trailerframes unterscheidbare Bildbereiche zählen stärker. Dadurch kann
eine schwache zweite Einstellung phasengenauer repariert werden, ohne den Fehler
durch Schwarzbild zu verdecken oder einen Beat manuell zu kuratieren.

Der Cutter-Report verwendet Clip-Caching. Bereits vorhandene Compare-Clips werden
wiederverwendet; bei gezielten Rematches wird nur der betroffene Beat neu gerendert
(`CUTTER_REPORT_FORCE_BEATS`). So bleibt der Report aktuell, ohne alle Beats jedes
Mal neu zu kodieren.

## Vision-Seeds vs. Vollscan

Der gewichtete Vision-Seed-Pfad ersetzt standardmäßig keinen normalen
FFmpeg-Vollscan. Vision-Beschreibungen sind semantische Hinweise, aber keine
Beweise; der volle CV-Scan bleibt aktiv, damit falsch bewertete Vision-Szenen
echte Treffer nicht verdrängen. Für schnelle Experimente kann
`skip_coarse_scan_with_weighted_seeds = true` gesetzt werden.

Gewichtete Vision-Seeds werden nicht zuerst durch den alten Midpoint-Template
Refine verschoben; sie gehen direkt in die lokale Content-Alignment-Prüfung.
Das schützt wiederholte Gesprächseinstellungen, bei denen ähnliche Momente
mehrfach in derselben Szene vorkommen.

Innerhalb der automatisch von Vision vorgeschlagenen Szenen läuft zusätzlich
eine dichte lokale Bildsequenzsuche. Sie misst den Phasenversatz in kleinen
Zeitschritten direkt am Bildinhalt und bevorzugt Kandidaten mit genügend
Restdauer in derselben Source-Szene. Das ist kein manueller Override: Vision
grenzt nur Suchbereiche ein, die Auswahl bleibt Content-, Timing- und
Coverage-getrieben. Nach einem dichten Vision-Treffer darf der spätere
lokale Aligner nur noch im Bereich dieses Scan-Schritts nachjustieren. So
kann ein korrekt gefundener Bewegungsmoment nicht wieder um viele Frames in
eine ähnlich aussehende Phase derselben Szene verschoben werden.

Für Vision-Action-Fenster nutzt die finale Retiming-Prüfung eine gemeinsame
Content-und-Motion-Suche pro Frame. Content und Bewegungsphase werden nicht
mehr als zwei getrennte Korrekturschritte angewendet; das verhindert, dass
eine kurze Geste erst korrekt erkannt und anschließend in eine spätere
ähnliche Körperhaltung verschoben wird. Wenn mehrere Vision-Kandidaten in
derselben Source-Szene ähnlich gut scoren und die Beat-Dauer abdecken,
bevorzugt der Matcher die frühere Phase.
Die Vision-Recovery läuft nicht nur für komplett fehlende Beats, sondern auch
für schwache unbestätigte Treffer. Gerade Low-Light-Beats dürfen nicht an einem
falschen dunklen CV-Treffer hängen bleiben, wenn der Cache semantisch eine
bessere Handlungsphase kennt.

Der zusätzliche Hi-Res-Phasenrefine bleibt lokal um den bereits validierten
Inpoint und übernimmt nur klare Verbesserungen. Er darf keine ganze lange
Dialogszene nach ähnlichen Layouts durchsuchen, weil sonst dieselbe Location
mit anderer Gestik als falsche Phase gewinnen kann und die Laufzeit explodiert.
Die lokale Retune-Wertung nutzt deshalb nicht nur den mittleren Frame-Score,
sondern auch den schlechtesten Einzelvergleich, die ersten sichtbaren Frames
und die Frame-zu-Frame-Bewegung. Dadurch gewinnt nicht mehr ein späteres
Standbild derselben Einstellung, nur weil Fenster, Gesichter und Licht fast
identisch aussehen.
Report-Clips werden zusätzlich an den bekannten Source-Szenenstart plus eine
sehr kurze Ein-Frame-Guard-Zone geklemmt, damit ein knapp vor oder direkt auf
der Schnittkante liegender Inpoint nicht mit Frames der vorherigen Einstellung
beginnt. Die Guard-Zone bleibt bewusst klein, weil eine längere Korrektur die
sichtbare Bewegungsphase innerhalb derselben Einstellung verschieben würde.

## Multi-Shot-Beats

Enthält ein Trailerbeat selbst einen harten Umschnitt, werden Kandidaten an
angrenzenden Source-Szenengrenzen zusätzlich als zusammenhängender Multi-Shot-
Span geprüft. Ein Match darf dann über eine Source-Szenengrenze laufen, aber
nur wenn die relative Source-Grenze zeitlich zu einem erkannten Trailer-
Umschnitt passt. So kann ein Beat aus Frage/Antwort-Shots vollständig erfasst
werden, ohne Szenen willkürlich zusammenzukleben.

## Reranking-Pipeline

Vor dem teuren Frame-Refine wird der gesamte Kandidatenpool mit einer
schnellen festen Inhaltsprüfung neu sortiert. Dadurch können korrekte Treffer
aus wiederholten Einstellungen einer Szene nach oben kommen, auch wenn ein
freier Template-Peak an anderer Stelle numerisch stärker war. Suchanker
bleiben im Pool erhalten, dürfen aber erst nach der Inhaltsprüfung nach oben
rücken. Wenn ein Kandidat visuell plausibel ist, aber wegen Trailerblende oder
kurzem Source-Span die normale Coverage knapp verfehlt, wird er als
provisional Match behalten statt als `NO MATCH` verworfen.

Dieses Reranking berücksichtigt zusätzlich die verbleibende Szenenlänge ab
dem Kandidaten-Inpoint, nutzt bewusst nur wenige repräsentative Referenzframes
und eine begrenzte Kandidatenzahl. Confirmed Matches werden zusätzlich durch
eine feste nahezu-Whole-Frame-Prüfung aus Luma, Kanten, Farbhistogramm und
räumlichen 4×4-Farbhistogrammen gedeckelt. Auch der lokale Content-Aligner
darf einen Inpoint nur noch übernehmen, wenn die feste Whole-Frame-/Spatial-
Validation dadurch besser wird.

Für gewichtete Vision-Kandidaten gibt es zusätzlich eine eigene Provisional-
Bewertung aus Content-Score, Restdauer und Seed-Stärke. Die Cache-
Normalisierung für Report/Export verwendet dieselbe niedrigere
Content-Untergrenze für nicht bestätigte Vision-Provisional-Treffer und
übernimmt die Multi-Shot-Coverage-Regel: gecachte Treffer, die passend zu
internen Trailer-Umschnitten über angrenzende Source-Szenen laufen, werden
nicht mehr auf die erste Source-Szene zurückgekürzt.

## Continuity-Seeds

Gezielte Einzel-Beat-Matches gewichten außerdem die automatisch aus
Nachbarbeats abgeleiteten Continuity-Seeds. Wenn ein Beat direkt an einen
bereits passenden Vorgänger anschließt, kann ein späterer ähnlich aussehender
Moment derselben Dialogszene den erwarteten Anschluss nicht mehr nur wegen
eines höheren Standbildscores verdrängen. Diese Continuity-Seeds sind aber
nur Suchanker: in derselben Szene darf ein späterer Inpoint gewinnen, wenn
die mehrframeige Content-Prüfung die Bewegungsphase klar besser trifft.

## Vision-Prepass für gezielte Match-Läufe

Bei aktivierter Vision wird für gezielte Match-Läufe zuerst ein schneller
seed-basierter CV-Prepass ausgeführt. Er überspringt den vollen FFmpeg-Stream
nur vorläufig und akzeptiert einen Treffer erst nach derselben Bild-/
Phasenvalidierung wie der normale Matcher. Nur nicht gelöste Beats fallen
danach auf den vollständigen Scan zurück.

Provisional Treffer aus diesem schnellen Prepass sind nicht endgültig: wenn
sie unterhalb der Confirmed-Schwelle bleiben, läuft zusätzlich der
vollständige CV-Scan und darf den besseren oder bestätigten Treffer
übernehmen.

## Fehlertoleranz bei Vision-API

OpenRouter-/Vision-Rate-Limits werden mit progressiv längeren Pausen erneut
versucht. Auch Netzfehler beim Lesen der Antwort (Timeouts,
Verbindungsabbrüche während einer DSL-Trennung) werden als retrybar
behandelt, nicht nur Verbindungsfehler beim Verbindungsaufbau.
Billing-, Credit- oder Token-Guthaben-Fehler werden dagegen sofort als
echter Blocker gemeldet, weil Warten dort nicht hilft.

Schlägt die Vision-Verifikation während der finalen Filter-/Repair-Stufe
trotzdem dauerhaft fehl, wird der bisherige gecachte Treffer für diesen
Beat behalten statt verworfen — ein Netzproblem darf keinen schon korrekt
gefundenen Match aus dem Cache löschen.

## Phasen-Reparatur und Recovery

Die Phasen-Reparatur an gefundenen Treffern läuft nicht nur in „langen"
Source-Szenen, sondern überall dort, wo die Szene mehr als nur das
Segment-Fenster trägt. Eine korrigierte Position wird übernommen, sobald sie
das Bildinhalt-Validate besteht UND nicht spürbar schlechter scort als das
Original (≤ 0.02 Verlust). Bereits bestätigte Treffer in eng zugeschnittenen
Szenen werden bewusst nicht angefasst, damit ein guter Match nicht durch
eine nominell gleichwertige Alternative ausgetauscht wird.

Beats, die nach dem CV-Lauf weder als Vollmatch noch als Segmentmatch
landen, durchlaufen anschließend eine Recovery-Stufe: Vibe-Check
(Histogramm/pHash) liefert Top-K Kandidatenszenen, die semantische
Action-Window-Suche prüft darin die Phase des sichtbaren Trailerbeat-
Anteils, und der CV-Aligner setzt den Inpoint frame-genau. Übernommen wird
nur ein Kandidat, der dieselbe Vision-Phasenvalidierung wie der Hauptpfad
besteht. Beats ohne sichtbares Bildmaterial (Logos, Titel-Karten,
durchgehende Fades) werden gar nicht erst gesucht — sie sind bewusst kein
Match.

## Behandlung von Blenden und Schwarzfeldern

Lange Trailerbeats werden nicht automatisch über ihre gesamte Beat-Länge
gegen einen einzigen Source-Clip validiert. Sobald nach einem sichtbaren
Source-Abschnitt eine anhaltende Schwarzblende oder Titel-/Credit-Insel
beginnt, endet der matchbare Referenzbereich dort; zwei aufeinanderfolgende
dunkle Samples reichen dafür. Spätere Text-/Creditbilder im selben Beat
gehen nicht mehr in Reranking, Validation oder Span-Schätzung ein.

Wenn nach einer Blende wieder sichtbares, matchbares Material kommt, wird
der Beat nicht mehr als „Source + schwarzer Tail" behandelt. Der CLI-Match
speichert zusätzliche `MatchSegment`-Einträge für jede automatisch erkannte
sichtbare Insel; der HTML-Report setzt diese Source-Segmente frame-lockend
zusammen und füllt nur echte Zwischenlücken mit Schwarz.

Beats mit mehreren sichtbaren Inseln werden direkt segmentiert gesucht,
statt zuerst als ein künstlich zusammenhängender Source-Clip über den
ganzen Film zu laufen. Falls ein kompletter Beat keinen belastbaren
Einzelclip ergibt, versucht der Matcher dieselbe Segmentlogik automatisch
als Fallback. Sehr kurze Inseln dürfen zusätzlich in den Source-Szenen
benachbarter bereits gematchter Beats lokal nach ihrer Bewegungsphase
suchen.

Besteht ein Beat nach automatischer Fade-/Titel-Filterung nur aus einer
einzigen sichtbaren Insel, wird diese Insel direkt als primäres Suchziel
verwendet. Gecachte segmentierte Treffer werden gegen die automatisch
sichtbare Referenzdauer normalisiert, nicht gegen Schwarz-/Blendränder
des gesamten Beats.

Sehr dunkle, kontrastarme oder noch nicht sauber auf-/abgeblendete
Referenzframes werden aus Score, Inhalts-Reranking, Phasen-Alignment und
Motion-Templates herausgenommen. Sichtbare Fade-Rampen werden nur in eine
matchbare Insel hinein erweitert, wenn sie strukturell stark zum ersten
bzw. letzten scorebaren Frame derselben Einstellung passen.

Treffer unter `provisional_content_threshold` werden nicht mehr gespeichert
oder aus alten Cache-Ergebnissen übernommen.

