# AGENTS.md — неизменяемые правила версии 4.0

## Главный закон

**Речь диктует монтаж.** Сначала реальные слова и их временные метки, затем субтитры, затем б-роллы, графика и переходы. Сценарий пользователя помогает исправлять распознавание, но никогда не подменяет фактически произнесённый текст.

## Исходники

- Перед выбором исходника запусти `scripts/discover_sources.py`.
- Нельзя выбирать «самый большой файл» и считать его единственным исходником.
- Если найдено несколько исходных видео, каждое обязано быть просмотрено, расшифровано и включено в отчёт.
- Старые экспорты с `final`, `edit`, `cut`, `render`, `preview`, `master` не могут стать исходниками без прямого указания пользователя.
- Храни исходники неизменными, вычисляй SHA-256, не режь их на месте.
- Для каждого файла длиннее 90 секунд создай глобальную сетку по всей длительности и просмотри лучшие окна по метрикам. Наличие файла в реестре не считается анализом содержимого.

## Синхронизация

- Используй Whisper/Faster-Whisper с `word_timestamps=True`.
- Нельзя выбирать CUDA только по наличию `nvidia-smi`: до загрузки модели
  проверяй CUBLAS. `--device auto` обязан безопасно перейти на CPU `int8`;
  явный CUDA-режим обязан завершиться с понятной ошибкой до декодирования.
- Запрещено распределять слова пропорционально длине сценария или длительности ролика.
- Запрещено придумывать заголовок, призыв или фразу, которой нет в распознанной речи.
- Первый титр начинается по первому реально произнесённому слову. Последний заканчивается по последнему слову.
- Монтажная шкала, субтитры, исходное видео и звук строятся из одного `timeline.json`.
- Видео и его родной звук нельзя резать разными командами или по разным спискам точек.

## Б-роллы

- Б-роллы накрывают базовый исходник, но не меняют его временную шкалу.
- Изображения кадрируются через `cover`, никогда не растягиваются.
- Для статичных изображений: плавный масштаб 1.02–1.06 и смещение до 2–4% за сцену; без вибрации.
- Б-ролл начинается на реальном слове/фразе, которую раскрывает, а не «примерно на восьмой секунде».
- Сетевой сток сначала ищется через `--search-only`; скачивается только после просмотра конкретных ID.
- У каждого сетевого файла обязательны автор, страница источника, лицензия, запрос и время загрузки.
- Манифест сетевых файлов накопительный: новый запрос не имеет права стирать
  записи прошлых загрузок. Кэш API привязан к корню скилла, а не к `cwd`.
- Pixabay API нельзя выдавать за API аудиомузыки: публично документированы изображения и видео.
- Пользовательские исходники имеют приоритет над сетевым стоком. Скачанный файл не обязан попадать в монтаж.
- До установления места/действия запрещена пачка коротких перебивок. В первые 5 секунд — не более двух склеек без прямого требования агрессивного тизера.

## Режиссёрский recut

- Один законченный смысл — один устойчивый визуальный эпизод.
- Если рот виден, видео и звук обязаны происходить из одного исходного интервала.
- Voice-over допускается только поверх кадра, где рот спикера не виден.
- При сокращении пауз каждое слово должно попасть ровно в один recut-интервал.
- Автоматический план пауз сначала строится через
  `scripts/build_pause_safe_recut.py`, затем проверяется
  `scripts/remap_recut_timeline.py`.
- `scripts/remap_recut_timeline.py` блокирует монтаж при потере или дублировании слова.
- Переход по умолчанию — чистая склейка. Эффектный переход требует смысловой причины.

## Музыка

- Трек из каталога Pixabay сохраняется локально и получает карточку через `scripts/register_music_asset.py`.
- Карточка содержит страницу трека, автора, лицензию, SHA-256, длительность и статус Content ID.
- Во время речи музыка приглушается sidechain-дакингом; после последней фразы может подняться на 3–6 дБ.
- Базовый Palmier-мастер собирается через `scripts/render_timeline_base.py`;
  фильтр сохраняется в отчёт, а выходной звук фиксируется как AAC 48 kHz stereo.
- Нельзя распространять музыкальный файл отдельно от созданного ролика.

## Шрифты и титры

- Используй только проверенный кириллический шрифт с локальной лицензией.
- Перед финалом запускай `scripts/font_audit.py`.
- ASS прожигается через `scripts/burn_ass_captions.py` с явным локальным
  `--fonts-dir`; системный font fallback не считается воспроизводимым.
- Самая длинная реальная фраза должна помещаться максимум в две заранее сбалансированные строки.
- RGB-регистрация допустима только как слабый вторичный слой; основной текст остаётся резким.
- Акцентная жёлтая плашка используется точечно, а не на каждой фразе.

## HyperFrames

