Knowledge Base3 · Ингест и синхрон → 3.4 Frame-Align & Build Verify
Последняя миля синхрона: числа из ingest.json должны доехать до таймлайна Premiere без потерь. Проверено на живых сборках (YTCH10, YTCH13, 16–17.08.2026): у clip-API Premiere нет субкадрового рычага — всё, что не на кадровой сетке, он молча округляет, и миллисекундная точность Fine Sync умирает на последнем шаге. Этот этап решает задачу системно: всё на сетку точными тиками, миллисекунды — в сами WAV, а после сборки — проверка числом. Итог: синхрон петлички с видео ≤ 1 сэмпла (0.02 мс), и ни одна поломка сборки не проходит молча.
3.2 Word-Sync 0.6–0.9 с3.3 Fine Sync 0.4 мс в числах3.4 Frame-Align · рендер + сеткаBuild IngestVerify PASS
1Быстрый старт2Физика Premiere3Архитектура4Пошагово5Сигналы и триаж6Критерий приёмки7Где лежит

3.4 · 1Быстрый старт (весь этап, любой проект)

Предпосылка: пройдены 3.2 и 3.3 — в ingest лежат выверенные wall_offset. Дальше:

P="/Volumes/DISK/YTXXNN_Project"
cd ~/YTAI/scripts/999_extra/wordsync_multicam

# 1. Рендер петличек в координаты таймлайна + патч ingest (идемпотентно)
python3 0113_frame_align.py --project "$P" --dry-run   # посмотреть
python3 0113_frame_align.py --project "$P"             # → "VERIFY OK: all placements on exact frame ticks"

# 2. Premiere: перезагрузить панель → вкладка Ingest → Build Ingest
# 3. Панель → Debug Dump (пишет 99_Pipeline/logs/{slug}_dump_*/)

# 4. Гейт числом — обязательный
python3 verify_build_sync.py --dump "$P/99_Pipeline/logs/<свежий дамп>"
# → PASS = таймлайн совпал с планом тик-в-тик. FAIL = не отдавать дальше.

