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

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

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

P="/Volumes/DISK/YTXXNN_Project"
V=~/YTAI/environment/.venv_transcribe/bin/python   # 0113 нужен soundfile — в системном python3 его нет
cd ~/YTAI/scripts/999_extra/wordsync_multicam

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

# 2. Маркеры говорящих — ПОСЛЕ 0113 (время маркера переводится через block_spans плана)
python3 ingest_markers.py --project "$P" --apply

# 3. Предполётная: план тем же кодом, что соберёт панель
node ~/YTAI/scripts/05_editing/0500_uxp/tools/plan_cli.js \
     --ingest "$P/00_Setup/01_Ingest/{CODE}_ingest.json" --out /tmp/plan.json
#    по каждой сцене: preset_ready: true · нет warnings tx_td_* / tx_unaligned_media

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

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

После ЛЮБОЙ правки ingest (finesync, Collect, дорожка экрана, пере-нарезка сцен) — заново 0113 → маркеры → Rebuild → verify. Гварды устаревания (ниже) не дадут забыть. ⚠️ python3 0113_frame_align.py системным интерпретатором падает на import soundfile — только через .venv_transcribe.

4.3 · 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 до фикса), одним сдвигом файла не лечится. Единственный сэмпл-точный носитель — сама медиа.

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

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

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

Секвенция — из пресета. В UXP нет API добавления дорожек, а удаление элементов в живом Premiere 25.6/26 молча не работает — поэтому прежний путь «посевной клип + заглушки прогрева» оставлял однокадровые огрызки на V1/A1 и на дорожках петличек. У измеренного ingest (scene_fps + audio_streams у каждого клипа камеры, пишет make_ingest.py) панель записывает .sqpreset и рождает из него секвенцию сразу с частотой сетки и точным числом дорожек: V по камерам, A — блок звука камер (по дорожке на звуковой поток, у DJI Pocket два) плюс петлички над ним. Сетка = scene_fps: 50p, только если вся сцена 50p. Неизмеренный ingest строится прежним путём от посева — лог называет путь каждой сцены, plan_cli.js показывает его заранее (preset_ready / preset_missing).

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

0113_frame_align.py рендерит на каждую (сцену × петличку) один WAV, где время файла = финальное время таймлайна: контент каждого слайса лежит ровно на том сэмпле, где слайс встанет (пер-слайсная компенсация из content_src_sec плана). Билдер кладёт identity-слайсы (offset == source-in, оба на тиках) — Premiere нечего округлять. Синхрон ≤ 1 сэмпла. Пути исходных полос сохраняются в tx_strips_source, пересборка читает их оттуда: полевой день — полосы в 01_Source/{сцена}/Sound/, студийный — срезы уроков {CODE}_{урок}_TXnn_timeline.wav в Sound/ урока; целые записи рекордера живут в 99_Pipeline/DJI_Audio/. Бэкап 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.

⚠
Рендер петлички — обычный файл: чинится без Premiere

*_timeline.wav — это просто WAV, где время файла = время таймлайна. Если звук разъехался, пересобирать Build не нужно: файл пересобирается offline по замеру в стейджинг и подменяется на месте, оригиналы уходят в бэкап. Так чинили YTUVIE01 25.09 (relav2.py, сегмент = дубль): 28 730 → 31 мс, 322 → 26 мс, 248 → 22 мс. Пересборка Build, наоборот, стирает клиповые эффекты — поэтому порядок навсегда синхрон → цвет.

формат рендера   pcm_f32le, моно        ← брать С ИСТОЧНИКА, не выбирать
pcm_s24le        НЕЛЬЗЯ                 ← срежет всё выше 0 dBFS:
                                          во float это законный сигнал,
                                          у двух файлов YTUVIE01 пик ровно в нуле

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

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

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

2
Build Ingest

Перезагрузить панель (если код обновлялся) → вкладка Ingest → отметить сцены → Build (Rebuild сам удалит прежние секвенции отмеченных сцен). В логе смотреть: путь сборки (sequence from preset, а не fallback seed+pre-warm), warnings плана (не должно быть tx_td_*), «Placed N video + M TX», и READBACK-строки — любой «VANISHED»/«stray» валит сборку, в Premiere дальше делать нечего. Затем Verify Sync — звук в кадрах (4.4).

3
Debug Dump → verify

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

4.3 · 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»лишний элемент — огрызок посева/прогрева 1 кадр (V1/A1 в нуле, хвосты дорожек петличек); бывает только у сборки от посева: удаление в живой 25.6/26 не срабатываетдозамерить ingest (make_ingest.py --probe-audio --probe-fps --ingest …) → Rebuild из пресета; или удалить руками
preset_missing в плане / «fallback seed+pre-warm» в логеingest не измерен: нет scene_fps или audio_streams у клипов — секвенция родится от посева, с огрызками и одной A-дорожкой на камеруmake_ingest.py --probe-audio --probe-fps --ingest … → 0113 → Rebuild
«Requires locked access» в логекод панели создаёт action вне lockedAccess — регресс билдерапанель обновить/чинить; тесты мока этот класс ловят
coverage gap в 0113рекордер выключился раньше видео — в рендере тишина, величина указанапрочитать и принять (или знать, где нет петли)
verify: position/source_in offPremiere положил не туда, куда просилисверить fps секвенции с планом (seed!), см. лог «sequence timebase»

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

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

⚠️ PASS значит «легло по плану», а не «звучит синхронно»: план считает тот же планировщик, что и сборку. На YTUVIE01 сверка с планом (prproj_verify.py) дала «сошлось 21 из 21» при петличке на 28,7 с мимо. После PASS — замер по звуку, 4.4.

значение
обещание 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 мс)

4.3 · 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
секвенция из пресета0500_uxp/src/ingest/placement/presetFile.js · sequencePreset.js
маркеры говорящих (после 0113)999_extra/wordsync_multicam/ingest_markers.py · marker_titles.py
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 тика. Дальше — проверка звуком (4.4 Sync Verification), затем этап 5 Editing.