- Перед рендером обязательны `doctor`, `lint --json`, `inspect --json` и предварительный просмотр.
- Корневая `data-duration` должна быть равна вычисленной длительности итоговой шкалы.
- Любой GSAP-таймлайн должен доживать до последнего кадра композиции.
- Используй только детерминированные анимации, привязанные к `hf-seek`.
- Каталожные блоки предпочтительнее самодельных эффектов.
- `spawn EFTYPE` на Windows означает обязательную проверку реального браузерного
  бинарника через `--version`. Очищать можно только браузерный кэш; затем
  использовать `browser ensure` или валидный `HYPERFRAMES_BROWSER_PATH`.

## Приёмка

- Сначала рендер 0–15 секунд и отдельный рендер последних 5 секунд.
- Контрольные кадры: первый звук, первые 3 слова, начало каждой перебивки, стык исходников, последнее слово, последний кадр.
- Итог нельзя отдавать, если `validate_sync.py`, `validate_full_end.py` или `validate_render.py` нашли ошибку.
- Финальный файл получает новое уникальное имя, чтобы приложение не показало закэшированную старую версию.
- Не объявляй успех без отчёта: источники, расшифровка, покрытие слов, длительность, последний кадр, звук, контрольные изображения.


## Правила версии 4.0

### Шрифты и субтитры

- В `--fonts-dir` передаётся **плоская** папка (`assets/fonts/all`). libass не читает
  подпапки и молча уходит на системный шрифт.
- Имя шрифта в стиле ASS берётся из `font_manifest.json` (`ass_names`), а не
  придумывается: libass ищет по `name ID 1`, и у вариативного файла это «... Thin».
- Прожиг обязан завершиться без подстановки шрифта. `burn_ass_captions.py` падает,
  если libass взяла системный шрифт.
- Блок с флагом `overflow` в `caption_map.json` — это брак, а не предупреждение.
- Стиль субтитров выбирается по контактному листу с рамкой безопасной зоны, а не по
  названию пресета.

### Речь

- Пауза, чей уровень выше `речь − 6 дБ`, не режется: это почти наверняка речь, не
  попавшая в границы слов.
- Вздох не удаляется полностью — остаётся короткий остаток.
- Слова-филлеры («ну», «вот», «типа») никогда не удаляются автоматически.
- Отчёт нарезки обязан показать `words_in == words_out`.
- Скорость меняется только на склейках. Границы сегментов выравниваются по кадрам.
- После ретайминга субтитры строятся по новому таймлайну, а не по исходному
  транскрипту.

### Переходы и звук

- Прямая склейка — вариант по умолчанию и стоит 0 очков энергии.
- Один рецепт не идёт дважды подряд, бюджет энергии на минуту не превышается.
- Переход, гасящий картинку, ставится только в паузу речи.
- Титры и музыка читают `cut_points` из отчёта переходов, а не исходные времена
  клипов: `xfade` перекрывает клипы и укорачивает результат.
- Уровень SFX считается от измеренной громкости голоса.

### Цвет и громкость

- Порядок: канвас → грейд → зерно → прожиг титров. Грейд применяется один раз.
- Громкость нормализуется один раз, в последнем шаге.
- Больше 1.5% кадра в чистом чёрном — тени потеряны, грейд надо ослабить.

### Кодеры и длинные видео

- Кодер берётся из `render_core.py`, а не угадывается. Наличие энкодера в списке
  ffmpeg ничего не значит.
- Отказ GPU переводится в действие («нужен драйвер X»), а не прячется.
- Видео длиннее двадцати минут рендерится чанками с возможностью возобновления.
- Звук обрезается по длительности **видеопотока**, а не контейнера: иначе на каждом
  шаге цепочки он становится на пару кадров длиннее.

### Стоки

- Поиск не скачивает. Скачивание требует явных `provider:id`.
- Выбор делается по контактному листу.
- Без записи автора, лицензии, страницы и SHA-256 файл не считается пригодным.


### Графика никогда не ложится на лицо

Позиция плашки не выбирается на глаз и не берётся из «обычно лицо в верхней трети».
Перед наложением зона измеряется:

```bash
python scripts/find_overlay_zone.py base.mp4 --canvas reels   --skip "4.5-8,17.8-21" --caption-map caption_map.json
```

Скрипт возвращает, какие строки кадра занимает спикер, где зона титров, где
безопасная зона платформы и какие свободные полосы остались. Высота плашки берётся
из `best_band`, а не из вкуса.

На реальном вертикальном материале спикер занимал y 356..1658 из 1920: единственная
свободная полоса — 0..356 сверху, а «типичная» полоса между лицом и титрами не
существовала вовсе. Плашка, поставленная по предположению, легла ровно на лоб, а
после сдвига вниз — на глаза.

- Плашка обязана целиком уместиться в измеренную полосу. Не уместилась — сокращай
  содержимое, а не занимай лицо.
- Нижняя полоса вертикального кадра почти всегда под интерфейсом платформы: она
  свободна для человека, но не для графики.
- Проверка обязательна: скомпонуй снапшот плашки поверх настоящего кадра и посмотри
  глазами до того, как рендерить сотни кадров.