После ЛЮБОЙ правки ingest (finesync, ручные offset'ы, пере-нарезка сцен) — заново 0113 → Rebuild → verify. Гварды устаревания (ниже) не дадут забыть.

3.4 · 2Физика Premiere — измерено, не предположено

Всё ниже — из тик-дампов реальных сборок (254 016 000 000 тиков/с; 54 точки данных YTCH13 + YTCH10), а не из документации Adobe (там квантование не описано вообще):

операциячто делает Premiere со временемследствие
setSourceInOut (трим источника)FLOOR к видеокадровой сетке — даже для аудио-only WAVсубкадровая in-точка невозможна
createInsertProjectItemActionОКРУГЛЯЕТ позицию к БЛИЖАЙШЕМУ кадру (28/28 замеров: дробь <20 мс → вниз, >20 мс → вверх @25p)субкадровая позиция невозможна; компенсация «дробь в позицию» не работает
createOverwriteItemActionon-grid тики проходят точно; float-секунды опасны (double 109.44 лёг на +1 кадр)позиции слать только целыми тиками кадра
compound-транзакцияисполняет свои действия В ОБРАТНОМ порядкебатч overwrite'ов: хвост раннего клипа съедает голову позднего → видео класть по одному, по возрастанию времени
фабрики действий (25.6)работают только внутри lockedAccess — иначе «Requires locked access»создавать action внутри колбэка транзакции, никогда до
«вставлено ok»≠ «элемент на таймлайне» (90_CTA: «Placed 3 TX, 0 skipped» — на дорожке 1)после сборки — обязательный пересчёт фактических элементов

Вывод, на котором стоит весь этап: субкадрового рычага у clip-API нет. Поправки fine-sync субкадровые и у каждого видеоклипа свои → остаток на каждом слайсе РАЗНЫЙ (−31.6…+19.9 мс на YTCH13 до фикса), одним сдвигом файла не лечится. Единственный сэмпл-точный носитель — сама медиа.

3.4 · 3Архитектура: три слоя

1
Всё на сетку — точными тиками

sceneLayout.gridAlignPlan: позиции видео и слайсов, in/out-точки — целые кадры, отправляются через TickTime.createWithTicks (float-секунды типа 109.44 сидят на пол-ULP мимо сетки и ловят ±1 кадр). Блок «видео + петля» якорится к кадру ЦЕЛИКОМ — ступенек на границах V/A нет по построению. Первый блок сцены якорится в 0 — мёртвый воздух до первого видео схлопывается (петличка часто стартует раньше камеры). Видео кладётся последовательными транзакциями по возрастанию времени.

2
Миллисекунды — в WAV: timeline-domain рендер (0113)

0113_frame_align.py рендерит на каждую (сцену × петличку) один WAV, где время файла = финальное время таймлайна: контент каждого слайса лежит ровно на том сэмпле, где слайс встанет (пер-слайсная компенсация из content_src_sec плана). Билдер кладёт identity-слайсы (offset == source-in, оба на тиках) — Premiere нечего округлять. Синхрон ≤ 1 сэмпла. Пути оригиналов петличек сохраняются в tx_strips_source — локально на SSD они остаются в 01_Source/{сцена}/, пересборка читает их оттуда; на Drive оригиналы архивируются в 99_Pipeline/DJI_Audio/{сцена}/, понадобилась пересборка — вернуть их в 01_Source локально и перегнать 0113. Бэкап ingest пишется рядом, повторный запуск идемпотентен. Хэндлы работают: потянуть край слайса = открыть соседний контент таймлайна.

3
Гейты: ни одна поломка не проходит молча

Три уровня. READBACK в билдере: после сборки панель пересчитывает фактические элементы по дорожкам против плана — «items VANISHED» / «stray items» видно прямо в логе сборки. Гварды устаревания в плане: рендер несёт отпечаток раскладки (block spans + wall_offset источников) — любое изменение после 0113 даёт tx_td_render_stale / tx_td_sources_changed. verify_build_sync.py: дамп секвенций против плана тик-в-тик — счётчики по каждой дорожке (включая камера-аудио), позиции, in-точки, длительности; допуск 2 мс; exit 1 = не отдавать проект дальше.

Единый источник правды: tools/plan_cli.js гоняет ТОТ ЖЕ чистый код раскладки, что и панель — 0113 и verify считают ровно то, что построит Build. Мок в тестах моделирует реальное квантование (floor/nearest/реверс/lock) — регрессии этого класса ловятся до Premiere.

3.4 · 4Пошагово с контролем результата

1
0113 — рендер и патч

Сначала --dry-run: таблица «сцена × петличка × слайсы», длины рендеров и coverage gaps (файл рекордера кончился раньше блока видео — там будет тишина; десятки мс на стыках 30-мин файлов — норма). Затем боевой запуск: рендеры {TX}_{сцена}_timeline.wav ложатся в 01_Source/{сцена}/ рядом с оригиналами, ingest патчится. Так — локально на SSD; на Drive и в прокси-комплекте монтажёра в 01_Source живут только *_timeline.wav — оригиналы рекордера (*_orig_*.wav) уезжают в архив 99_Pipeline/DJI_Audio/{сцена}/. Обязателен финал «VERIFY OK: all placements on exact frame ticks» — иначе не продолжать.

2
Build Ingest

Перезагрузить панель (если код обновлялся) → вкладка Ingest → Build Ingest. В логе смотреть: warnings плана (не должно быть tx_td_*), «Placed N video + M TX», и READBACK-строки — любой «VANISHED»/«stray» = сборка плохая, в Premiere дальше делать нечего.

3
Debug Dump → verify

Панель → Debug Dump → verify_build_sync.py --dump …. PASS = этап закрыт, можно резать. FAIL печатает точный элемент и величину в мс — это и есть тикет на разбор, гадать не нужно.

3.4 · 5Сигналы и триаж

сигналчто значитчто делать
tx_unaligned_mediaпетличка кладётся из оригинальных WAV — 0113 не прогнан; синхрон деградирует до ≤½ кадра на слайспрогнать 0113 → Rebuild
tx_td_render_staleраскладка видео изменилась ПОСЛЕ рендера (finesync, offset'ы, пере-нарезка) — звук в рендере устарел, хотя позиции выглядят идеально0113 заново → Rebuild
tx_td_sources_changedwall_offset источника петлички в tx_strips_source отличается от запечённого в рендер0113 заново → Rebuild
tx_td_render_unverifiableрендер без отпечатка (старый 0113) — проверить свежесть невозможно0113 заново → Rebuild
READBACK: «items VANISHED»транзакция отчиталась ok, но элементов на дорожке нет (класс 90_CTA)лог сборки → повторный Build; если стабильно — разбор с дампом
READBACK: «stray items present»лишний элемент — обычно огрызок seed-плейсхолдера 1 кадр в нуле верхней V/A (зачистка в 25.6 нестабильна)удалить руками (верх V + его аудио); гейт продолжит напоминать
«Requires locked access» в логекод панели создаёт action вне lockedAccess — регресс билдерапанель обновить/чинить; тесты мока этот класс ловят
coverage gap в 0113рекордер выключился раньше видео — в рендере тишина, величина указанапрочитать и принять (или знать, где нет петли)
verify: position/source_in offPremiere положил не туда, куда просилисверить fps секвенции с планом (seed!), см. лог «sequence timebase»

3.4 · 6Критерий приёмки и история чисел

Годно: verify_build_sync.py → PASS (все элементы всех дорожек всех сцен в пределах 2 мс от плана; фактически — 0 тиков) и ни одного tx_td_*-warning'а в плане. Проверять ВСЕ сцены — чинить по одной и сломать соседнюю здесь уже нельзя, но правило остаётся.

значение
обещание fine-sync в числах (ingest)невязка ≤ 0.4 мс (24 пары, YTCH13)
на таймлайне ДО этапаостаток −31.6 … +19.9 мс, у каждого слайса свой; ступеньки V/A; +1 кадр на overwrite; пропавшие слайсы
на таймлайне ПОСЛЕ (живая сборка 17.08.2026)26/26 identity-слайсов тик-в-тик (0 расхождений по A-дорожкам петличек); контент ≤ 1 сэмпла (0.02 мс)

3.4 · 7Где лежит

рендер + патч ingest (шаг 1)999_extra/wordsync_multicam/0113_frame_align.py
гейт после сборки (шаг 3)999_extra/wordsync_multicam/verify_build_sync.py
офлайн-план = код панели0500_uxp/tools/plan_cli.js
grid-раскладка (чистая логика + тесты)0500_uxp/src/ingest/layout/sceneLayout.js · gridAlignPlan · tests/ingest/wallclock/
размещение (тики, lock, порядок)0500_uxp/src/ingest/placement/clipPlacer.js · wallClockBuilder.js
мок с реальным квантованием Premiere0500_uxp/tests/mocks/premierepro.js
шаги в конвейереdocs/wordsync/WORDSYNC_RUNBOOK.md — Шаг 4.6 + ГЕЙТ 6.5
полная история вопроса (постановка → решение){YTCH13}/HANDOFF_frame_shift.md
предыдущий слой (звуковая корреляция)/kb/fine-sync/
Синхрон — измерение, сборка — пересчёт из чисел, а доставка чисел в Premiere — отдельная инженерия: всё на сетку тиками, миллисекунды в медиа, после сборки — проверка числом. Ушам доверять нельзя: они слышат от ~1 кадра, гейт видит от 1 тика.