[
 {
  "level": "macro",
  "domain": "syllable budget",
  "rule": "Держите число спетых слогов украинского текста в пределах ±5 % от числа спетых слогов оригинальной песни, считая гласные (после снятия акута) в том performance-тексте, который gen2 реально отправляет.",
  "why": "Все три версии поются с одинаковой скоростью (~191-205 слог/мин), поэтому длина — это объём текста, а не темп: каждые ~32 лишних слога стоят ~10 с. Оригинал ≈625-645 слогов → 188.6 с; Taras 620-626 → 193.9 с; наш спетый текст 766-770 → 239.4 с (+45 с). Скилл запрещает обязательный процент расширения, но не задаёт потолка; DUR не рычаг, потому что cover-эндпоинт его не принимает.",
  "evidence": [
   "measurements/summary.json: syllables 645/620/762, syllablesPerMin 205.2/191.8/191.0, sungSpan end 188.56/193.94/239.4; film.sungTextSyllables 770 (=766 vowels + 2×2 for 'go pure')",
   "lyrics/lyrics-alignment.md §3 section totals; lyrics/lyrics-songcraft.md §1 ('length is a text-volume problem')",
   "song-v02/stress/lyrics-with-stress-v04.txt re-counted: 98 phrases, 766 vowels"
  ],
  "placement": [
   ".agents/skills/song-clone-ua/SKILL.md → Стадия 2 'Бюджет строки' (L238-240, рядом с 'нет обязательного процента расширения') → добавить глобальный потолок ±5 % от спетых слогов оригинала",
   "references/adapt-phase-contract.md → шаг 4 'Песенный текст', колонка проверки (L27) → строка 'слоги спетого текста vs оригинал, ±5 %'",
   "tools/song_budget.py (новый) → вызывается из scripts/gen2.py song_fields() (L55) на файле LYRICS → fail выше +5 % без versioned director-decision с причиной",
   "AGENTS.md → правило 18 (L136) → одна фраза: бюджет слогов = бюджет оригинала"
  ],
  "statusGuess": "strengthen",
  "lens": "songwriter"
 },
 {
  "level": "macro",
  "domain": "section clock / drift",
  "rule": "Не превышайте 1,15× слогов оригинала в каждой секции, а любой перерасход возвращайте в другой секции так, чтобы конец вокала лежал не позже +6 с от оригинала, и фиксируйте карту дрейфа по карточкам.",
  "why": "Taras тратит +14 с на сцену в магазине и возвращает их в машине (0.72×), у наставницы (0.91×), в финале (0.93×) и CTA (0.68×): пик дрейфа +18.2 с на карточке 23, финал +5.4 с. Мы перерасходуем только там, где добавлен продуктовый текст (наставница +31 слог/+11 с, результаты +39/+10 с, CTA +25/+7 с; коллапс 1.62×) и никогда не возвращаем: +50.9 с монотонно.",
  "evidence": [
   "lyrics/lyrics-alignment.md §6 drift row: Taras 0/+5.2/…/+18.2 (card 23)/…/+5.4; ours 0/+2.5/+5.1/+12.4/…/+50.9",
   "lyrics/lyrics-songcraft.md §1 section table: mentor 118/38.2 s · 131/34.9 · 149/49.2; results 88/27.2 · 86/28.0 · 127/37.0; CTA 15/5.9 · 14/4.0 · 40/12.7",
   "film/line-anchors.json masterVocalTime vs sourceTime"
  ],
  "placement": [
   "musical-phrasing-plan-vNN.json (per adaptation) → densityAndDuration → сделать по секциям: source syl, source s, new syl, planned s, offsetting cut",
   "references/adapt-phase-contract.md → шаг 4 и шаг 7 'song→visual timing map' → обязательная кривая дрейфа с возвратом ≤ +6 с к CTA",
   "tools/song_budget.py --sections → печать ratio по секциям, fail > 1.15 без названного компенсирующего сокращения"
  ],
  "statusGuess": "new",
  "lens": "songwriter"
 },
 {
  "level": "macro",
  "domain": "phrasing",
  "rule": "Пишите музыкальными фразами, а не карточками субтитров: целевое число спетых фраз ≈ 0,6-0,7 от числа дыхательных фраз оригинала (его ASR-сегментов), сливая две-три карточки в одну фразу на одном дыхании, где и выпадают связки.",
  "why": "Taras: 43 фразы (медиана 14 слогов) на 65 карточек — слияния 1+2, 9+10+11, 15+16, 20+21, 23+24, 42+43+44, 47+48, 53+54+55, 57+58, 59+60+61, 63+64. Мы: line-anchors 65/65 строк по карточкам, затем 98 фраз / 36 мыслей — больше внутренних разрывов и больше сквозных дуг; карточка 10 (4.6 с в оригинале) стала тремя фразами 28.4-35.5 с (7.1 с). AGENTS 18 уже говорит, что строки не диктуют границы, но инструменты ключуют всё 1:1 к sourceCaptionId.",
  "evidence": [
   "taras/asr.json: 43 segments; targ/asr.json: 75 segments; film/asr.json: 70 segments (98 planned phrases in song-v02/lyrics-phrases-v06.json)",
   "film/asr.json 28.4-30.5 'За півроку постаріла' / 30.5-32.8 'Мов на п'ять років' / 32.8-35.5 'Стрес і сльози щоночі' vs targ 24.7-31.0",
   "lyrics/lyrics-alignment.md §7; lyrics/lyrics-advertorial.md §9; film/line-anchors.json"
  ],
  "placement": [
   ".agents/skills/song-clone-ua/SKILL.md → 'Что считать строкой' (L176) + Стадия 2 'Полная мысль и музыкальная фраза' (L230) → целевое число фраз = ASR-фразы оригинала × 0.6-0.7",
   "references/adapt-phase-contract.md → шаг 4 output lyrics_uk.txt → одна строка = одно дыхание, не одна карточка",
   "tools/targ_film_prepare.py → captionAnchors → потреблять спетые фразы, а не карточки"
  ],
  "statusGuess": "strengthen",
  "lens": "songwriter"
 },
 {
  "level": "macro",
  "domain": "breath / arrangement",
  "rule": "Впишите воздух в текст: инструментальные паузы 1-2 с на сильнейших ударах оригинала (не менее одной паузы > 1 с на каждый акт оригинала), и принимайте только дубль, чей вокальный стем показывает суммарное время пауз ≥ оригинала и долю озвученных кадров ≤ оригинала.",
  "why": "Taras отдыхает 19.9 с (10 % хронометража; 5 пауз > 1 с: 21.1 перед 'Вона з тих', 58.3 внутри скана, 64.5 перед 'Потім вона усміхнулась', 148.9 после 'майже не було', 188.7 перед брендом), и картинка использует эти холды (taras-S17 ECU глаз новой жены, taras-S35 рука на щеке). У нас 0 пауз > 1 с, 8.7 с отдыха всего, 42.8 с / 133 слога без разрыва (81-124 с). Паузы стоят ноль слогов и делают текст песней.",
  "evidence": [
   "measurements/summary.json: gapsOver1s targ 1 (91.88), taras 5 (21.14, 58.32, 64.52, 148.92, 188.74), film 0; voicedFrameFraction 0.708/0.577/0.787",
   "lyrics/lyrics-songcraft.md §1 rests 16/11.1 s · 28/19.9 s · 18/8.7 s; §2.3",
   "ledgers/taras-seg2.json taras-S17 56.25-60.65 ('~1.7 s without vocal'); ledgers/taras-seg4.json taras-S35 147.17-152.33; frames taras/shotframes/s017-mid.jpg, s035-mid.jpg",
   "ASR gaps ≥ 0.5 s: targ 5 (3.7 s), taras 8 (9.8 s), film 1 (0.9 s)"
  ],
  "placement": [
   ".agents/skills/song-clone-ua/SKILL.md → Стадия 2 → musical-phrasing-plan получает rests[] (source time, beat, reason); пустая строка/бракетированная ремарка в lyrics_uk.txt на каждой паузе; Стадия 3 STYLE-шаблон → 'leave instrumental breaths between phrases'",
   "scripts/critic_music.py → Стадия 4 отбор → новые advisory-оси rest_gaps, voiced_fraction (сравнение с оригиналом)",
   "references/ukrainian-stress-contract.md → 'Stress changes the composition' (L41-51) → поле breath boundary становится обязательным для каждой фразы"
  ],
  "statusGuess": "new",
  "lens": "songwriter"
 },
 {
  "level": "macro",
  "domain": "music generation / cover defaults",
  "rule": "Используйте загруженный оригинал как часы и фразовый контейнер (клок фраз, регистр подачи, дуга энергии), а совпадение тональности/темпа/тембра делайте совещательным критерием под versioned-решением, а не ключевым гейтом критика.",
  "why": "Taras — не музыкальный клон: A minor против F major, лучшая транспозиция 10 полутонов, chroma-DTW без транспозиции 0.0771 = reversed baseline 0.0775 (после транспозиции 0.0687, слабая связь), темп 152/76 с локальными перескоками — и владелец предпочитает его. Наш — настоящий клон (103.4 BPM, транспозиция 0, 0.0518 < 0.0572), сделанный cover с AW 0.85/SW 0.65/WC 0.2 и STYLE 'Closely follow uploaded original timbre'. critic_music считает tempo = 1−|Δbpm|/6 и держит intro_mode в KEY, так что песню класса Taras наш критик забраковал бы. Правило не отменяет обязательную загрузку своего оригинала (AGENTS 2/16), а меняет, что мы просим скопировать.",
  "evidence": [
   "measurements/music-similarity.json: taras chromaDtwCostVsOriginal 0.0771, reversed baseline 0.0775, bestTransposition {10, 0.0687}; film 0.0518 / 0.0572 / {0, 0.0518}",
   "measurements/summary.json: keyTop F major 0.78 / A minor 0.83 / C-F major; tempoLibrosa 103.4/152/103.4; taras tempoFrameP10P90 103.4-161.5",
   "scripts/critic_music.py L23-24 (KEY = timbre, cohere, intro_mode, intro_start), L199 tempo score",
   "song-v02/jobs/targ_r2_v6_cover03-request.json (audioWeight 0.85, styleWeight 0.65, weirdness 0.2); song-v02/style-v04.txt"
  ],
  "placement": [
   ".agents/skills/song-clone-ua/SKILL.md → Стадия 3 STYLE-шаблон (L278-293) → заменить жёсткие '<BPM> bpm, <key>' на 'phrase clock of the original, spoken-sung storytelling, <groove> per decision'; AW/SW/WC выбираются с письменной причиной",
   "scripts/critic_music.py → KEY (L24) → вывести tempo/intro_mode из KEY, когда director-decision-cover-vNN.json фиксирует осознанную смену грува",
   "generation_contracts.md → шаг 6 (L44) 'жанр, подача' → 'фразовый ритм, подача, дуга энергии'",
   "references/adapt-phase-contract.md → 'Музыкальная проверка' (L43) → tempo/key 'с неопределённостью', метрики контейнера становятся гейтами"
  ],
  "statusGuess": "conflict",
  "lens": "songwriter"
 },
 {
  "level": "macro",
  "domain": "vocal delivery",
  "rule": "Просите в STYLE разговорно-спетое повествование (женщина рассказывает подруге) и гейтуйте дубль по числам оригинала: диапазон P5-P95 ≥ оригинала − 1 полутон, std громкости вокала ≥ оригинала, доля кадров 'в ноте' ≤ оригинала + 0,03.",
  "why": "Taras: on-note 0.73, диапазон 15.1 st, RMS std 6.57 dB; оригинал 0.799 / 14.1 / 5.89; наш 0.823 / 12.5 / 5.63 — самый 'спетый' и самый плоский из трёх. Наш STYLE прямо просил 'expressive rising/falling melody … never flat spoken prose'. Владельческое 'милозвучна … саме піснею' Taras выполняет говорящей подачей, а не большей мелодичностью.",
  "evidence": [
   "measurements/summary.json: vocalOnNoteFraction 0.799/0.73/0.823; rangeP5P95Semitones 14.1/15.1/12.5; vocalRmsDbStd 5.89/6.57/5.63; noteP5P50P95 G3-E4-A4 / A3-C#4-C5 / A3-D#4-A4",
   "song-v02/style-v04.txt ('expressive rising/falling melody … no spoken prose')",
   "references/owner-generation-rules.md advertorial-to-song section"
  ],
  "placement": [
   ".agents/skills/song-clone-ua/SKILL.md → Стадия 3 STYLE-шаблон → 'conversational sung storytelling; whisper-level low point in the car, lift at the mirror'",
   ". agents/skills/song-clone-ua/SKILL.md → Стадия 4 таблица приёмки (L299) → четыре числа подачи из audio-dna.json рядом с LUFS/BPM",
   "references/adapt-phase-contract.md → 'Музыкальная проверка' → допуски 'диапазон, подача' относительно оригинала"
  ],
  "statusGuess": "new",
  "lens": "songwriter"
 },
 {
  "level": "macro",
  "domain": "CTA",
  "rule": "Спетый CTA — один вдох: не длиннее CTA оригинала по слогам (+1) и по секундам, одно предложение, бренд назван один раз во всём спетом тексте, конец на «посилання нижче/внизу»; дисклеймер и инструкция формы живут на энд-карте или в разговорном теге, никогда в мелодии.",
  "why": "Оригинал 'The cream is called Seora / The link's below / If you need it' 15 слогов / 5.9 с; Taras 'Крем називається Ханбіт, посилання нижче' 14 слогов / 4.0 с (189.9-193.9), бренд один раз, без субтитра, энд-карта несёт четыре чека + 'ПОСИЛАННЯ НИЖЧЕ'. Наш: 'go pure — це крем для шиї. Результат у кожної свій. / Посилання внизу. У формі — ім'я й телефон лиши, / Якщо такий догляд потрібен тобі.' 40-42 слога / 12.7 с (226.7-239.4), бренд дважды (139.2, 226.7), дисклеймер и инструкция формы поются над презентерским планом.",
  "evidence": [
   "targ/asr.json 181.6-188.6 (ledger targ-S42 word timing 182.64-188.56); taras/asr.json 189.9-193.9; film/asr.json 226.7-230.0 / 230.0-233.7 / 233.7-236.2 / 236.2-239.4",
   "lyrics/lyrics-alignment.md rows 63-65: syl O/T/U 6/8/15, 4/6/16, 4/0/11",
   "ledgers/taras-seg4.json taras-S43 184.21-188.42, taras-S44 188.42-194.17; ledgers/film-seg4.json film-S36 226.57-235.2 (pointing at jar), film-S37 235.2-239.7",
   "lyrics/lyrics-songcraft.md §2.10; lyrics/lyrics-advertorial.md §6-7"
  ],
  "placement": [
   ".agents/skills/song-clone-ua/SKILL.md → 'Гейт бренда' (L108) → пункт: бренд поётся один раз, в CTA; Стадия 2 → бюджет CTA = CTA оригинала",
   "references/adapt-phase-contract.md → шаг 4 'слышимый CTA' → 'CTA ≤ слогов оригинала +1, один вдох'",
   "tools/song_budget.py --cta → fail при превышении и при brand tokens ≠ 1",
   "references/film-quality-meta-rules.md → F12 (L125) / film handoff-endcard-director-decision → дисклеймер и текст формы = on-screen copy"
  ],
  "statusGuess": "new",
  "lens": "songwriter"
 },
 {
  "level": "macro",
  "domain": "product mechanism / mentor block",
  "rule": "Сохраняйте в блоке наставницы два коротких императива оригинала («стоп, всё это только хуже» и «остальное выбрось») плюс одну ритуальную строку; детали применения и текст этикетки показывайте картинкой, а не пойте.",
  "why": "Оригинал: 'She said stop, all of that is making it worse' → 'Use this every night / Throw the rest aside'. Taras: 'Вона сказала, зупинися, все це робить тільки гірше' (114.2-117.7), 'наноси щовечора' (121.0-125.4), 'Решту викинь' (4 слога, 1.8 с, 125.4-127.2). У нас нет бита 'стоп' (карточка 34 → 'А шия? Їй щодня потрібен власний догляд' 132.2-136.7) и нет 'выбрось' (карточка 38 → 'Легенько знизу вгору / Без обіцянок чудес' 146.2-150.8); героиня читает этикетку 139.2-150.8. Бит-разворот оригинала — бросить прежнее — исчез.",
  "evidence": [
   "targ/asr.json 105.1-108.0, 114.1-116.4, 116.4-118.8; taras/asr.json 114.2-117.7, 121.0-125.4, 125.4-127.2",
   "film/asr.json 132.2-136.7, 139.2-141.6 'Go pure, я стала читати', 141.6-150.8 (four label segments)",
   "lyrics/lyrics-alignment.md §1 cards 33-38, §5, §8; lyrics/lyrics-songcraft.md §2.9"
  ],
  "placement": [
   "references/adapt-phase-contract.md → шаг 3 adaptation-map → именованные функции stop-verdict и discard-rest со своими строками, каждая ≤ 6 слогов",
   ".agents/skills/song-clone-ua/SKILL.md → Стадия 1 архетип 'Личная история' (L206) + Стадия 2 → бит-разворот назван явно",
   "references/problem-passport-contract.md → состояние application → ритуал показывается визуально (F11 'действие, а не постер')"
  ],
  "statusGuess": "new",
  "lens": "songwriter"
 },
 {
  "level": "micro",
  "domain": "concreteness / numbers",
  "rule": "Сохраняйте каждое число оригинала числительным (столько же числовых якорей, сколько в оригинале), не добавляйте своих и никогда не заменяйте число на «кілька».",
  "why": "Оригинал — 12 числовых якорей; Taras — 11 (выпало только 'fifteen'); у нас 11 токенов, но два — 'кілька' ('Минуло кілька тижнів' 155.5 с вместо 'Three weeks later'; 'через кілька місць' 194.1 с), а 'week six', 'a decade', 'no foundation' исчезли — секция результатов осталась без таймлайна. Числа — самая дешёвая конкретность, и стресс-гейт уже вынуждает писать их словами.",
  "evidence": [
   "film/asr.json 155.5-157.6 'Минуло кілька тижнів', 194.1-198.6 'через кілька місць'; taras/asr.json 131.7-136.4 'Через три тижні', 144.5-148.9 'На шостому тижні', 150.7-155.2 'десять років без тонального'",
   "lyrics/lyrics-songcraft.md §2.6, §2.11; lyrics/lyrics-alignment.md cards 40, 45, 47-48, 51",
   "tools/song_stress.py L36 tokens() rejects digits; scripts/lines_check.py L17-21 norm() maps only 16/34/60"
  ],
  "placement": [
   "references/adapt-phase-contract.md → шаг 3 map → numericAnchors[] (preserve/replace/reason + product-truth fact ID); неподтверждённое число → поведенческий якорь, не размытое слово",
   ".agents/skills/song-clone-ua/SKILL.md → Стадия 2 чеклист → 'числа оригинала сохранены'",
   "tools/song_budget.py --numbers → fail на 'кілька' там, где карта содержит число; scripts/lines_check.py norm → расширить карту чисел"
  ],
  "statusGuess": "new",
  "lens": "songwriter"
 },
 {
  "level": "micro",
  "domain": "motifs / refrains",
  "rule": "Сохраняйте опорные слова, рефрены и коллбэки оригинала одним и тем же украинским словом при каждом повторе — не менее N−1 из N вхождений оригинала.",
  "why": "'Then' ×5 → Taras 'Потім' ×4 (16.7, 48.1, 66.4, 155.2), у нас 0; 'will do that' ×3 → Taras 'стрес це робить, сльози щоночі це роблять' (2 из 3), у нас 0; 'her opinion mattered / mine finally did' → Taras 'її думка щось важить / моя нарешті важить' (тот же глагол, 178.0-188.7), у нас 'Її думка — не головна / Бо нарешті я ціную…' (параллель сломана); коллбэк машины: Taras 'машини' 79.4 → 'машині' 176.0, у нас 'авто' 80.7 → 'машині' 215.5. Taras держит 5 из 7 движков повтора, мы — 2.",
  "evidence": [
   "taras/asr.json: 'Потім' at 16.7, 48.1, 66.4, 155.2; 'сказала' ×4; 178.0-184.0 / 184.0-188.7 'важить' ×2",
   "film/asr.json: 'Потім' 0, 'сказала' 1 (217.9); 217.9-226.7 'її думка не головна / Бо нарешті я ціную те, що думаю сама'",
   "lyrics/lyrics-songcraft.md §2.8 motif table"
  ],
  "placement": [
   "references/adapt-phase-contract.md → шаг 3 map → секция motifs[] (source word, occurrences, positions, adapted word, occurrences ≥ N−1, identical each time)",
   ".agents/skills/song-clone-ua/SKILL.md → Стадия 2 'Рифма, созвучия и повторы' (L251) → сделать проверяемым",
   "tools/song_budget.py --motifs → отчёт по каждому мотиву"
  ],
  "statusGuess": "strengthen",
  "lens": "songwriter"
 },
 {
  "level": "macro",
  "domain": "register balance",
  "rule": "Держите число продуктовых токенов в спетом тексте (зона, «догляд», «крем», бренд) не выше числа повторяющихся слов персонажа/истории и не выше числа продуктовых упоминаний оригинала ×1,2.",
  "why": "У нас 'шия/шию/шиї' 10 + 'догляд' 4 + 'go pure' 2 + 'крем' ≈ 20 продуктовых токенов против ≈ 8 сюжетных мотивов; Taras 'крем' 2 + 'Ханбіт' 1 + 'плями' 2 + 'тюбик' 1 ≈ 6 против ≈ 16 сюжетных ('потім', 'сказала', 'робить', 'сиділа', 'злилась', 'важить'…). Мотив продукта заменил мотив персонажа.",
  "evidence": [
   "song-v02/stress/lyrics-with-stress-v04.txt re-count: шия-forms 10, догляд 4, go pure 2, крем 5",
   "taras/asr.json: крем 2, Ханбіт 1, Потім 4, сказала 4",
   "lyrics/lyrics-songcraft.md §2.8 (ours 'шия' ×10, product motif 13 occurrences)"
  ],
  "placement": [
   "tools/song_budget.py --motifs → печать ratio product/story, fail если product > story",
   ".agents/skills/song-clone-ua/SKILL.md → Стадия 2 → пункт 'мотив персонажа ≥ мотива продукта'",
   "references/adapt-phase-contract.md → шаг 4 validation column"
  ],
  "statusGuess": "new",
  "lens": "songwriter"
 },
 {
  "level": "macro",
  "domain": "process / measurement",
  "rule": "Запускайте все бюджетные и линт-гейты на performance-тексте, который gen2 реально отправляет (stressed lyric, привязанный к ledger), и показывайте в отчёте прозовый сценарий и спетый текст рядом с раздельными счётчиками.",
  "why": "lyrics_uk-v07.txt (прозовая стадия) содержит 843-847 слогов; спетый lyrics-with-stress-v04.txt — 766 (770 с брендом) и отличается в 62 из 65 строк; summary.json первоначально считал lyricSyllablesFromText на прозе. Бюджет, измеренный на прозе, бессмыслен; gen2 уже сверяет SHA промпта с ledger.",
  "evidence": [
   "measurements/summary.json film: sungTextSyllables 770, proseScriptSyllables 847, sungTextSource note ('differs in 62/65 lines')",
   "lyrics/lyrics-alignment.md §0 row 'Ours words + time'",
   "scripts/gen2.py L101-108 stress_fields() (actualPromptSha256 check); references/owner-approved-adapt-baseline.md L51 ('Show the requested lyric and actual accepted performed text separately')"
  ],
  "placement": [
   "scripts/gen2.py → song_fields() (L55) → вызов tools/song_budget.py на файле LYRICS перед POST",
   "ADAPT report (build_adapt.py / шаг 8) → печать обоих счётчиков (проза vs спетый)",
   "references/adapt-phase-contract.md → шаг 4 output column → 'бюджет измерен на спетом тексте'"
  ],
  "statusGuess": "strengthen",
  "lens": "songwriter"
 },
 {
  "level": "micro",
  "domain": "register / tense",
  "rule": "Ведите повествование в простом прошедшем времени, как оригинал; настоящее время допускается только внутри прямой речи или в хабитуальных утверждениях («бачу щодня»).",
  "why": "У нас 'Стою, дивлюся на крем' (48.9-50.8), 'Знаю вона' (59.1), 'Її погляд ковзає вниз' (62.7-64.6), 'Кажу, креми для обличчя' (123.4-126.2). Taras: 'Я стояла там у суботу' (35.4), 'Потім почула її голос' (48.1), 'Вона подивилась … Спитала' (104.6-111.5); единственное настоящее — хабитуальное 'бачу щодня'. Оригинал весь в прошедшем.",
  "evidence": [
   "film/asr.json 48.9-50.8, 59.1-62.7, 62.7-64.6, 123.4-126.2",
   "taras/asr.json 35.4-40.9, 48.1-53.1, 104.6-108.3, 108.3-111.5",
   "lyrics/lyrics-songcraft.md §2.5 Tense"
  ],
  "placement": [
   ".agents/skills/song-clone-ua/SKILL.md → Стадия 2 чеклист → 'нарратив в прошедшем времени'",
   "tools/song_budget.py --register → список глаголов настоящего времени в нарративе (стою, дивлюся, бачу, ковзає, кажу, каже) → WARN вне «» и без щодня/завжди",
   "references/adapt-phase-contract.md → шаг 4 validation"
  ],
  "statusGuess": "new",
  "lens": "songwriter"
 },
 {
  "level": "micro",
  "domain": "stress-to-beat",
  "rule": "Ставьте первый ударный слог фразы на 1-й или 2-й слог (не более одного безударного затакта), а последнее слово фразы делайте знаменательным с ударением на предпоследнем/предпредпоследнем слоге, если мужское окончание не задумано специально.",
  "why": "10 из наших 98 спетых фраз открываются ≥ 2 безударными затактами ('У суботу зайшла', 'Із сусіднього ряду', 'Від обличчя до шиї', 'Результат у кожної свій', 'Посилання внизу'), которые певица проглатывает или ложно акцентирует (аудит слышал 'но́ву' вместо 'нову́'). Taras открывает на Я / Вона / Потім / Лоб / Плями / Донька / Крем и заканчивает дактилически ('бе́резень', 'зчи́тано', 'щове́чора'); наши окончания masc/fem/dact 16/42/7 против Taras 9/23/11.",
  "evidence": [
   "song-v02/stress/lyrics-with-stress-v04.txt: 'У суботу зайшла до крамниці.', 'Із сусіднього ряду — голос.', 'Від обличчя до шиї.', 'Результат у кожної свій.', 'Посилання внизу.'",
   "lyrics/lyrics-songcraft.md §1 phrase-final stress 9/23/11 vs 16/42/7; §2.2",
   "taras/asr.json openings at 16.7, 22.2, 48.1, 60.1, 66.4, 136.4, 138.4, 189.9"
  ],
  "placement": [
   "references/ukrainian-stress-contract.md → 'Stress changes the composition' (L41-51) → добавить тест затакта и конца строки в строку musical phrasing plan",
   "tools/song_stress.py → парсер акутов (L36 tokens) → WARN на фразу: pickups ≥ 2; финальное слово ≥ 5 слогов с непредпоследним ударением",
   ".agents/skills/song-clone-ua/SKILL.md → Стадия 2"
  ],
  "statusGuess": "strengthen",
  "lens": "songwriter"
 },
 {
  "level": "micro",
  "domain": "syntax / punctuation",
  "rule": "Держите бюджет пунктуации как в карточках оригинала (они без знаков): на 10 фраз ≤ 2 тире, ≤ 1 пара кавычек, 0 двоеточий перед прямой речью; прямой порядок слов; прямая речь = прошедшее «сказала/спитала» + реплика ≤ 6 слогов.",
  "why": "У нас 21 тире, 8 '«', 8 двоеточий, 73 точки (5.3 слова на предложение), телеграфные фрагменты 'Із сусіднього ряду — голос.' (56.0), 'У червні — концерт.' (187.8), 'Її думка — не головна.' (217.9), инверсии 'Андрія нова дружина' (194.1), 'Я крем нанесла' (150.8). Taras: 0 тире, 0 кавычек, запятые между разговорными клаузами; 'Спитала, а ти чим?' (108.3), 'Вона сказала, зупинися' (114.2), 'Донька сказала, мам, ти якась інша' (138.4).",
  "evidence": [
   "song-v02/stress/lyrics-with-stress-v04.txt re-count: dashes 21, « 8, colons 8 (98 phrases)",
   "lyrics/lyrics-songcraft.md §1 punctuation row (Taras 0/0/4), §2.5",
   "film/asr.json 56.0, 150.8, 187.8, 194.1, 217.9; taras/asr.json 108.3, 114.2, 138.4"
  ],
  "placement": [
   "tools/song_budget.py --punctuation → на plain lyric, fail выше бюджета",
   ".agents/skills/song-clone-ua/SKILL.md → Стадия 2 'Полная мысль и музыкальная фраза' (L230) → правило пунктуации и SVO",
   "cross-lens: caption cards наследуют грамматику без пунктуации (targ_film_prepare captionAnchors)"
  ],
  "statusGuess": "new",
  "lens": "songwriter"
 },
 {
  "level": "micro",
  "domain": "register / lexicon",
  "rule": "Используйте в повествовании бытовые существительные и глаголы регистра «жінка розповідає подрузі», как оригинал, и ведите список запрещённых печатно-рекламных слов с заменами («авто»→«машина», «кохав»→«любив», «лиш»→«тільки», «мов»→«наче», «опора»→«тримала дітей»).",
  "why": "Один бит, два регистра: 'Я пішла до авто. П'ятнадцять хвилин сиділа.' (80.7-87.2) vs Taras 'Я дійшла до машини і просто сиділа' (79.4-83.6); 'Ніби я — лиш бліда тінь жінки, яку її чоловік кохав першою' (75.0-80.7) vs 'Наче дивилась на стару версію жінки, яку її чоловік любив першою' (72.2-79.4); 'Я крем нанесла — легко ковзає, зволожує шкіру' (150.8-155.5) vs 'Я вичавила краплю, не пекло, не лущило' (127.2-131.7); 'Була дітям опорою' (35.5).",
  "evidence": [
   "film/asr.json 35.5-38.8, 75.0-80.7, 80.7-87.2, 150.8-155.5",
   "taras/asr.json 72.2-79.4, 79.4-83.6, 127.2-131.7",
   "lyrics/lyrics-songcraft.md §2.4 register table"
  ],
  "placement": [
   "references/song-register-uk.md (новый, рядом с references/pronunciation-traps.md) → список слов + замены, расширяемый по продукту",
   "tools/song_budget.py --register → WARN на слова из списка",
   ".agents/skills/song-clone-ua/SKILL.md → Стадия 2 абзац 'Ловушки произношения' (L275) → соседний абзац 'Ловушки регистра'"
  ],
  "statusGuess": "new",
  "lens": "songwriter"
 },
 {
  "level": "micro",
  "domain": "singability",
  "rule": "Не допускайте клитических кластеров («й»+согласный, «та ж», «із с-») и слов ≥ 5 слогов с непредпоследним ударением в конце фразы; длинные слова ставьте в середину фразы, как это делает разговорная речь.",
  "why": "У нас 'Та ж жінка' (211.4), 'ім'я й телефон лиши' (233.7), 'І гелі, й маски перепробувала' (127.0-132.2), 'Із сусіднього ряду' (56.0), 'перепробувала' дважды как конец строки (53.5-56.0, 127.0-132.2), 'користуєшся' (117.7-123.2). У Taras длинных слов больше (8.3 % ≥ 4 слогов), но они стоят в середине фразы ('називається', 'користується' 100.1-104.6); короткие панчлайны открыто-гласные ('Решту викинь').",
  "evidence": [
   "film/asr.json 53.5-56.0, 56.0-59.1, 117.7-123.2, 127.0-132.2, 211.4-215.5, 233.7-236.2",
   "taras/asr.json 100.1-104.6, 125.4-127.2",
   "lyrics/lyrics-songcraft.md §2.1; references/ukrainian-stress-contract.md L30, L69-73 (clitic records)"
  ],
  "placement": [
   "tools/song_stress.py → классификация clitic/vowelless (L102-107) → WARN на смежность й/ж/з-кластеров и на длинное слово в конце строки",
   ".agents/skills/song-clone-ua/SKILL.md → Стадия 2 → пункт про кластеры и концы строк"
  ],
  "statusGuess": "new",
  "lens": "songwriter"
 },
 {
  "level": "macro",
  "domain": "density map",
  "rule": "Сохраняйте профиль плотности оригинала: карточку, которую оригинал поёт на одном дыхании при ≥ 4 слог/с, оставляйте одним дыханием, а замедления покупайте паузами, не дополнительными клаузами.",
  "why": "Оригинал варьирует от 2.37 слог/с (CTA) до 4.24 (коллапс); Taras варьирует 2.97-3.87 и сдвигает карту (медленно на скане, быстро на списке наставницы 111.5-117.7); у нас плоско 2.99-3.44 везде. Самую быструю секцию оригинала (коллапс, карточки 9-14) мы замедлили сильнее всего (1.62×): карточка 10 4.6 с → 7.1 с в трёх фразах (28.4-35.5), карточка 13 3.2 с → 6.9 с (43.9-50.8). При плоской скорости длину каждой секции задаёт текст, а не певица.",
  "evidence": [
   "lyrics/lyrics-alignment.md §3 syl/s columns, §6 'Density profile'; Appendix A/B syl/s",
   "film/asr.json 28.4-35.5 (three phrases), 43.9-50.8; targ/asr.json 24.7-31.0, 33.0-39.1",
   "measurements/phase1-digest.md L209 (collapse 4.24 syl/s, ours flat 2.99-3.44)"
  ],
  "placement": [
   "musical-phrasing-plan-vNN.json → structuralDevelopment[] → поля sourceSylPerSec и plannedSylPerSec на строку",
   ".agents/skills/song-clone-ua/SKILL.md → Стадия 2 'Длина против смысла' (L253) → правило 'спринт остаётся спринтом'",
   "tools/song_budget.py --sections → плотность по секциям vs оригинал"
  ],
  "statusGuess": "new",
  "lens": "songwriter"
 },
 {
  "level": "micro",
  "domain": "names / localization",
  "rule": "Называйте по имени только тех персонажей, кого называет оригинал; безымянные роли оставляйте безымянными с одним словом-идентификатором («сусідка за п'ятдесят»), не добавляя собственных имён.",
  "why": "Оригинал называет Mark, Target, Tri-Luma, Seora; 'this Korean woman' безымянна. Taras: Андрій, Кореянка, Ханбіт. Мы добавили 'Ірина' ('Станців та Мирина / Бачу її майже щодня' 101.6-108.7) — слогоёмкое представление, которое к тому же теряет подсказку доверия (старшая женщина из культуры ухода). Локализация (AGENTS 12) просит украинские идентичности, а не лишние имена.",
  "evidence": [
   "film/asr.json 101.6-106.1, 106.1-108.7; taras/asr.json 100.1-104.6 (unnamed 'вона'); targ/asr.json mentor block 105-118 unnamed",
   "lyrics/lyrics-songcraft.md §2.11; lyrics/lyrics-alignment.md card 28",
   "measurements/lead-observations.md L7 (mentor = Iryna in ours, 'Кореянка' in Taras)"
  ],
  "placement": [
   "references/localization-and-resolution-rules.md → раздел casting (L13-18) → фраза: именование не является локализацией",
   "references/adapt-phase-contract.md → шаг 2/3 → таблица ролей с полем named-in-source yes/no",
   ".agents/skills/song-clone-ua/SKILL.md → Стадия 2"
  ],
  "statusGuess": "new",
  "lens": "songwriter"
 },
 {
  "level": "macro",
  "domain": "coverage / reverse-shot",
  "rule": "Любой удержанный план оригинала длиннее 6 с, несущий две и более названных в строке реакции, адаптируй как минимум тремя постановками (двойной план плюс сингл или POV на каждого реагирующего), где каждая склейка ложится в пределах 0,5 с от ключевого слова, наследуя биты оригинала, а не его единственную кадровку.",
  "why": "Taras alone covers the sympathy sting with reverse angles (5 setups over 21.4 s) and it is the scene the owner singles out; our 12.0 s two-shot under four caption cards hides the gaze-drop (eyes-only ~1.5 s), drifts identity and ends on a cartoon shock face. The original's 10.1 s two-shot is the source of the hold, so the rule triggers on the source shot length, not on song time.",
  "evidence": [
   "taras seg2: S16 53.8-56.25 MS two-shot static > S17 56.25-60.65 ECU eye-scan > S18 60.65-63.4 MCU cowed > S19 63.4-69.6 CU pity smile push-in > S20 69.6-75.2 CU tears push-in (taras/sheets/sheet03 frames 1:00-1:14)",
   "film seg2 S16 62.4-74.4 (12.0 s; film/shots.json i=16 62.433-74.433): 'gaze-drop legible only at full resolution (eyes-only ~1.5 s)', bun morphs 69-74, 'final open-mouth shock reads as fear'",
   "targ seg1 S18 47.03-57.1 (10.07 s) source two-shot",
   "film seg3 S22 112.5-122.4 (9.9 s WS): lyric 'глянула на шию' not staged",
   "phase1-digest film[60,120]: 'regenerate S16 as two shorter takes with a locked hairstyle and a real lean/gaze-drop'"
  ],
  "placement": [
   "tools/targ_film_prepare.py → unit loop after gen_duration → emit coverageSetups[] (two-shot + singles) and one spec per setup when retainedSeconds > 6 and >= 2 facial-reaction subevents",
   "references/film-quality-meta-rules.md → F11 → add 'a reaction named by the lyric gets its own single'",
   "generation_contracts.md → step 8 validation column → count distinct setups >= 3 per qualifying unit"
  ],
  "statusGuess": "new",
  "lens": "editor"
 },
 {
  "level": "macro",
  "domain": "rhythm / hold budget",
  "rule": "Держи длину каждого удержанного плана не более 1,5 длины соответствующего плана оригинала и не более 8 с, а число планов длиннее 8 с — не выше, чем в оригинале (targ: 4 по леджеру, 6 по авто-детекции); избыток песенного времени отдавай новой постановке (вставка, обратный ракурс, наезд), а не удлинению удержания.",
  "why": "Taras keeps the original's rhythm (median 4.10 vs 4.30 s) at identical total length; our +45 s of song was absorbed by stretching holds (13 auto shots >= 8 s vs 6 in the original, max 13.3 s), which is where the 'slideshow' feel comes from. The harness only caps generation at 15 s.",
  "evidence": [
   "summary.json: auto shots >= 8 s targ 6 / taras 3 / film 13; medians 4.48 / 4.17 / 5.97 s; film max 13.27 s",
   "ledgers (time-span recount): >= 8 s holds film S16 12.0, S25 11.8, S28 10.4, S27 10.3, S22 9.9, S36 8.6, S24 8.6, S33 8.0, S18 8.0; targ 4 (S18 10.1, S22 8.8, S29 9.4, S32 8.1); taras 3 (S30 8.5, S34 8.0, S38 8.3)",
   "unit stretches: film-S04 5.0 vs targ-S04 2.27; film-S12 4.5 vs targ-S12 2.0; film-S13 6.5 vs targ-S13 4.07; film-S18 8.0 vs targ-S20 3.93; film-S24 8.6 vs targ-S28 4.3; film-S23 6.8 vs targ-S27 4.6",
   "tools/targ_film_prepare.py lines 109-110: gen_duration = max(4, math.ceil(duration)); assert gen_duration <= 15; tools/plan_film.py line 51: assert 0<duration<=15"
  ],
  "placement": [
   "tools/targ_film_prepare.py → unit loop → assert retainedSeconds <= min(8, 1.5 * sourceDuration) or coverageSplit present",
   "tools/plan_film.py → duration assert → same bound",
   "references/film-quality-meta-rules.md → F02/F11 → rhythm derived from source shot lengths, not from song time",
   "tools/build_film_report.py → G06 gate row → shot-length histogram: count(>= 8 s) <= source count, median <= 5 s"
  ],
  "statusGuess": "new",
  "lens": "editor"
 },
 {
  "level": "micro",
  "domain": "inserts",
  "rule": "На каждый ~30-секундный блок истории ставь не меньше одной бессловесной вставки (руки, предмет, пустая декорация, ECU детали, пэкшот) длиной 1,3-2,5 с, срезанной на существительное строки, и держи долю планов не длиннее 2 с не ниже, чем в оригинале (7 из 44).",
  "why": "Inserts are how both references turn a caption line into a picture without a face; our film has one object insert at 6.8 s (too long) and only three shots <= 2 s, so every line is carried by a face hold.",
  "evidence": [
   "taras: S06 15.4-16.8 hands with rings (1.4 s) on 'років'; S07 16.8-22.75 empty doorway 19.5-22.75 over the vocal gap 21.14-22.2; S10 25.7-28.5 ECU eye; S29 115.5-117.5 mentor CU punch (1.7 s) on 'тільки гірше'; S31 126.0-130.5 packshot; S32 130.5-132.0 application (1.7 s)",
   "targ: S15 39.33-42.13 clutter insert; S27 100.5-105.1 still-life; S34 143.0-146.4 bin + 0.9 s empty frame; S41 180.33-182.47 mirror punctuation (2.1 s)",
   "film: only S15 52-56 tilt-down to jars and S23 122.4-129.2 (6.8 s still-life); shots <= 2 s: S02 2.0, S08 2.0, S15b 1.7 (ledger recount: 7 / 5 / 3 shots <= 2 s)"
  ],
  "placement": [
   "public-targ-film/evidence/shot-plan.json → adaptedSubevents[].type = object-insert → own <= 2.5 s spec in tools/targ_film_prepare.py",
   "references/film-quality-meta-rules.md → F02 → add 'add a wordless insert where the adapted line is longer than the source shot'",
   "tools/build_film_report.py → report gate → inserts per minute >= 1 and shots <= 2 s >= source share"
  ],
  "statusGuess": "strengthen",
  "lens": "editor"
 },
 {
  "level": "micro",
  "domain": "problem visibility",
  "rule": "Делай проблемную зону читаемой (не менее 15 % высоты кадра, ledger problemZoneVisible = yes) не менее чем в половине планов героини в состоянии «до», как в оригинале (12 из 21), и сажай каждую строку, называющую проблему, на CU/ECU, где зона — самый крупный элемент кадра.",
  "why": "In the original and in Taras the problem is on screen for most of the before-state runtime (57 % of before-state heroine shots in both), so the result needs no explanation; in ours the neck is hidden by collar or hair in all but four shots (21 %), and the passport only requires the state to exist, not its screen share or size.",
  "evidence": [
   "ledger recount (time-span dedup): before-state heroine shots with zone = yes: targ 12/21 (S01, S03, S08, S09, S10, S11, S14, S16, S18, S21, S22, S29); taras 12/21 (seg1 S04, S11, S14, S15, S16; seg2 S15 50.5-53.8, S20-S24, S26); film 4/19 (S15 52-56 jars/chest, S19 ECU, S20, S25 tail)",
   "film seg1 note: 'neck lines visible only in S09 (partial) and S15, and there while the face is out of frame'",
   "taras-S23 87.6-88.3 finger lands on the patch on 'Пігментні плями' (taras/sheets/sheet03 1:28-1:29); taras-S24 90.05-92.7 front-lit passport frame; film-S19 92.3-93.8 ECU on 'На шиї' is the one working exception",
   "references/problem-passport-contract.md line 92: Boolean problemVisible per spec; tools/problem_quality.py validate_export checks problemRegionVisible per row only"
  ],
  "placement": [
   "references/problem-passport-contract.md → video-spec fields → add film-level visibleShareMin: 0.5 and zoneMinFrameHeight: 0.15",
   "tools/problem_quality.py → validate_export → count problemRegionVisible true over before-state units >= 0.5",
   "tools/targ_film_prepare.py → units with problemVisible → sizeTarget CU|MCU on the naming line"
  ],
  "statusGuess": "strengthen",
  "lens": "editor"
 },
 {
  "level": "micro",
  "domain": "camera motion",
  "rule": "Любой план длиннее 4 с, который удлинён относительно исходного плана оригинала или не имеет там движения камеры, веди медленным наездом (3-10 % масштаба за план) или сменой мизансцены; статичный кадр с одной лишь лицевой анимацией допускай не дольше 4 с.",
  "why": "53 % of Taras shots carry a slow push-in (its most consistent craft signature, beyond the original's 23 %); our long holds are static, which reads as animated stills. The original's own long holds either push (S18, S29) or live on caption turnover, so the rule is scoped to holds we lengthened.",
  "evidence": [
   "ledger camera recount: taras 25/47 moving (push-ins S01, S12, S14, S19, S20, S22, S23, S30, S31, S33-S37, S39-S41, S43); targ 10/44; film 12/44 (S12, S13, S15, S16 drift, S19, S23, S25, S27, S30, S32, S33, S35)",
   "film static holds > 8 s: S18 8.0 (ledger 'drags'), S22 9.9, S24 8.6, S28 10.4, S36 8.6; S16 12.0 staged static (ledger camera 'handheld drift')",
   "phase1-digest targ[0,50]: 'Static frames dominate; the source gets its film feel from a handful of soft pushes (S05, S13, S18)'"
  ],
  "placement": [
   "tools/targ_film_build.py → per-slot GSAP → tl.fromTo('#picNNN',{scale:1},{scale:1.06,duration,ease:'none'},0) for slots > 4 s without a source camera move (editorial, no purchase)",
   "tools/targ_film_prepare.py → directions → 'camera: slow push-in' for retained > 4 s",
   "references/film-quality-meta-rules.md → F11 → keep 'camera does not replace acting', add 'static hold > 4 s on a lengthened unit is a defect'"
  ],
  "statusGuess": "new",
  "lens": "editor"
 },
 {
  "level": "micro",
  "domain": "reaction singles / legibility",
  "rule": "Каждую реакцию, названную в строке (жалость, взгляд-сканирование, улыбка, растерянность, комплимент, удивление), покрывай отдельным синглом на лице реагирующего (не шире MCU, не дольше 4,5 с), врезанным в пределах 0,5 с от ключевого слова, и проверяй читаемость выражения на 480-пиксельном прокси телефона.",
  "why": "Taras makes the scan and the smile legible by isolating eyes; ours keeps them in a wide or two-shot, or plays them over closed eyes, so the lyric names an emotion the phone viewer cannot see.",
  "evidence": [
   "taras-S03 7.0-10.8 pity CU lands 9.0-10.5 on 'шкода'; taras-S17 56.25-60.65 ECU eye-scan; taras-S19 63.4-69.6 POV CU smile",
   "film-S16 65-66.5 gaze-drop 'eyes-only'; film-S31b 198.7-203.3 'здивування' over the blonde's closed eyes; film-S22 117.7-121.2 'глянула на шию' not staged in WS; film-S33 210.4-212 blonde reads worried on the compliment tail",
   "taras also fails it in S38 161-169.3 (no 'confused' look) and S39 169.3-173.5 (no compliment-giver) - the mechanism, not Taras, is the rule"
  ],
  "placement": [
   "tools/targ_film_prepare.py → shot-plan adaptedSubevents[] of type facial-reaction → coverage: single, sizeMax: MCU",
   "public-targ-film/evidence/full-film-review.json → eventChecks → add legibleAt480px",
   "references/film-quality-meta-rules.md → F13 → phone-proxy legibility row"
  ],
  "statusGuess": "strengthen",
  "lens": "editor"
 },
 {
  "level": "micro",
  "domain": "acting valence",
  "rule": "На любой строке об улучшении, комплименте или «удивлении в глазах» открывай план на неотрицательном лице (брови вверх или нейтраль, рот закрыт или мягкая улыбка), держи удивление не дольше 1 с и запрещай сведённые брови, открытый рот и руку, сжимающую зону, как это делает оригинал (удивление → смех → рука к груди в targ-S32).",
  "why": "In ours 'positive news reads as bad news for the first half of every result beat' (film seg3 note); both references play result beats as relief then joy, so the caption and the face contradict each other exactly where the product is proven.",
  "evidence": [
   "film-S26b 155.7-162.9: knit brows + hand on neck under 'Заломи вже не так помітні' (film/sheets/sheet06 2:35-2:42)",
   "film-S27 167.5-169 shock face on 'Гарна інша' (sheet06 2:48-2:49); film-S28 173.7-177.3 shock + palpation under 'помітно гладша' (sheet06 2:57-2:59); film-S31a 194-196.5 blonde alarm; film-S25 146-149.6 near-tearful frown while reading",
   "taras-S34 139-147 tender smile, daughter touches cheek 145-146; taras-S35 147-152.5 surprise -> toothy grin timed to the vocal rest 148.92-150.72; taras-S40 173.5-178.9 tender ECU",
   "targ-S32 128.2-136.3 surprise -> laugh -> hand to chest; targ-S33 shock -> smile by 140.5"
  ],
  "placement": [
   "tools/targ_film_prepare.py → per-unit directions → expressionValence derived from the lyric line (positive lines -> 'relief/joy, no alarm')",
   "references/problem-passport-contract.md → states intermediate/resolved → add 'expression: relief/joy'",
   "public-targ-film/evidence/full-film-review.json → eventChecks → valenceMatchesLyric",
   "references/film-quality-meta-rules.md → F11/F13"
  ],
  "statusGuess": "new",
  "lens": "editor"
 },
 {
  "level": "micro",
  "domain": "acting amplitude",
  "rule": "Ограничивай амплитуду выражения амплитудой соответствующего бита оригинала: никакого открытого рта шире, чем в исходном событии, никаких улыбок прямо в камеру в наблюдательных планах; горе неси мокрыми глазами, дрожью губ и взглядом, а не отвисшей челюстью.",
  "why": "The ledgers flag our heroine as 'slightly overacted' from the first second and 'cartoon-wide' at 6 s; Taras reads emotion from eyes and small mouth shapes, and its overplays are the exceptions the ledgers call out.",
  "evidence": [
   "film-S01 1.0 s grimace 'slightly overacted'; film-S03 6.0 s 'cartoon-wide' shock; film-S07 22.5 direct-to-camera grin 'breaks the observational tone'; film-S10 darting eye uncanny; film-S16 72-74.4 shock 'reads as fear'",
   "taras-S11 28.5-33.4 lip trembling + lean-in; taras-S20 69.6-75.2 tears with push; taras overplays flagged: S15 fists, S19 closed-eye smile, S10 doll eye",
   "targ-S10 28.0 bulging eyes (uncanny in the source - not to copy)"
  ],
  "placement": [
   "tools/targ_film_prepare.py → prompt block → 'restrained: wet eyes, small mouth shapes; open-mouth shock only where the source event says so'",
   "tools/targ_film_prepare.py → shot spec criteria.actingMin → add overacting: 0",
   "references/film-quality-meta-rules.md → F11"
  ],
  "statusGuess": "new",
  "lens": "editor"
 },
 {
  "level": "micro",
  "domain": "transitions",
  "rule": "Каждый спецпереход оригинала (бумажная диафрагма, белая вспышка, блюр, белый дизолв, радиальный зум, врезка-воспоминание) воспроизводи ровно один раз там же, где он стоит в оригинале, а между состояниями проблемы используй либо кросс-фейд совмещённого ракурса (та же кадровка, голова в пределах 5 % высоты кадра, не дольше 0,5 с), либо склейку на удержанный кадр; кросс-фейд двух разных кадровок считай дефектом (двойная экспозиция).",
  "why": "Our four ghost dissolves are the most visible seams in the film; Taras uses hard cuts almost everywhere, the original's devices each appear once, and AGENTS rule 14 'use fades between problem states' as written is what produced the ghosts.",
  "evidence": [
   "film ledger transitionOut/In: cross-dissolve 107.7-108.9 (S21a>S21b, 'ghost double exposure ~1.2 s'); 155.6-155.9 (S26a>S26b); 173.3-173.7 (S27>S28, after a ~3-frame wider flash; film/sheets/sheet06 frame 2:53 shows the double face); ~198.45-198.7 (S31a>S31b)",
   "taras: 44 hard cuts, one 0.3 s dissolve S25a>S25b 95.1-95.4 ('reads as glitch'), intended ghost-bride overlay S21 75.2-79.85",
   "targ devices once each: paper 3.37-4.0, flash 11.87-12.27, blur 45.97-46.07, white 85.6-86.6, morph 98.9-99.7, radial zoom 155.03-155.43",
   "tools/targ_film_build.py line 270 opacity fade .45 s; lines 279-280 #intermediate-compare .5 s fade; 'zoom' types rendered as opacity+scale (lines 253-261)"
  ],
  "placement": [
   "tools/targ_film_build.py → state-boundary transitions → hold-then-cut unless a matched-view frame-diff check (head position <= 5 % of frame height) passes; render targ-tr-06 as a radial zoom, not a blur-dissolve",
   "AGENTS.md → rule 14 'Use fades between problem states' → qualify: matched-view fades only",
   "references/localization-and-resolution-rules.md → step 6 → same qualification; references/film-quality-meta-rules.md → F05"
  ],
  "statusGuess": "conflict",
  "lens": "editor"
 },
 {
  "level": "micro",
  "domain": "angle change",
  "rule": "Никогда не ставь рядом два сгенерированных дубля с одинаковыми ракурсом и крупностью: каждая склейка меняет крупность хотя бы на одну ступень (ECU/CU/MCU/MS/MLS/WS) или ракурс не менее чем на 30°, либо бит генерируется одним непрерывным клипом; сброс того же ракурса в оригинале (targ-s026, targ-s037) адаптируй как обратный ракурс.",
  "why": "Same-angle resets teleport hands and props and force a dissolve to hide the jump; Taras changes angle on nearly every cut (8 adjacent same-size pairs vs 13 in the original), and its one same-angle reset is also its one visible glitch.",
  "evidence": [
   "film-S22a>S22b 117.7 'posture/blocking teleport (heroine's hand, mentor's book)'; film-S31a>S31b 198.6 same angle + ghost",
   "targ-S25>S26 95.4 reset and targ-S36>S37 155.03 radial-zoom smear masking a same-angle take reset (source seams)",
   "taras-S25a>S25b 95.1-95.4 is Taras's one such failure; otherwise S26 MS two-shot > S27 OTS MCU > S28 MS > S29 CU > S30 MS",
   "adjacent same-size pairs: targ 13/42, taras 8/43, film 10/41"
  ],
  "placement": [
   "tools/targ_film_prepare.py → units flagged same-angle-reset → merge into one longer spec or assign a reverse composition",
   "tools/targ_film_build.py → slot assembly → refuse adjacent slots with identical sourceComposition",
   "references/film-quality-meta-rules.md → F07/F11"
  ],
  "statusGuess": "new",
  "lens": "editor"
 },
 {
  "level": "macro",
  "domain": "render style consistency",
  "rule": "Рендери все планы, включая ECU проблемной зоны, панели состояний паспорта и продуктовые вставки, в одной стилизации фильма; фотореалистичные кожа, руки или натюрморт внутри стилизованного фильма — дефект, даже если оригинал в этом же месте сам меняет стиль (targ-S24, targ-S27).",
  "why": "Our film has five style seams, two of them exactly on the problem-zone evidence shots, so the proof looks like a different production; Taras has two seams, the original two, and we copied the original's seams as fidelity.",
  "evidence": [
   "film-S15 53-56 chest/hands semi-real; film-S19 92.3-93.8 photoreal neck ECU vs Pixar profile; film-S21b 108.3-112.5 mentor semi-real (copied targ-S24); film-S23 photoreal still-life (copied targ-S27); film-S25/S26a 146-155 heavier skin-texture pass vs S27/S28",
   "taras-S17 56.25-60.65 semi-real newWife; taras-S31 126-130.5 semi-real packshot",
   "targ-S24 86.03-90.33 semi-real portrait; targ-S27 100.5-105.1 still-life"
  ],
  "placement": [
   "references/problem-passport-contract.md → states[].image acceptance → 'same stylization as the cast passport sheet'",
   "references/film-quality-meta-rules.md → F09 (derived crops re-stylized) and F13 (style-consistency row)",
   "tools/targ_film_prepare.py → directions for targ-s030/s031/s033 → 'skin texture drawn in the film's stylization; no photoreal pores'"
  ],
  "statusGuess": "strengthen",
  "lens": "editor"
 },
 {
  "level": "macro",
  "domain": "character continuity",
  "rule": "Фиксируй каждому повторяющемуся персонажу один силуэт (форма и цвет причёски, цвет верха, аксессуары) на весь фильм, меняй его только по сюжетному мотиву (свадебное воспоминание, платье на концерт) и никогда не наследуй собственные перебросы причёски и гардероба оригинала как «верность».",
  "why": "Taras is recognisable 'by cardigan + bun + patches' across 45 shots although its faces drift; ours splits the heroine into two silhouettes (high bun vs loose waves) and copies the original's re-rolls, and the harness continuity groups are per visit, not per film.",
  "evidence": [
   "taras heroine: low bun + dusty-rose cardigan + white tee in every present-day shot (seg1-4 notes 'Heroine wardrobe fully consistent'); newWife cream top; mentor black bob + white streak + pearls",
   "film heroine: high bun (S01-S03, S13-S19, S21a-S22b) vs loose waves (S09-S11, S15, S20, S25-S36); film-S20 93.8-101.6 hair down between bun shots S19 and S21a (inherited from targ-S22's own switch); film-S16 69-74 bun morphs into loose updo inside the take; film-S12 beige cardigan; film-S25 loose hair on studio day 2",
   "targ: 25 hair descriptors, 'five outfits in 50 s' (seg3)",
   "tools/problem_quality.py continuityGroups (lines 48, 104, 191) are per visit/location"
  ],
  "placement": [
   "tools/problem_quality.py → continuityGroups → add a film-wide silhouetteLock group per characterId whose accepted image is attached to every spec",
   "tools/targ_film_prepare.py → allowedChanges → must not import source wardrobe switches",
   "references/film-quality-meta-rules.md → F07 → extend from 'each visit' to 'whole film unless motivated'"
  ],
  "statusGuess": "strengthen",
  "lens": "editor"
 },
 {
  "level": "micro",
  "domain": "generation length / identity",
  "rule": "Генерируй дубль с лицом в крупности MS и крупнее не дольше 6 с (удержание не дольше 5 с), режь более длинные биты на реакции или смене мизансцены и проверяй идентичность первого и последнего кадра каждого дубля (пропорции лица, силуэт причёски).",
  "why": "Identity drifts inside long generations in all three videos and ours has the longest takes; the harness caps generation at 15 s and checks identity per accepted still, not per take end-frame.",
  "evidence": [
   "film-S16 62.4-74.4 face rounder/younger by 74 s, hair morphs; film-S27 162.9-173.2 'different, younger-looking face' at the end of a 10.3 s take",
   "taras-S38 161-169.3 blonde swaps sides, bun changes; targ-S29 push 115.5-117 background morphs",
   "tools/targ_film_prepare.py lines 109-110: gen_duration cap 15 s"
  ],
  "placement": [
   "tools/targ_film_prepare.py → gen_duration → 6 s cap for face-bearing units (MS or tighter)",
   "references/film-quality-meta-rules.md → G05 → add first/last-frame identity row"
  ],
  "statusGuess": "new",
  "lens": "editor"
 },
 {
  "level": "micro",
  "domain": "captions / typography",
  "rule": "Верстай титры по грамматике оригинала: жирный геометрический гротеск, высота прописных не менее 3,5 % высоты кадра, непрозрачный белый скруглённый бокс с центром на 68-72 % высоты кадра (никогда в нижних 15 %), не более 2 строк и 9 слов, без конечной пунктуации, тире, кавычек и двоеточий (внутренние запятые допустимы), карточка не короче 1,5 с.",
  "why": "Both references share this grammar; ours is smaller (2.7 %), lower (78-88 %), humanist, fully punctuated (70 full stops, 22 em dashes, 16 guillemets, 8 colons) and flashes one-second one-word cards, so the text layer reads as prose subtitles rather than lyric cards.",
  "evidence": [
   "taras captionStyle seg1-4: Inter/Roboto-Bold-like, ~3.5-4 % per line, box centre 68-72 % (86 % after 181.6), 1-2 lines, median 6 words, max 9, no terminal punctuation, 14 internal commas in 54 cards",
   "targ: Poppins-like ~5 %, box centre 68-75 %, median 7 words, max 13, 17 commas, no full stops",
   "film: tools/targ_film_build.py line 355 .caption{bottom:108px; font-size:23px} on 486x864 (2.7 % cap height, box ~78-88 %); 49/67 cards end with '.'; film-S34 218.4-224.07 four cards in 5.67 s incl. 'щиро.' 1.0 s and 'Її думка — не головна.' 1.0 s",
   "Taras's own faults (mid-sentence 'Я'/'І' capitals, 'через | Ряд' split) not to copy; film display_caption() lowercasing stays"
  ],
  "placement": [
   "tools/targ_film_build.py → css .caption → font-size >= 0.035*HEIGHT, box centred at 0.68-0.72*HEIGHT; font asset captions-bold.ttf → geometric OFL face",
   "tools/targ_film_build.py → display_caption() → strip terminal '.'/':' , «», ' — '",
   "tools/targ_film_build.py → caption loop → merge captionAnchors whose masterVocalTime span < 1.5 s",
   "references/film-quality-meta-rules.md → F15 → add the typographic spec"
  ],
  "statusGuess": "new",
  "lens": "editor"
 },
 {
  "level": "micro",
  "domain": "overlays / disclaimer",
  "rule": "Не держи дисклеймер постоянной полосой на сюжетных планах: одна строка на финальной карточке или полоса не дольше 3 с только на первом нанесении, и никогда под лирическими титрами, как в оригинале, где сюжетные планы не несут никаких оверлеев.",
  "why": "The strip adds a second text layer for 90 s and crowds the bottom third together with the caption box and the CTA; neither reference has it. Whether a card-only disclaimer satisfies the claim limit is an owner/legal choice, hence conflict.",
  "evidence": [
   "tools/targ_film_build.py line 371: #fiction-note .disclosure data-start='149.63' to end of film; visible on every film/sheets/sheet06 frame under the caption box",
   "film seg4 segmentNotes: 'bottom third is crowded by both the lyric caption box and the persistent disclaimer strip'; film seg3 agent: 'unobtrusive and correctly begins with the first product-use shot' (observation split)",
   "taras: no overlay on story shots, one disclaimer line on S44 188.42-194.17; targ: none"
  ],
  "placement": [
   "tools/targ_film_build.py → .disclosure clip → data-start = card_start (or <= 3 s at first application)",
   "references/problem-passport-contract.md → claimLimits → owner decides whether a card-only disclaimer satisfies 'Fictional cosmetic illustration'"
  ],
  "statusGuess": "conflict",
  "lens": "editor"
 },
 {
  "level": "macro",
  "domain": "ending / CTA block",
  "rule": "Укладывай финал в бюджет оригинала: план ведущей не дольше 6 с с одним жестом на продуктовой строке, финальная карточка не короче карточки оригинала (не менее 5,5 с), полностью статичная, с не менее 0,5 с карточки после последнего спетого слова и без лирических титров поверх неё, а строка о собственной ценности заканчивается на лице героини, а не на продукте.",
  "why": "Both references close in ~12 s (6 + 6); ours spends 13 s, its card is the shortest of the three with the vocal running to 0.3 s before the end, and captions, disclaimer and CTA stack on the card.",
  "evidence": [
   "targ-S42 182.47-188.5 (6.03 s, palm-down on 'link's below') + targ-S43 188.5-194.37 (5.87 s, vocal ends 188.56)",
   "taras-S43 184.21-188.42 (4.21 s, two-handed hold) + taras-S44 188.42-194.17 (5.75 s; brand line sung over the card, uncaptioned)",
   "film-S36 226.57-235.2 (8.63 s, three gestures, four caption cards) + film-S37 235.2-239.7 (4.5 s, vocal to 239.4, lyric caption and disclaimer on the card)",
   "shot-plan.json endingEditorialOverride.sourceCardDuration already recorded"
  ],
  "placement": [
   "public-targ-film/evidence/shot-plan.json → endingEditorialOverride → enforce card >= sourceCardDuration",
   "tools/targ_film_build.py → card_start → derive from vocal end - 5.5 s; caption loop → drop cards with start >= card_start",
   "cross-domain: .agents/skills/song-clone-ua/SKILL.md → stage-2 text budget → sung CTA <= 2 lines"
  ],
  "statusGuess": "strengthen",
  "lens": "editor"
 },
 {
  "level": "macro",
  "domain": "vocal rests as visual rests",
  "rule": "Каждую вокальную паузу от 1 с закрывай бессловесным удержанным кадром (пустая декорация, предмет, лицо, дожимающее выражение) с выключенными титрами, и требуй от песенного этапа не меньше суммарного времени пауз оригинала (16 пауз, 11,1 с) с не менее чем тремя паузами от 1 с, иначе монтажу негде дышать.",
  "why": "Taras's most remembered images (empty doorway, eye-scan, surprise-to-smile) sit on its five vocal rests; our master has zero gaps > 1 s (sung fraction 0.994, 8.7 s of rests against +45 s of runtime), so no held frame is possible. Cross-domain: the edit cannot fix it alone.",
  "evidence": [
   "summary.json gapsOver1s: taras 21.14-22.2 (S07 empty doorway 19.5-22.75), 58.32-60.06 (S17 ECU scan, caption off 59.08-60.79), 64.52-66.36, 148.92-150.72 (S35 caption off 149.5-151.0, expression morph), 188.74-189.92 (card); targ one gap 91.88-92.9; film none",
   "summary.json voicedFrameFraction taras 0.577 vs film 0.787; sungFraction 0.948 / 0.95 / 0.994",
   "phase1-digest songcraft: rests on vocal stems taras 28 / 19.9 s (10.3 %), original 16 / 11.1 s, ours 18 / 8.7 s (3.6 %); our longest unbroken vocal stretch 42.8 s (81-124 s)",
   "film only caption hole 98.9-100.0 (S20)"
  ],
  "placement": [
   ".agents/skills/song-clone-ua/SKILL.md → stage-2 text budget → rest budget >= original",
   "references/adapt-phase-contract.md → step 4/7 gate → measured gaps >= 1 s >= 3",
   "public-targ-film/evidence/shot-plan.json → restBeats[] → mapped to inserts in tools/targ_film_prepare.py",
   "tools/targ_film_build.py → caption loop → no card during a rest"
  ],
  "statusGuess": "new",
  "lens": "editor"
 },
 {
  "level": "micro",
  "domain": "recommender coverage",
  "rule": "Показывай рекомендательницу хотя бы в одном CU/MCU не дольше 3 с на её строке совета с открытой чистой зоной (кожа/шея), занимающей не менее 20 % высоты кадра; покрытие совета только общим планом — дефект, оригинал даёт портрет (targ-S24) и MCU (targ-S28).",
  "why": "Taras brings the mentor into MCU/CU at the door; ours keeps the ask and answer in wides where faces are ~1/12 of the frame and the 'smooth neck' proof is a sliver above a collar, so the recommender's resolved state is never legible.",
  "evidence": [
   "taras-S25a 92.7-95.4 MCU, S27 105.5-110.0 OTS MCU, S29 115.5-117.5 CU punch on 'тільки гірше', S30 117.5-126 MS two-shot",
   "film-S21a 101.6-108.3 WS (faces ~1/12 frame), film-S22 112.5-122.4 WS, film-S21b 108.3-112.5 CU 'the smooth neck the lyric sells is only a sliver above the collar', film-S24 129.2-137.8 MCU 8.6 s (no neck focus)",
   "targ-S23/S25/S26 WS + S24 86.03-90.33 portrait + S28 105.1-109.4 MCU",
   "tools/problem_quality.py line 37: recommender requiredVisibleState == 'resolved' only"
  ],
  "placement": [
   "tools/problem_quality.py → recommender participant → add zoneVisibleInAtLeastOneCU: true",
   "tools/targ_film_prepare.py → mentor units → size target <= MCU on the advice line",
   "AGENTS.md → rule 21 / references/problem-passport-contract.md → recommender paragraph"
  ],
  "statusGuess": "strengthen",
  "lens": "editor"
 },
 {
  "level": "micro",
  "domain": "beat timing",
  "rule": "Сажай склейку или смену выражения на ключевое спетое слово в пределах 0,3-0,5 с, как это делают оригинал и обе адаптации, и не «чини» этот уже работающий механизм.",
  "why": "This is the one rhythm mechanism all three share and the harness already plans it (217 microevents with targetSongTime); listed so nobody removes it while fixing the rest.",
  "evidence": [
   "taras-S03 'шкода' 9.0-10.5, S10 'ще не дійшов' 27.4, S16 'почула' 48.2, S23 'Пігментні плями' 87.6, S33 'зупинилась' 135.0, S35 'майже не було'",
   "film-S16 57.5 'голос', S19 92.3 'На шиї', S36 232.2 'Посилання'; targ 'brighter'/'shock'/'even' within ~0.3 s",
   "public-targ-film/evidence/shot-plan.json: 43 units, 217 adaptedSubevents with targetSongTime"
  ],
  "placement": [
   "public-targ-film/evidence/shot-plan.json → adaptedSubevents[].targetSongTime → keep",
   "public-targ-film/evidence/full-film-review.json → eventChecks → |event time - key-word onset| <= 0.5 s"
  ],
  "statusGuess": "exists",
  "lens": "editor"
 },
 {
  "level": "micro",
  "domain": "product / packshot",
  "rule": "Везде, где этикетка продукта занимает не менее 10 % высоты кадра, композитируй реальный пэкшот в HyperFrames (трекинг или вставка не дольше 5 с) вместо генераторной надписи, и ставь отдельную пэкшот-вставку не дольше 5 с между передачей продукта и нанесением, как натюрморт оригинала (targ-S27, 4,6 с).",
  "why": "Generated labels are garbled in all three videos; Taras's clean S31 packshot is the exception and is what makes its brand legible, while our presenter shot shows a garbled jar three seconds before a clean card.",
  "evidence": [
   "film-S36 226.6-235.2 'jar label is AI-garbled pseudo-text' beside a clean card 3 s later; film-S25 137.8-149.6 body label 'only partly' readable",
   "taras-S31 126-130.5 clean 'HANBIT SKINCARE / ARBUTIN 7.0+TXA 4.0 CREAM' packshot vs taras-S43 184.2-188.4 garbled vertical text; taras-S30 117.5-119.7 tube vanishes inside clasped hands",
   "targ-S42 secondary label garbled; targ-S27 100.5-105.1 still-life"
  ],
  "placement": [
   "tools/targ_film_build.py → presenter/hand-off slots → packshot overlay; insert slot between hand-off and application",
   "tools/targ_film_prepare.py → units targ-s029/s030/s042 → 'label rendered by the editor'",
   "references/film-quality-meta-rules.md → F03 (product truth)"
  ],
  "statusGuess": "new",
  "lens": "editor"
 },
 {
  "level": "macro",
  "domain": "scene inventory / establishing shots",
  "rule": "Установочный или переходный план оригинала (проход в магазин, кухня, машина снаружи) либо сохраняй в длине исходного плана с допуском 25 %, либо выбрасывай целиком, как делает Тарас, но никогда не растягивай его для поглощения более длинной адаптированной строки.",
  "why": "The lead observation that these scenes were 'added' is wrong - they are in the original; what differs is that ours doubled them into lulls, while Taras dropped them. The fix (drop vs split) differs from the general hold budget, so it is a separate rule.",
  "evidence": [
   "targ-S12 31.0-33.0 (2.0 s) kitchen with two children -> film-S12 37.0-41.5 (4.5 s); targ-S13 33.0-37.07 (4.07 s) store entrance -> film-S13 41.5-48.0 (6.5 s, 'longest shot in the segment, a lull'); targ-S20 63.17-67.1 (3.93 s) car exterior -> film-S18 81.6-89.6 (8.0 s, 'drags')",
   "taras drops all three (S12 bed -> S13 aisle wide 35.5-38.3, 2.8 s; car interior S22 4.1 s)",
   "measurements/lead-observations.md line 21 ('ours adds a kitchen-with-kids scene ... that the original does not have') contradicted by targ-seg1 ledger"
  ],
  "placement": [
   "tools/targ_film_prepare.py → unit tag establishing from sourceCausality → retained/source <= 1.25 or unit dropped",
   "generation_contracts.md → step 8 validation"
  ],
  "statusGuess": "new",
  "lens": "editor"
 },
 {
  "level": "macro",
  "domain": "problem-legibility budget",
  "rule": "Держите долю времени героини в состоянии «до», когда проблемная зона читается (класс yes), не ниже доли оригинала (targ ≈65 % против наших 30 %), а первый читаемый кадр зоны — не позже первого кадра оригинала плюс 10 с (targ 0,0 с; у нас 52,0 с), измеряя обе величины на ледже реального экспорта, где засчитывается любая крупность, читаемая на телефоне.",
  "why": "Оба чемпиона продают проблему до передачи продукта (59–65 % времени героини в фазе «до»); у нас 30 % и ноль в первые 52 с; суммарное «yes» сопоставимо, но сдвинуто в разрешённое состояние (57 с в развязке), т.е. проблема не продана там, где решается покупка.",
  "evidence": [
   "ledgers/{targ,taras,film}-seg1..4.json recount (0.5 s grid, first-seen dedup): before-phase yes targ 57.5/88.0 s, taras 48.5/82.5 s, film 28.5/94.5 s",
   "First before yes: targ-S01 0.0 s; taras-S04 10.8 s; film-S15 52.0 s (tilt-down)",
   "film-S01 'no (neck mostly covered by cardigan collar)', film-S03/S08/S11/S13/S14 'no', film-S09 'partial: 3-4 faint creases … too faint to read at phone size'",
   "Longest before-phase gap without yes/partial: targ 12.5 s (10.5–23), taras 15.0 s (13.5–28.5), film 27.0 s (0–27)"
  ],
  "placement": [
   "references/problem-passport-contract.md → Versioned JSON contract → new field legibilityBudget {beforeShareMinRatioToOriginal: 1.0, firstLegibleSecMax: original + 10} bound to the original's own ledger figures",
   "references/video-dna-contract.md → per-shot fields of the ORIGINAL → add problemZoneVisible class per shot so the ratio has a denominator (canonical DNA JSON legibilityProfile)",
   "tools/problem_quality.py → validate_export → compute the before-phase share from unitVisualRanges + per-unit zone class of the export ledger and fail below the ratio",
   "references/film-quality-meta-rules.md → F05 / G06 → clause: доля читаемости «до» ≥ доли оригинала, проверяется на экспорте"
  ],
  "statusGuess": "strengthen",
  "lens": "passport"
 },
 {
  "level": "macro",
  "domain": "passport zone exposure",
  "rule": "Задавайте в паспорте проблемы для каждой группы непрерывности инварианты экспозиции зоны (вырез, причёска, ракурс), которые держат зону в кадре в каждом MCU/CU состояния «до» так же надёжно, как лицо держится в кадре оригинала, вместо копирования лицевых композиций оригинала 1:1.",
  "why": "Оригинал и Taras кладут дефект на лицо, которое есть в каждом кадре 9:16; шея — легитимная правда продукта, но требует плана: наш паспорт заморозил воротник кардигана и высокий пучок, скрывающие зону, а единственный открытый портрет S20 получен ценой разрыва идентичности (волосы распущены, без кардигана между двумя кадрами с пучком).",
  "evidence": [
   "public-targ-film/evidence/problem-passport.json continuityGroups.oksana-same-shop-visit invariants: 'All chestnut hair gathered in the same high bun', 'Same muted rose cardigan, cream scoop-neck…'; word counts in passport: collar 0, neckline 0, exposure 0",
   "film-S01 0–4 'no (neck mostly covered by cardigan collar)', film-S14 48–52 'no (neck covered)', film-S16 56–62.4 'partial: faint neck lines above the collar'",
   "film seg2 identityDrift: S20 93.8–101.6 'hair fully down and no cardigan between two bun-and-cardigan car/lobby shots'",
   "Before-phase CU/MCU/ECU share: taras 50.0/82.5 s = 61 %, targ 18.5/88.0 s = 21 %, film 23.0/94.5 s = 24 %"
  ],
  "placement": [
   "references/problem-passport-contract.md → continuityGroups schema → required zoneExposureInvariants[] for beneficiary groups in before state when the zone is not on the face",
   "tools/problem_quality.py → validate_plan → strings(group.get('zoneExposureInvariants')) required alongside invariants",
   "tools/targ_film_prepare.py → directions (line ~154) → inject the group's zoneExposureInvariants into every before-state prompt",
   "references/film-quality-meta-rules.md → F07 → add вырез/открытость зоны to the continuity list"
  ],
  "statusGuess": "new",
  "lens": "passport"
 },
 {
  "level": "micro",
  "domain": "hook first frame",
  "rule": "Показывайте дефект в первом кадре героини хука так же, как первый кадр оригинала (targ-S01: пятна читаются с 0,0 с), а не только её реакцию на него.",
  "why": "Оригинал открывает проблему в кадре 0,0 с, Taras — на 10,8 с, мы — на 52 с; наш хук продаёт горе без видимой причины.",
  "evidence": [
   "targ-S01 0.0–3.9 zone 'yes: brown pigment patches on both cheeks and forehead'",
   "taras-S04 10.8–13.3 zone 'yes - one large flat beige patch on the cheekbone'",
   "film-S01 0–4 zone 'no (neck mostly covered by cardigan collar)'",
   "film seg1 emotionLegibility: 'no shot lets the viewer see the neck texture and her reacting face at the same time at phone size'"
  ],
  "placement": [
   "AGENTS.md → rule 6 (first 2–3 s) → add: если зона видна в первом кадре оригинала, она видна в первом кадре адаптации",
   "tools/plan_film.py → shot-plan unit targ-s001 → problemVisible: true required for the heroine's first unit; zone exposure in the spec prompt",
   "generation_contracts.md / SKILL.md → шаг 8 validation → первый кадр героини проверяется на читаемость зоны"
  ],
  "statusGuess": "strengthen",
  "lens": "passport"
 },
 {
  "level": "micro",
  "domain": "state rendering at phone scale",
  "rule": "Рисуйте состояние «до» столь же явно и «после» столь же чисто, как оригинал, на масштабе телефона (кадр 360 px), а промежуточное состояние делайте коротким и явно светлее, а не тонким отличием, которое читается только при увеличении.",
  "why": "Taras читается на телефоне («mottled cheek/forehead patches clearly read at phone size»), наши линии «too faint to read at phone size»; гейт F13 проверяет полное разрешение и увеличение, что пропускает текстуру, невидимую на телефоне; наш промежуточный кадр «lines fainter… but still traceable».",
  "evidence": [
   "taras seg1 whatWorks: patches clearly read at phone size; taras-S24 90–92.7 'pigment at maximum'",
   "film-S09 27–31 'too faint to read at phone size'; film-S12 'faint neck lines in profile'; film-S18 81.6–89.6 'neck visible but too small to read lines'",
   "film-S26b 155.7–162.9 intermediate 'lines fainter than S25/S26a but still traceable'",
   "references/film-quality-meta-rules.md F13: 'Проверять проблемную зону в фактическом разрешении и увеличенных деталях' (no phone-scale check)"
  ],
  "placement": [
   "references/film-quality-meta-rules.md → F05 / F13 → add a phone-scale check: downscale export to 360×640 and re-read the zone beside the enlarged inspection",
   "tools/build_film_report.py → comparison section → publish both enlarged and 360 px state comparisons",
   "references/problem-passport-contract.md → states[] → phoneScaleLegibility acceptance note per state"
  ],
  "statusGuess": "strengthen",
  "lens": "passport"
 },
 {
  "level": "micro",
  "domain": "looking beats",
  "rule": "Ставьте оба «смотрящих» бита оригинала — сканирование соперницей (targ-S18) и взгляд наставницы (targ-S27/S28) — так, чтобы глаза оценивающей или камера физически двигались к зоне (для шеи — вниз) с отдельным крупным планом глаз, а не статичным двухплановым кадром на 12 с.",
  "why": "Taras изолирует глаза оценивающей в ECU и добавляет POV-наезд; оригинал держит скан в двухплановом кадре, но с лицом ≥ 1/3 кадра; у нас 12 с статичного двухплана, где опускание взгляда «eyes-only for ~1.5 s and easy to miss», а взгляд наставницы на шею не поставлен вовсе.",
  "evidence": [
   "taras-S17 56.25–60.65 ECU 'the eyes literally scan forehead-cheek-jaw'; taras-S19 63.4–69.6 POV push-in",
   "targ-S18 47.03–57.10 two-shot, rival 'eyes lowered/half-closed scanning heroine's face'",
   "film-S16 62.4–74.4 12.0 s static two-shot; film seg2 emotionLegibility on 'погляд ковзає вниз'",
   "film seg3: 'the S22 lyric глянула на шию is not staged (reads as ordinary eye contact)' (film-S22 112.5–122.4)"
  ],
  "placement": [
   "tools/plan_film.py → shot-plan units targ-s018 / targ-s027 → plannedReaction must name gaze source, gaze path (face→zone) and times instead of the boilerplate sentence",
   "references/film-quality-meta-rules.md → F11 → «взгляд на адресата» extended to «взгляд на зону» for every lyric-named look",
   "generation_contracts.md → шаг 10 → check gaze legibility at phone scale for lyric-named looks"
  ],
  "statusGuess": "new",
  "lens": "passport"
 },
 {
  "level": "macro",
  "domain": "result acting arc",
  "rule": "Задавайте в паспорте для каждого результата дугу выражения как в оригинале — удивление, переходящее в радость не позже ~0,3 с после ключевого спетого слова, с открытой и не закрытой руками зоной не менее 2 с — и не пропускайте через гейт кадр результата, который открывается испугом под подписью о результате.",
  "why": "У оригинала улыбка появляется через 0,3 с после «almost gone», у Taras удивление переходит в улыбку на вокальной паузе; у нас оба результата открываются испугом (сведённые брови, рука на горле; открытый рот, руки закрывают шею с 175,5 с), а паспорт вообще не содержит слов о выражении лица.",
  "evidence": [
   "targ-S33 136.3–143.0: caption 'The melasma was almost gone' 138.4–140.2, smile 140.5, eyes-closed grin 141",
   "taras-S35 147–152.5 surprise→toothy grin on the vocal rest 148.92–150.72 (summary.json gapsOver1s)",
   "film-S26b 155.7–162.9 'eyes widen and brows knit (157.5), hand to neck…'; film-S28 173.7–175.5 'wide-eyed open-mouth surprise', hands to neck 175.5; film/sheets/sheet06.jpg 02:36–02:42, 02:54–02:59",
   "problem-passport.json word counts: expression 1, smile 0, joy 0, surprise 0; shot-plan plannedReaction for targ-s031/s033 is the generic boilerplate"
  ],
  "placement": [
   "references/problem-passport-contract.md → states[] → expressionArc {onWord, from, to, maxLagSec: 0.3, unoccludedHoldSec: 2} for intermediate and resolved",
   "tools/problem_quality.py → preflight → require expressionArc on every intermediate/resolved beneficiary unit",
   "tools/targ_film_prepare.py → directions → include the arc and 'hands off the zone until after the held view'",
   "references/film-quality-meta-rules.md → F05 (улыбка дополняет результат) + F11 → add: результат играется положительно на ключевом слове; generation_contracts.md шаг 10 → alarm face under a result caption = fail"
  ],
  "statusGuess": "new",
  "lens": "passport"
 },
 {
  "level": "macro",
  "domain": "mirror compare continuity",
  "rule": "Ставьте зеркальные биты «до / промежуточно / после» в одной ванной с одинаковой мизансценой, как оригинал (targ-S31 и S33 — та же OTS-раскадровка), объединяя их в одну группу непрерывности несмотря на разные сюжетные дни, и проверяйте matched-view фейд по обоим крайним кадрам экспорта, а не по плану.",
  "why": "Прямое сравнение оригинала держится на одинаковой раскадровке; у нас три разные ванные и запланированный 0,5 с matched crossfade в экспорте стал трёхкадровой вспышкой и ghost-диссолвом в иначе скадрированный CU.",
  "evidence": [
   "targ-S31 122.9–128.2 and targ-S33 136.3–143.0 'Same OTS blocking as S31' ('direct compare')",
   "film seg4: 'Three different bathrooms in one thread: S28 white-tile/wooden mirror, S29 mint wall, S35 arched gold mirror'",
   "film seg3: ghost dissolves 155.6–155.9 and 173.3–173.7, preceded by a ~3-frame flash of the wider framing",
   "shot-plan.json problemStateTransitionPlan: intermediate→resolved 173.232–173.732 '0.5s crossfade' (planned) vs export"
  ],
  "placement": [
   "references/problem-passport-contract.md → continuityGroups → allow a cross-day mirrorCompareGroup binding targ-s009/s030/s031/s033/s041 to one accepted set image",
   "references/localization-and-resolution-rules.md → Repeatable resolution lock, step 6 → add 'same set + same blocking' and verification of both fade endpoints on export frames",
   "tools/targ_film_build.py → transitions → assert near-identical framing on both sides of a state fade (frame diff) before accepting the crossfade"
  ],
  "statusGuess": "strengthen",
  "lens": "passport"
 },
 {
  "level": "micro",
  "domain": "recommender proof portrait",
  "rule": "На строке о безупречной коже наставницы давайте держанный портрет, где зона результата открыта целиком (для шеи — от ключиц до подбородка, без воротника), как портрет targ-S24 показывает всё лицо, и засчитывайте только такой кадр как доказательство рекомендателя.",
  "why": "Портрет наставницы в оригинале работает как «after»-референс; у нас на строке «шия гладенька» видна лишь полоска шеи над воротником — правило 21 выполнено на бумаге и невидимо на экране.",
  "evidence": [
   "targ-S24 86.03–90.33 MCU 'flawless, semi-photoreal skin' after the white wash",
   "taras-S25b 95.4–100.05 isolated mentor portrait",
   "film-S21b 108.3–112.5 zone 'partial - only the front of the neck above the collar'; film seg2 fix list: 'open the mentor's collar in S21b so the smooth neck is on screen'",
   "film-S21a 101.6–108.3 'neck partly under collar'"
  ],
  "placement": [
   "references/problem-passport-contract.md → participants[role=recommender] → proofShot {unitId, exposure: 'unoccluded zone', holdSec ≥ original portrait}",
   "tools/problem_quality.py → validate_plan → recommender without proofShot fails",
   "AGENTS.md → rule 21 → define 'visibly embody' as an unoccluded zone at the proof line",
   "tools/targ_film_prepare.py → directions for targ-s024 → collar open, zone in frame"
  ],
  "statusGuess": "strengthen",
  "lens": "passport"
 },
 {
  "level": "macro",
  "domain": "mentor authority localization",
  "rule": "Фиксируйте замену иностранного авторитета наставницы оригинала (кореянка «за пятьдесят с безупречной кожей») на украинскую героиню как явное версионированное владельческое решение в карте адаптации, сохраняя выводимую часть — ежедневную знакомость и видимое доказательство результата, — а не как автоматическое следствие правила 12.",
  "why": "Правило 12 требует украинский состав, а любимый владельцем референс сохраняет кореянку и согласует её с буллитом «Корейська формула»; эта линза не может решить конфликт, но выводимая часть авторитета (доказательный кадр + «бачу щодня») не зависит от этничности.",
  "evidence": [
   "taras ASR 92.3–100.1 'Кореянка, яку я бачу щодня, за п'ятдесят'; taras-S44 bullet 'Корейська формула — Арбутин і транексамова' (taras/sheets/sheet07.jpg 03:08–03:13)",
   "targ-S24 86–90.3 mentor portrait; film captions 105.5–112.5 'Там Ірина — бачу її майже щодня. Їй п'ятдесят п'ять — шия гладенька'",
   "AGENTS.md rule 12; references/localization-and-resolution-rules.md 'Region gate before casting or picture purchase'"
  ],
  "placement": [
   "references/localization-and-resolution-rules.md → Region gate → record mentorAuthorityDecision {kept-foreign | localized, ownerDecisionRef} per adaptation",
   "generation_contracts.md → шаг 4 adaptation-map → field for the decision and the retained authority devices (proof portrait, daily familiarity)",
   "AGENTS.md → rule 12 → note that the owner reference deviates and the decision is versioned, not inferred"
  ],
  "statusGuess": "conflict",
  "lens": "passport"
 },
 {
  "level": "macro",
  "domain": "product reveal and naming",
  "rule": "Показывайте упаковку на той же доле хронометража, что оригинал (targ 56 %), держите её «загадочным предметом» и пойте название бренда один раз там, где его поёт оригинал (последняя мысль после строки о самоценности, ≈97 %), а не превращайте передачу в чтение этикетки с брендом на 58 %.",
  "why": "Оба чемпиона называют бренд один раз в конце; мы называем на 58 % и читаем этикетку 9,2 с / 28 слогов с «near-tearful frown», затем снова на 94,6 %; загадка упаковки потеряна, а название перестаёт быть развязкой.",
  "evidence": [
   "lyrics/lyrics-advertorial.md §4: name sung targ 182.6 s (96.9 %), taras 189.9 s (97.8 %), ours 139.4 s (58.2 %) and 226.7 s (94.6 %)",
   "film-S25 137.8–149.6 captions 'goPure. Я стала читати:' 139.4–141.6, label read 141.6–150.8; film seg3: 'needs a curious rather than grieving face'",
   "targ-S29 109.4–118.8 captions 'pink tube / Korean writing, something I'd never tried'; taras-S30 117.5–126 'Рожевий тюбик корейські літери'"
  ],
  "placement": [
   "generation_contracts.md → шаг 4 adaptation-map → brandNamingPosition must equal the original's function (CTA) unless a 1:1 replacement of a mystery-object word",
   "​.agents/skills/song-clone-ua/SKILL.md → Стадия 2 → Бюджет строки → brand word count = original's count; label text is card content",
   "scripts/lines_check.py → count brand occurrences in sung text vs original; fail if greater",
   "references/problem-passport-contract.md → claimLimits → 'label/ritual text belongs to the card, not the lyric'"
  ],
  "statusGuess": "new",
  "lens": "passport"
 },
 {
  "level": "micro",
  "domain": "packshot legibility",
  "rule": "В момент передачи вставляйте один кадр реального пэкшота не короче вставки оригинала (targ: «SEORA» читается ~2,3 с; Taras 4,5 с) и никогда не полагайтесь на сгенерированную этикетку для читаемости бренда — в руке героини и в кадре презентёра этикетка должна быть композитом реального пэкшота.",
  "why": "Taras даёт 4,5 с читаемого пэкшота; у нас этикетка банки в кадре презентёра — AI-абракадабра рядом с чистым пэкшотом карты через 3 с; у оригинала тот же дефект (S42), его не копируем.",
  "evidence": [
   "taras-S31 126.0–130.5 INSERT 'HANBIT SKINCARE / ARBUTIN 7.0+TXA 4.0 CREAM' legible",
   "film seg4 defect: 'goPure jar label in S36 (226.57–235.2) is AI-garbled pseudo-text (only lid GOPURE readable)'",
   "film-S25 'GOPURE on cap band; body label small, only partly legible' 137.8–149.6",
   "targ seg4 defect: 'garbled secondary label on the tube (S42)'; targ-S29 label readable from ~116.5"
  ],
  "placement": [
   "tools/plan_film.py → shot-plan units targ-s029/s030 → packshotInsert {src: real packshot, holdSec ≥ original}",
   "tools/targ_film_build.py → label-bearing shots → composite the real packshot PNG over the jar with a legibility check on export frames",
   "references/film-quality-meta-rules.md → F03 / F13 → brand legibility check at hero size on export",
   "generation_contracts.md → шаг 11 → no generated label at hero size"
  ],
  "statusGuess": "strengthen",
  "lens": "passport"
 },
 {
  "level": "macro",
  "domain": "ending block budget",
  "rule": "Держите презентёра не дольше кадра оригинала (targ-S42 6,0 с; у нас 8,6 с) одним планом с одним жестом, карту — не короче карты оригинала (5,9 с; у нас 4,5 с), а спетый CTA — в бюджете оригинала (≈14 слогов / 5,9 с; у нас 40 / 12,7 с), позволяя последней строке дозвучать поверх карты.",
  "why": "Оба чемпиона заканчивают коротким презентёром и картой ≥ 5,75 с; Taras допевает бренд поверх карты; у нас 8,6 с презентёра с тремя жестами, 4,5 с перегруженной карты и нет воздуха после вокала.",
  "evidence": [
   "targ-S42 182.47–188.50 (6.0 s, tube upright + palm-down on 'link's below'); targ-S43 188.50–194.37 (5.9 s)",
   "taras-S43 184.21–188.42 (4.2 s, two-handed hold, no pointing); taras-S44 188.42–194.17 (5.75 s) with 'Крем називається Ханбіт…' 189.9–193.9 sung over the card, uncaptioned (taras/sheets/sheet07.jpg 03:09–03:13)",
   "film-S36 226.57–235.20 (8.6 s: points at jar, palm-up, points down at 232.2); film-S37 235.2–239.7 (4.5 s); film/sheets/sheet08.jpg 03:47–03:59",
   "lyrics-advertorial.md §6 CTA row: 14 / 14 / 40 syllables"
  ],
  "placement": [
   "tools/plan_film.py → shot-plan endingEditorialOverride → constrain heroRange ≤ source hero range and cardRange ≥ source card range",
   "​.agents/skills/song-clone-ua/SKILL.md → Стадия 2 → CTA syllable budget = original's CTA syllables",
   "scripts/lines_check.py → CTA syllable count vs original; fail above budget",
   "generation_contracts.md → шаг 11 validation → card duration and post-vocal room checked on export"
  ],
  "statusGuess": "new",
  "lens": "passport"
 },
 {
  "level": "micro",
  "domain": "end card content",
  "rule": "Стройте финальную карту как оригинал и Taras — вордмарк, заголовок, 3–4 буллита пользы с подстрочником-доказательством из permittedUk-фактов, пэкшот, кнопка CTA и одна мелкая строка раскрытия — без лирического бокса и без полосы дисклеймера на карте.",
  "why": "Карта оригинала несёт пользы + тюбик + LINKS BELOW, у Taras четыре буллита с подстрочниками; у нас нет буллитов пользы, а лирический бокс и полоса заполняют нижнюю треть карты.",
  "evidence": [
   "taras/sheets/sheet07.jpg 03:08–03:13: 'ЧИСТА ШКІРА. / ВПЕВНЕНА ТИ.', 'Щовечірній крем від пігментації.', 4 check bullets with sub-lines, tube, '▼ ПОСИЛАННЯ НИЖЧЕ ▼', tiny footer",
   "film/sheets/sheet08.jpg 03:55–03:59: goPure wordmark, 'Догляд за шиєю', jar, green button, offer lines + lyric caption box + grey strip",
   "film seg4: 'End card is 4.5 s … bottom third crowded by lyric box plus the persistent disclaimer strip'",
   "targ-S43 188.5–194.37 pink card, benefits left, tube right, 'LINKS BELOW' band"
  ],
  "placement": [
   "tools/targ_film_build.py → card HTML/CSS (.brand, h1, .card-cta, .offer, .disclosure) → add benefit bullets from product permittedUk; skip .caption clips whose start ≥ card start; end #fiction-note before the card",
   "generation_contracts.md → шаг 11 → card checklist (wordmark, headline, bullets, pack, CTA, one disclosure line)",
   "tools/plan_film.py → shot-plan unit targ-s043 plannedAction → list the card elements explicitly"
  ],
  "statusGuess": "strengthen",
  "lens": "passport"
 },
 {
  "level": "macro",
  "domain": "disclaimer placement",
  "rule": "Не пойте дисклеймеры (в оригинале и у Taras — ноль спетых) и не держите постоянную полосу раскрытия: размещайте раскрытие мелкой строкой на карте, как Taras, или полосой ≤ 3 с на кадре нанесения, вместо полосы с 149,63 с до конца (37,6 % хронометража).",
  "why": "Два спетых дисклеймера стоят в двух самых убедительных местах (передача и CTA), а полоса добавляет второй текстовый слой на 90 с, включая карту; чемпионы — ноль спетых и один футер.",
  "evidence": [
   "tools/targ_film_build.py line 371: #fiction-note data-start=\"149.63\" data-duration=\"{total-149.63}\" → 90.1 s of 239.7 s",
   "film captions 148.2–150.8 'Без обіцянок чудес', 230–233.7 'Результат у кожної свій' (lyrics-advertorial.md §6 disclaimer row: none / none)",
   "taras-S44 tiny footer only (taras/sheets/sheet07.jpg); film/sheets/sheet08.jpg strip on every frame 03:30–03:59"
  ],
  "placement": [
   "tools/targ_film_build.py → line 371 → #fiction-note data-start = card start (or application start with data-duration ≤ 3)",
   "references/problem-passport-contract.md → claimLimits → disclosurePlacement: 'card-footer' as the default",
   "​.agents/skills/song-clone-ua/SKILL.md → Стадия 2 → 'дисклеймеры и условия формы не поются'",
   "scripts/lines_check.py → flag disclaimer phrases in the sung text"
  ],
  "statusGuess": "new",
  "lens": "passport"
 },
 {
  "level": "micro",
  "domain": "claim grammar",
  "rule": "Сохраняйте пятичастную грамматику заявления оригинала — сюжетное число недель (вымышленная рутина, не обещание), то же существительное, один допустимый абсолютный глагол, один поведенческий якорь сравнения и одно поведенческое доказательство — хеджируя один раз одним словом и не повторяя «не так помітні» / «на вигляд».",
  "why": "Taras копирует все пять частей дословно; мы убираем число и якорь, дважды используем сравнительное «не так помітні» и дважды «на вигляд», раздувая результат до 61 слогов против 32; факт продукта разрешает вымышленную многонедельную рутину, а поёт ли владелец число — открытое решение.",
  "evidence": [
   "lyrics-advertorial.md §6 table: 'Через три тижні… стали світлішими' / 'На шостому тижні… майже не було / десять років без тонального' vs ours 'Минуло кілька тижнів… не так помітні' (160.3), 'Вона помітно гладша, / На вигляд — пружніша. / І лінії впоперек уже не так помітні' (177.4–183.6)",
   "product JSON permittedUk 'Допомагає шкірі шиї виглядати гладкішою та пружнішою'; time fact 'fictional weeks-long routine and distinct intermediate state, never a deadline guarantee'",
   "film-S29 183.6–189 scarf into drawer = behavioral proof (keep)"
  ],
  "placement": [
   "generation_contracts.md → шаг 4 adaptation-map → claim table with the five slots for each result line",
   "references/adapt-phase-contract.md → Контракт каждого шага (advertorial) → claim-grammar check before song purchase",
   "references/problem-passport-contract.md → claimLimits → allowed absolute verbs list derived from permittedUk; ownerDecision flag for singing a story-time number",
   "scripts/lines_check.py → repeated-hedge detector ('не так помітні', 'на вигляд' > 1)"
  ],
  "statusGuess": "strengthen",
  "lens": "passport"
 },
 {
  "level": "micro",
  "domain": "daughter beat",
  "rule": "В бите дочери давайте физический контакт ребёнка с зоной результата (для шеи — объятие за шею, щека к шее), как в оригинале рука к груди и у Taras ладонь на щеке, и убирайте дубль испуга на комплимент.",
  "why": "У Taras прикосновение дочери продаёт «ти інша» без деталей кожи; у нас жест ладонью, испуг «over-scaled for a compliment», нет контакта и 3,6 с строки «минув час» поверх не той картинки.",
  "evidence": [
   "taras-S34 139–147: daughter's hand on the mother's cheek 145–146 ('best acting beat')",
   "targ-S32 128.2–136.3 hand-to-chest",
   "film-S27 162.9–173.2: shock face 167.5–169, toothy grin 172, no contact; film/sheets/sheet06.jpg 02:48–02:49",
   "film seg3: caption 045 'Минув ще час…' plays 3.6 s over the living-room CU"
  ],
  "placement": [
   "tools/plan_film.py → shot-plan unit targ-s032 → plannedAction names the contact with the zone and an expressionArc (fond→joy, no shock)",
   "references/film-quality-meta-rules.md → F11 (взаимодействие) → contact with the result zone as the interaction",
   "generation_contracts.md → шаг 10 → shock take under a compliment caption = fail"
  ],
  "statusGuess": "new",
  "lens": "passport"
 },
 {
  "level": "micro",
  "domain": "application beat",
  "rule": "Сохраняйте бит нанесения длиной 4–6 с с не менее чем тремя микродействиями и видимой зоной под кремом, как в оригинале (targ-S30 4,1 с, четыре микродействия), — наш S26a (6,1 с) уже соответствует, не сокращайте его до 1,7 с, как Taras.",
  "why": "Taras режет нанесение до 1,7 с («too short to sell no sting»); наше нанесение честное и читаемое — это уже сильная сторона.",
  "evidence": [
   "targ-S30 118.8–122.9 four micro-actions",
   "taras-S32 130.5–132.0 (1.7 s)",
   "film-S26a 149.6–155.7 'neck fully exposed with crepey texture … white cream'; film/sheets/sheet06.jpg 02:30–02:35"
  ],
  "placement": [
   "tools/plan_film.py → shot-plan problemStateTransitionPlan.applicationSequence (already present) → keep",
   "references/film-quality-meta-rules.md → F05 (already present) → keep"
  ],
  "statusGuess": "exists",
  "lens": "passport"
 },
 {
  "level": "micro",
  "domain": "payoff close-ups",
  "rule": "Сохраняйте в развязке все крупные планы с разрешённой зоной и круглую вставку памяти оригинала (targ-S39), как уже сделано в S31–S36 нашего фильма, и не сводите биты развязки к тексту, как Taras.",
  "why": "Развязка — не место, где мы проигрываем: наша развязка ближе всего к targ-s035..s043, а Taras теряет три ключевых бита развязки, оставляя их только в подписях.",
  "evidence": [
   "targ-S39 memory inset 169.9–173.4; film-S33 inset 214.9–218.4; film seg4: 'Fidelity to targ s033-s043 is close to 1:1'",
   "taras seg4: 'three of the source's key payoff beats survive only as text' (S39 solo compliment, S40 no car/inset)",
   "film S31–S36 V-neck resolved zone in every close-up; taras 'every face shot from 150 to 188 shows clean cheeks'"
  ],
  "placement": [
   "tools/plan_film.py → shot-plan overlays targ-memory-inset (already present) → keep",
   "AGENTS.md → rule 21 (result in every appearance) → already covers → keep"
  ],
  "statusGuess": "exists",
  "lens": "passport"
 },
 {
  "level": "macro",
  "domain": "passport sung noun",
  "rule": "Фиксируйте в паспорте одно спетое существительное проблемы и один глагол результата, которые лирика повторяет в диагнозе, зеркале и результате, и держите спетый диагноз в слоговом бюджете диагноза оригинала (targ 25; Taras 18; у нас 29 плюс хедж «на вигляд»).",
  "why": "Строка problem паспорта называет три подпризнака, и лирика называет три плюс хедж; Taras обходится одним существительным в 18 слогах и эхом «плями → плями → пігментації»; одно существительное экономит слоги и делает диагноз показываемым.",
  "evidence": [
   "problem-passport.json problem: 'Coarse crepey neck texture, lax-looking skin and conspicuous transverse neck lines'",
   "lyrics-advertorial.md §5: targ 25 syl (3 zones), taras 18 syl (1 zone), ours 29 syl + 'на вигляд' hedge; film captions 92.0–98.9 'На шиї — дрібні заломи, / Лінії впоперек. Шкіра на вигляд в'яла.'",
   "noun echo: taras 'плями → плями світлішими → пігментації не було'; ours 'заломи → заломи → лінії впоперек'"
  ],
  "placement": [
   "references/problem-passport-contract.md → Versioned JSON contract → sungProblemNoun and sungResultVerb fields",
   "tools/problem_passport.py → prompt_context → pass both into the gen2 musicPrompt (PROBLEM_PASSPORT)",
   "​.agents/skills/song-clone-ua/SKILL.md → Стадия 2 → diagnosis syllable budget = original's diagnosis syllables",
   "scripts/lines_check.py → noun-echo check across diagnosis / mirror / result lines"
  ],
  "statusGuess": "new",
  "lens": "passport"
 },
 {
  "level": "micro",
  "domain": "diagnosis shot",
  "rule": "Сохраняйте наезд в зеркало на слове диагноза (оригинал targ-S21 на «Melasma», Taras S23 палец на пятне на «Пігментні плями», наш S19 ECU на «На шиї») как единственное обязательное движение камеры блока проблемы и защищайте его от перемонтажа.",
  "why": "Это единственное место, где все три версии совпадают и где наш фильм уже делает «the one true camera move… pays off»; правило нужно, чтобы исправления других битов его не сломали.",
  "evidence": [
   "targ-S21 67.1–72.2 car-mirror push-in lands the patches on 'Melasma' (69.2)",
   "taras-S23 84.5–90.05 finger lands on the patch at 87.6–88.3 on 'Пігментні плями' (88.08)",
   "film-S19 89.6–93.8 push-in to the ECU on 'На шиї' (92.0); film seg3 praise"
  ],
  "placement": [
   "tools/plan_film.py → shot-plan unit targ-s021 (already present) → keep the push-in and the word anchor",
   "references/film-quality-meta-rules.md → F11 (already present) → keep"
  ],
  "statusGuess": "exists",
  "lens": "passport"
 },
 {
  "level": "macro",
  "domain": "harness/measurement",
  "rule": "H01 Measure the ORIGINAL's container before writing: every canonical original gets a SHA-bound containerBudget-vNN.json (sung vowel-syllables, per-section syllables and syl/s, breath-phrase count, rests ≥1 s, voiced fraction, vocal range/RMS-std/on-note, shot-length histogram, inserts, camera-move share, CU/MCU share, before-state zone-visible share and first legible instant, product-reveal %, brand tokens, CTA syl/s, card duration, caption geometry). Every later ratio gate reads this file, and each gate record carries sourceValue/adaptedValue/ratio/threshold/derivation.",
  "why": "Every existing gate is N/N completeness (43/43 units, 217/217 events, 65/65 thoughts); AGENTS rule 5 / generation_contracts:155–157 say 'derive from the original' but nothing records the original's quantities as a budget, so no ratio gate can exist.",
  "evidence": [
   "runs/video-dna-v1/targ/analysis.json has 43 shots and 65 captions with times but no derived budget block",
   "film-handoff-v02.json coverage: mergedSourceMicroshots 0, adaptedSubevents 217; coverage-map-v06.json allThoughtsMapped",
   "measurements/summary.json targ/taras/film: syllables 645/620/762, medianShotSec 4.48/4.17/5.97, gapsOver1s 1/5/0",
   "film-quality-meta-rules-v01.json requiredGateRecordFields has no source/adapted ratio fields"
  ],
  "placement": [
   "new tools/video_dna_budget.py (reuse runs/difference-dna-v1/measurements/{audio_dna,shots,cut_align}.py)",
   "references/video-dna-contract.md step 6 output column + level-7 row (containerBudget)",
   "generation_contracts.md step 2 output; pipeline.json containerBudgetRequired/containerBudgetTool",
   "references/video-dna-canonical-v01.json containerBudget binding per original",
   "references/film-quality-meta-rules-v01.json requiredGateRecordFields += sourceValue, adaptedValue, ratio, threshold, derivation"
  ],
  "statusGuess": "new",
  "lens": "harness"
 },
 {
  "level": "macro",
  "domain": "harness/song-budget",
  "rule": "H02 Sung-syllable cap: tools/song_budget.py runs in scripts/gen2.py song_fields() on the ledger-bound performance lyric and fails when vowel-syllables > containerBudget.syllables × 1.05 unless SONG_BUDGET_DECISION names a versioned director decision; CONTAINER_BUDGET is a required env var like STRESS_LEDGER; DUR defaults to the original's sung span, not 240; gen2 writes <tag>-container-request-audit.json.",
  "why": "All three versions sing at ≈3.2 syl/s, so surplus syllables become runtime 1:1 (+140 syllables = +45 s). The skill forbids a floor ('no mandatory +15–25 %') but sets no cap, DUR is hard-coded 240, the planning band [220,245] was frozen without a rule, and critic_music's form axis weights duration at ≈0.",
  "evidence": [
   "summary.json: sung text 770 syl vs original ≈625–645, Taras 626; syllablesPerMin 205/192/191; sungSpan end 188.6/193.9/239.4",
   "scripts/gen2.py:64 duration = int(os.environ.get('DUR','240'))",
   "song-v02/musical-phrasing-plan-v08.json densityAndDuration intentSeconds 230, reviewPlanningBandSeconds [220,245], 'not duration arithmetic'",
   "scripts/critic_music.py:206–208 form axis |dur−R.dur|/20 at 0.3 weight",
   "song-clone-ua/SKILL.md:238–240, 261–269 (no cap; +19 % request scored best); SKILL.md:77–78"
  ],
  "placement": [
   "scripts/gen2.py song_fields() after checked_text(lyrics), before stress_fields(); main() audit write",
   "new tools/song_budget.py",
   "song-clone-ua/SKILL.md 'Бюджет строки' (:240–242) add global cap; Stage 3 env template (:284–286) add CONTAINER_BUDGET=",
   "references/adapt-phase-contract.md step 4 validation column",
   "AGENTS.md rule 18 one clause: the original song sets the budget and clock"
  ],
  "statusGuess": "strengthen",
  "lens": "harness"
 },
 {
  "level": "macro",
  "domain": "harness/section-clock",
  "rule": "H03 Per-section budget ≤ 1.15× the original's section syllables and drift returns to the clock: musical-phrasing-plan structuralDevelopment[] rows gain sourceSyllables/sourceSeconds/newSyllables/plannedSeconds/sourceSylPerSec/plannedSylPerSec/offsettingCut; song_budget --sections fails a section >1.15× without an offsettingCut and warns when phrase-start drift vs the original does not return to ≤3 % of runtime by the CTA; the original's fastest section stays its fastest.",
  "why": "Taras borrows +18 s on the scan and repays it (final +5.4 s); ours is a monotone staircase to +50.9 s and flattens the density profile (collapse 1.62×).",
  "evidence": [
   "lyrics/lyrics-alignment.md §3/§6 drift table: Taras peak +18.2 s at card 23, end +5.4 s; ours +12.4/+21.8/+31.6/+40.6/+50.9",
   "lens-songwriter section budgets: mentor 149 vs 118 syl, results 127 vs 88, CTA 40 vs 15",
   "musical-phrasing-plan-v08.json structuralDevelopment rows carry only text directions"
  ],
  "placement": [
   "musical-phrasing-plan-vNN.json schema (structuralDevelopment rows)",
   "references/adapt-phase-contract.md step 4 output and step 7 'song→visual timing map must show the drift curve'",
   "song-clone-ua/SKILL.md 'Длина против смысла' (:253–259)",
   "tools/song_budget.py --sections"
  ],
  "statusGuess": "new",
  "lens": "harness"
 },
 {
  "level": "macro",
  "domain": "harness/coverage-semantics",
  "rule": "H04 Coverage is of dramatic/selling FUNCTIONS, not thoughts: adaptation-map gains functions[] (id, sourceCards, speechAct, protected, budgetSyl = source × 1.15) and textureCuts[]; coverage.allThoughtsMapped is replaced by allFunctionsMapped + protectedFunctionsIntact; protected reversal beats (stop-verdict, discard-rest, self-worth parallel) must map to a phrase with the same speech act within budget; thoughts may be cut when the function survives, a function never.",
  "why": "Rule 18 'preserve full selling logic' was executed as all 65 thoughts mapped with zero cuts plus added product text (+≈80 syllables), while two reversal beats vanished; Taras drops 4 cards and condenses 20 yet keeps all 17 functions.",
  "evidence": [
   "coverage-map-v06.json coverage.allThoughtsMapped; lyrics-alignment: ours 41 literal/14 rephrased/9 expanded/1 added/0 dropped; Taras 38/20/2/4 dropped",
   "card 34 → 'А шия? Їй щодня потрібен власний догляд' (no stop), card 38 → application instructions (no throw-away); Taras 'зупинись, усе це робить тільки гірше' 114.2–117.7, 'Решту викинь' 125.4–127.2",
   "lyrics-advertorial.md §2: all 17 functions present in all three; ours has no negative cell"
  ],
  "placement": [
   "adaptation-map schema (runs/.../adaptation-map-v07.json rows) + functions[]/textureCuts[]",
   "references/adapt-phase-contract.md step 3 validation ('все исходные функции покрыты' → functions table)",
   "AGENTS.md rule 18 and references/owner-generation-rules.md:48–61 one clause: coverage of functions, not words",
   "song-clone-ua/SKILL.md Stage 1 'Личная история' (:216–217) lists the reversal beats; tools/song_budget.py --functions"
  ],
  "statusGuess": "conflict",
  "lens": "harness"
 },
 {
  "level": "macro",
  "domain": "harness/cta-compliance-placement",
  "rule": "H05 Sung CTA ≤ original CTA syllables × 1.1 and ≤ original CTA seconds; brand sung ≤ original brand-token count and first sung brand position ≥ the original's position ratio; no label reading. claimLimits[] and product fictionalDisclosure gain placement: end-card | on-screen-strip | sung (default end-card); song_budget --cta fails if any non-sung-placement phrase is in the sung text; the end card carries ritual steps, 'results vary' and form fields.",
  "why": "The only unmistakably 'ad' passage in ours is a 40-syllable CTA carrying a disclaimer and a lead-form instruction; both champions sing 14–15 syllables and put caveats on the card. The harness has no notion of where compliance lives.",
  "evidence": [
   "CTA 15 syl/5.9 s (targ 182.6–188.5) · 14/4.0 s (taras 189.9–193.9) · 40 (42 with brand)/12.7 s (film 226.7–239.4)",
   "brand sung once at 96.9 %/97.8 % vs twice at 58.2 % and 94.6 %; label read aloud 141.6–150.8 (28 syl)",
   "sung disclaimers 'Без обіцянок чудес' 148.2, 'Результат у кожної свій' 230.0; taras-S44 four benefit bullets + footer",
   "song-clone-ua/SKILL.md:108–137 brand gate says where, not how often; problem-passport-contract.md claimLimits have no placement"
  ],
  "placement": [
   "references/problem-passport-contract.md claimLimits[].placement",
   "song-clone-ua/SKILL.md 'Гейт бренда' bullet: brand sung once, in the CTA; label text shown, not sung",
   "references/adapt-phase-contract.md step 4 'слышимый CTA' → CTA ≤ original budget, one breath",
   "tools/targ_film_build.py end-card block (:338–340) renders the placed copy; tools/song_budget.py --cta"
  ],
  "statusGuess": "conflict",
  "lens": "harness"
 },
 {
  "level": "macro",
  "domain": "harness/rests",
  "rule": "H06 Write the air in and gate on it: the phrasing plan gains rests[] (source time, beat, reason) with count ≥ the original's rests ≥1 s placed at its heaviest beats; lyrics_uk.txt marks each with a bracketed direction; the STYLE template asks for instrumental breaths; Stage 4 acceptance requires vocal-stem gaps ≥1 s ≥ original count and voiced fraction ≤ original + 0.03; critic_music gains advisory axes rest_gaps/voiced_fraction; the build shows no caption during a rest and the handoff maps each rest to a wordless held frame (restBeats[]).",
  "why": "Taras's most remembered images sit on its five rests; our master has zero gaps >1 s, the STYLE asked for 'connected breath, no spoken prose', and no tool measures rests, so the edit cannot breathe.",
  "evidence": [
   "summary.json gapsOver1s 1/5/0; voicedFrameFraction 0.708/0.577/0.787",
   "lyrics-songcraft: rests ≥0.22 s 16/28/18 (11.1/19.9/8.7 s); longest unbroken run 42.8 s (81–124 s)",
   "taras-S07 19.5–22.75 empty doorway over gap 21.1–22.2; taras-S17 ECU scan over gap 58.3–60.1",
   "song-v02/style-v04.txt 'connected breath, no spoken prose'; adapt-phase-contract.md:68–69 lists 'паузы' with no tool"
  ],
  "placement": [
   "song-clone-ua/SKILL.md Stage 2 (rests[]), Stage 3 STYLE template (:281–283), Stage 4 table (:299–323)",
   "scripts/critic_music.py new advisory B axes rest_gaps, voiced_fraction (not KEY)",
   "references/adapt-phase-contract.md step 7 validation",
   "tools/targ_film_prepare.py restBeats → insert specs; tools/targ_film_build.py caption loop (:333–336)"
  ],
  "statusGuess": "new",
  "lens": "harness"
 },
 {
  "level": "macro",
  "domain": "harness/music-container-vs-clone",
  "rule": "H07 Score the container, make key/tempo advisory: critic_music KEY keeps timbre/cohere/intro_start; tempo and intro_mode become advisory when director-decision-cover-vNN.json records a deliberate groove/mode choice; container axes (syllables, phrase count, rests, range ≥ original − 1 st, RMS std ≥ original, on-note ≤ original + 0.03) become gates; the STYLE template's '<BPM> bpm, <key>' fields become optional and 'phrase clock of the original, spoken-sung storytelling, energy arc' required. The own-original upload (AGENTS r2/r16) is unchanged.",
  "why": "The owner prefers a song that is not a musical clone (A minor, 10 st, DTW near chance) but keeps the original's container; our critic would score a Taras-like song as a failure, and our clone is the flattest delivery of the three.",
  "evidence": [
   "measurements/music-similarity.json: taras best transposition 10 st, DTW 0.0771 untransposed (baseline 0.0775), 0.0687 at best; film 0 st, 0.0518",
   "summary.json vocals: range 14.1/15.1/12.5 st, RMS std 5.89/6.57/5.63 dB, on-note 0.799/0.73/0.823",
   "scripts/critic_music.py:24 KEY = timbre, cohere, intro_mode, intro_start; :199–202 tempo/intro_mode scoring",
   "song-clone-ua/SKILL.md:202–204 'жанр… дословно', :281–283 STYLE template; request targ_r2_v6_cover03 AW 0.85/SW 0.65/WC 0.2"
  ],
  "placement": [
   "scripts/critic_music.py KEY list and new axes",
   "song-clone-ua/SKILL.md Stage 0 (:202–204) and Stage 3 template",
   "generation_contracts.md step 6 'жанр, подача' → 'фразовый ритм, подача, дуга энергии'",
   "references/adapt-phase-contract.md 'Музыкальная проверка' tolerances (:67–71)"
  ],
  "statusGuess": "conflict",
  "lens": "harness"
 },
 {
  "level": "micro",
  "domain": "harness/phrase-unit",
  "rule": "H08 One lyric line = one musical phrase (one breath); target phrase count = original breath-phrase count × 0.55–0.70 for Ukrainian with the derivation recorded; song_budget --phrases warns on phrases <8 or >18 syllables; captionAnchors and caption cards consume sung phrases, not sourceCaptionId; coverage-map rows may map several cards to one phrase.",
  "why": "The tooling keys everything 1:1 to caption IDs (coverage-map rows, captionAnchors, lyrics-map), which is where connective words and extra breaks come from; AGENTS 18 already says rows do not dictate boundaries but nothing enforces the phrase as the unit.",
  "evidence": [
   "film: 65 caption-keyed lines / 98 phrases / 36 thoughts (song-v02/lyrics-phrases-v06.json); Taras 43 phrases, median 14 syl / 4.5 s",
   "lyrics-alignment §7 merges (1+2, 9–11, 15+16, 42–44, 53–55, 59–61, 63+64)",
   "tools/targ_film_prepare.py:94 'captionAnchors': h['actualSongLineAnchors'] (per caption)"
  ],
  "placement": [
   "song-clone-ua/SKILL.md 'Что считать строкой' (:176–177) and Stage 2 (:248–251)",
   "references/adapt-phase-contract.md step 4 output (lyrics_uk.txt = phrase per line)",
   "tools/targ_film_prepare.py captionAnchors consumer; tools/verify_adapt_handoff.py",
   "tools/song_budget.py --phrases"
  ],
  "statusGuess": "strengthen",
  "lens": "harness"
 },
 {
  "level": "micro",
  "domain": "harness/singability-lint",
  "rule": "H09 tools/song_stress.py emits WARN rows from the acute positions it already parses: phrase opens with ≥2 unstressed syllables (≤10 % of phrases allowed), phrase-final word ≥5 syllables with non-penultimate stress, clitic clusters (й+consonant, та ж, із с-); the phrasing plan's 'breath boundary' becomes a required per-phrase field; WARNs are written to the stress request audit and unresolved WARNs need a decision before purchase.",
  "why": "The ledger validates coverage, never phrase shape; ours opens 10/98 phrases on pickups the singer must swallow and ends on hard monosyllables, and the audits heard но́ву/прожи́ли.",
  "evidence": [
   "lens-songwriter: 10/98 phrases with ≥2 pickups; phrase-final masc/fem/dact taras 9/23/11 vs ours 16/42/7",
   "'Та ж жінка' 211.4, 'ім'я й телефон лиши' 233.7, 'перепробувала' line-final twice",
   "tools/song_stress.py classifies clitic/vowelless and parses acutes but returns no warnings; references/ukrainian-stress-contract.md:50–52 'breath boundary' is prose"
  ],
  "placement": [
   "tools/song_stress.py validate() return warnings[]; scripts/gen2.py writes them into -stress-request-audit.json",
   "references/ukrainian-stress-contract.md 'Stress changes the composition' (add the two tests)",
   "song-clone-ua/SKILL.md Stage 2 checklist"
  ],
  "statusGuess": "strengthen",
  "lens": "harness"
 },
 {
  "level": "micro",
  "domain": "harness/register-lint",
  "rule": "H10 song_budget --register on the plain lyric: present-tense narration verbs only inside «» or with a habitual marker; per 10 phrases ≤2 dashes, ≤1 quotation pair, 0 colons before reported speech; a banned print/ad word list with replacements in a new references/song-register-uk.md, extendable per product.",
  "why": "Same beats, two registers: ours flips to print/ad wording exactly where product text was added; Taras and the original stay colloquial past-tense SVO; no register or punctuation lint exists.",
  "evidence": [
   "sung text: 21 dashes, 8 «, 8 colons vs Taras 0/0/0; 'Стою, дивлюся', 'Кажу:' vs 'Я стояла', 'Спитала'",
   "banned words in ours: авто, кохав, бліда тінь, опорою, на вигляд, власний догляд, обіцянок (lyrics-songcraft §2.4–2.5)"
  ],
  "placement": [
   "new references/song-register-uk.md beside references/pronunciation-traps.md",
   "song-clone-ua/SKILL.md Stage 2 'Ловушки регистра' next to 'Ловушки произношения' (:275–276)",
   "references/adapt-phase-contract.md step 4 validation; tools/song_budget.py --register --punctuation"
  ],
  "statusGuess": "new",
  "lens": "harness"
 },
 {
  "level": "micro",
  "domain": "harness/concreteness-lint",
  "rule": "H11 adaptation-map gains numericAnchors[] (source number, preserve/replace, reason, fact ID) and motifs[] (source word, positions, adapted word, required ≥ N−1 identical); song_budget --numbers --motifs --names fails 'кілька' where the map has a number, motif count < N−1, product-word tokens > story-motif tokens, and a sung proper name for a role the source leaves unnamed (localization mainRoles[] become {id, namedInSource, sungName}).",
  "why": "Numbers and refrains are the cheapest concreteness; ours blurred the timeline, lost 'Потім' ×5, replaced the character motif with a product motif and named the mentor, which rule 12 never required.",
  "evidence": [
   "lyrics-songcraft §2.6/§2.8/§2.11: Потім 4/0, сказала 4/1, шия ×10, догляд ×4, go pure ×2; 'Минуло кілька тижнів' for 'Three weeks later'",
   "scripts/lines_check.py norm maps only 16/34/60; tools/song_stress.py tokens() rejects digits",
   "runs/.../localization-v01.json 'Original Korean mentor→mature Ukrainian peer Iryna55'; sung 'Там Ірина' 105.4–108.7"
  ],
  "placement": [
   "adaptation-map schema numericAnchors[]/motifs[]",
   "references/adapt-phase-contract.md steps 2/3 role table (namedInSource)",
   "references/localization-and-resolution-rules.md region gate (:13–21) one sentence: naming is not localization",
   "scripts/lines_check.py norm number map; tools/song_budget.py"
  ],
  "statusGuess": "new",
  "lens": "harness"
 },
 {
  "level": "macro",
  "domain": "harness/film-unit-stretch",
  "rule": "H12 Each handoff unit records stretchRatio = target song seconds / source shot seconds; ratio > 1.25 requires coverageSplit (insert, reverse, push-in beat), never a longer hold; establishing/transitional units keep ≤ 1.25 or are dropped with a decision; total retained/source ≤ 1.05. Checked in verify_adapt_handoff and again in targ_film_prepare before any spec is written.",
  "why": "The handoff's monotone interpolation maps one spec per source unit to song time, so a 23 % longer song became 23 % longer holds by construction; the only length check is a flat 15 s ceiling. The ratio is computable from the handoff before purchase.",
  "evidence": [
   "computed from film-handoff-v02.json: 11/43 units ≥1.5× (targ-s012 2.36, s020 2.15, s004 2.08, s028 2.00, s011/s014 1.84, s017 1.66, s034 1.61, s010 1.57, s013/s033 1.54)",
   "tools/targ_film_prepare.py:109–110 gen_duration = max(4, ceil(duration)); assert ≤ 15; tools/plan_film.py:51; tools/film_provider.py:127",
   "targ-S12 2.0 s → film-S12 4.5 s; targ-S13 4.07 → 6.5 s; targ-S20 3.93 → film-S18 8.0 s ('drags')"
  ],
  "placement": [
   "tools/targ_film_prepare.py unit loop after :109 (assert or split)",
   "tools/verify_adapt_handoff.py; handoff schema stretchRatio/coverageSplit",
   "generation_contracts.md step 8 validation column",
   "references/film-quality-meta-rules.md F02 (:29–38): rhythm derived from source shot lengths, not song time"
  ],
  "statusGuess": "new",
  "lens": "harness"
 },
 {
  "level": "macro",
  "domain": "harness/rhythm-gate",
  "rule": "H13 New gate G08 (rhythm) between G06 and G07: tools/film_rhythm.py computes the export's shot-length distribution against the original — median ≤ original × 1.15, count(≥8 s) ≤ original's, count(≤2 s) ≥ original's × 0.7, wordless inserts ≥ original's, camera-move share ≥ original's — and prints the histogram beside the original's; a failure blocks G07.",
  "why": "Taras keeps the original's rhythm at equal length; ours reads as a slideshow, and no gate looks at distribution.",
  "evidence": [
   "summary.json medianShotSec 4.48/4.17/5.97, p90 8.77/7.08/10.9",
   "lens-editor §0: ledger shots ≥8 s 4/3/9, ≤2 s 7/5/3, camera moves 10/44, 25/47, 12/44, inserts 2/2/1"
  ],
  "placement": [
   "references/film-quality-meta-rules.md gates table (:200–208) + -v01.json gates[] G08 with H01 record fields",
   "new tools/film_rhythm.py (reuse measurements/shots.py + export ledger); tools/build_film_report.py / targ_film_report.py rhythm row",
   "pipeline.json filmRhythmGate"
  ],
  "statusGuess": "new",
  "lens": "harness"
 },
 {
  "level": "macro",
  "domain": "harness/coverage-split",
  "rule": "H14 A unit with retained > 6 s and ≥2 facial-reaction/scan/response subevents is prepared as ≥3 setups (two-shot + a single/POV per reactor), each cut ≤0.5 s from its key word; every lyric-named reaction gets a single ≤ MCU, ≤4.5 s, judged legible on a 480 px proxy; handoff units gain coverageSetups[] and prepare emits one spec per setup.",
  "why": "Taras covers the sympathy sting with five setups; our single 12 s two-shot drifts identity and ends on a cartoon face, and 'looked at my neck' was never staged; F11 asks for reactions but one spec per unit cannot host them.",
  "evidence": [
   "taras-S16 53.8–56.25 → S17 ECU scan 56.25–60.65 → S18 → S19 POV → S20 (21.4 s, 5 setups)",
   "film-S16 62.4–74.4 one take; film-S22 112.5–122.4 WS; film-S31b 198.7–203.3 'здивування' over closed eyes",
   "handoff adaptedSubevents[].type includes facial-reaction/scan/response (film-handoff-v02.json)"
  ],
  "placement": [
   "tools/targ_film_prepare.py unit loop (spec per setup, sizeMax)",
   "handoff schema coverageSetups[]",
   "references/film-quality-meta-rules.md F11 (:115–121) add 'a reaction named by the lyric gets its own single'",
   "public-targ-film/evidence/full-film-review.json eventChecks legibleAt480px"
  ],
  "statusGuess": "new",
  "lens": "harness"
 },
 {
  "level": "micro",
  "domain": "harness/inserts",
  "rule": "H15 adaptedSubevents[].type = object-insert produces its own ≤2.5 s spec cut on the sung noun; each ≈30 s story block has ≥1 wordless insert; total inserts ≥ the original's; a unit stretched under H12 may spend its surplus on an insert.",
  "why": "Inserts turn a caption line into a picture without a face; F02 only preserves the source's inserts; ours has one insert and it is 6.8 s.",
  "evidence": [
   "taras-S06 hands/rings 1.4 s, S07 empty doorway, S29 1.7 s punch, S31 packshot 4.6 s, S32 application 1.7 s",
   "targ-S15, S27, S34 bin + empty frame, S41 2.1 s; film only S15 tilt and S23 6.8 s still-life; shots ≤2 s 7/5/3"
  ],
  "placement": [
   "handoff/shot-plan subevent type; tools/targ_film_prepare.py",
   "references/film-quality-meta-rules.md F02 add 'add one where the adapted line is longer than the source shot'",
   "G08 row (H13)"
  ],
  "statusGuess": "strengthen",
  "lens": "harness"
 },
 {
  "level": "macro",
  "domain": "harness/problem-visibility-budget",
  "rule": "H16 Passport gains visibilityBudget {beforeStateZoneVisibleShareMin = original × 0.85, firstLegibleInstantMaxSec = original first-yes + 5 % of runtime, minSizeForCount = MCU, zoneMinFrameHeight = 0.15}; problem_quality.validate_export sums problemRegionVisible over before-state unit durations and fails below the share; specs with problemVisible get sizeTarget ≤ MCU; every sung line naming the problem lands on a shot where the zone is the largest element.",
  "why": "Rules 11/21 count the state and the END; problemVisible/problemRegionVisible are per-shot Booleans with an accepted reason, so a film that hides the zone in most before shots passes. The champions show the defect ≈60 % of the before phase from 0–11 s.",
  "evidence": [
   "lens-passport §0 spot-checked: before-state zone yes 57.5 s/65 % · 48.5 s/59 % · 28.5 s/30 %; first yes 0.0 · 10.8 · 52.0 s; longest before gap 12.5 · 15.0 · 27.0 s",
   "lens-editor: before shots with zone yes 12/21 · 12/21 · 4/19; film-S09 'too faint at phone size', S14 covered",
   "tools/problem_passport.py:313–321 Boolean problemVisible; tools/problem_quality.py:174–178 Boolean problemRegionVisible + visibilityReason"
  ],
  "placement": [
   "references/problem-passport-contract.md video-spec bullets (:90–96) and v2 section",
   "tools/problem_quality.py validate_plan (require budget) and validate_export (compute share after :174–178)",
   "tools/targ_film_prepare.py sizeTarget in directions",
   "AGENTS.md rule 21 clause: the BEFORE must be sold, not only the END"
  ],
  "statusGuess": "strengthen",
  "lens": "harness"
 },
 {
  "level": "micro",
  "domain": "harness/zone-exposure-plan",
  "rule": "H17 Passport gains zoneExposurePlan[] (per state: wardrobe neckline, hair, framing that keep the zone in frame at MCU/CU); validate_plan cross-checks that continuityGroups.invariants do not contradict it; the 'looking' beats (rival scan, mentor glance) are subevents with gazeTarget: zone and a physical cue, injected as prompt lines.",
  "why": "Our continuity lock froze a collar and a bun that hide the neck while copying face-framed compositions 1:1; invariants lock appearance, not exposure.",
  "evidence": [
   "lens-passport §1: invariants 'muted rose cardigan, cream scoop-neck… high bun'; film-S01/S14 'neck covered'",
   "taras-S23 finger lands on the patch on 'Пігментні плями' 87.6; film-S16 gaze-drop eyes-only ~1.5 s; film-S22 'looked at my neck' not staged",
   "tools/problem_quality.py:48–63 continuityGroups invariants"
  ],
  "placement": [
   "references/problem-passport-contract.md v2 bullets",
   "tools/problem_quality.py validate_plan (:14–64) and preflight context (:103–106)",
   "tools/targ_film_prepare.py per-subevent directions"
  ],
  "statusGuess": "new",
  "lens": "harness"
 },
 {
  "level": "micro",
  "domain": "harness/expression-arc",
  "rule": "H18 Passport states intermediate/resolved (and every participant's resolved) gain expressionArc ('relief → joy ≤1 s after the key word; no alarm, no hand clutching the zone during a ≥2 s clean hold'); handoff subevents gain expressionValence derived from the lyric class; prepare injects a restraint line (wet eyes, small mouth shapes; open-mouth shock only where the source event has it); full-film-review eventChecks gain valenceMatchesLyric and overacting = 0.",
  "why": "The passport describes skin texture, not the face, so the generator defaulted to worried self-inspection on good news; F11 has no amplitude/valence cap.",
  "evidence": [
   "film-S26b 156–161 knit brows + hand on neck under 'Заломи вже не так помітні'; film-S28 173.7–177.3 shock under 'помітно гладша'; film-S27 167.5–169",
   "taras-S35 147–152.5 surprise → grin on the vocal rest; targ-S32/S33 surprise → laugh/smile",
   "tools/targ_film_prepare.py:136–140 directions have no valence; plan_film.py criteria actingMin only"
  ],
  "placement": [
   "references/problem-passport-contract.md states schema",
   "tools/problem_quality.py preflight prompt context (:103–106)",
   "tools/targ_film_prepare.py directions (:136–140)",
   "references/film-quality-meta-rules.md F11/F13; full-film-review schema"
  ],
  "statusGuess": "new",
  "lens": "harness"
 },
 {
  "level": "micro",
  "domain": "harness/recommender-proof",
  "rule": "H19 Each recommender participant needs ≥1 exported unit with problemRegionVisible: true at size ≤ MCU, ≤3 s, on her advice/'flawless' line; validate_export fails when every recommender row is hidden-with-reason; validate_plan requires zoneVisibleInAtLeastOneCU: true.",
  "why": "problem_quality forces the recommender's state to resolved but allows every appearance to be hidden with a reason, so rule 21 is satisfied on paper and invisible on screen.",
  "evidence": [
   "film-S21b 108.3–112.5 'the smooth neck the lyric sells is only a sliver above the collar'; film-S21a/S22 WS",
   "taras-S25a MCU, S27 OTS MCU, S29 CU punch; targ-S24 full-face portrait",
   "tools/problem_quality.py:37 (requiredVisibleState resolved), :92 (recommender state), :174–178 (visibility Boolean)"
  ],
  "placement": [
   "tools/problem_quality.py validate_plan (:37) and validate_export (after :177)",
   "references/problem-passport-contract.md recommender bullet (:135–141)",
   "AGENTS.md rule 21 clause 'with her own zone visibly shown in at least one close-up'"
  ],
  "statusGuess": "strengthen",
  "lens": "harness"
 },
 {
  "level": "micro",
  "domain": "harness/matched-view-fades",
  "rule": "H20 targ_film_build computes a head-bbox frame-diff of the last frame of A vs the first frame of B before any opacity crossfade; if the head moves >5 % of frame height or the framing size differs, the crossfade is refused and replaced by a held frame + hard cut; export review counts double-exposure frames = 0; each special transition is used once, where the source has it.",
  "why": "Rule 14 / localization step 6 / F05 require 'deliberate fades' and the build implements them as opacity crossfades with no matched-view check, producing four ghost seams.",
  "evidence": [
   "film ghosts 107.7–108.9, 155.6–155.9, 173.3–173.7 (after a 3-frame wider flash), 198.45–198.7",
   "tools/targ_film_build.py:268–270 (index 31 0.45 s opacity), :276–280 (intermediate-compare 0.5 s), :251–254 zoom types as opacity",
   "taras: 44 hard cuts, one 0.3 s dissolve (reads as glitch)"
  ],
  "placement": [
   "tools/targ_film_build.py transition block",
   "AGENTS.md rule 14 and references/localization-and-resolution-rules.md step 6 (:41–43): 'matched-view fades or held cut'",
   "references/film-quality-meta-rules.md F05 (:56–64); full-film-review ghostFrames: 0"
  ],
  "statusGuess": "conflict",
  "lens": "harness"
 },
 {
  "level": "micro",
  "domain": "harness/take-length-angle",
  "rule": "H21 A generated take holding a face at MS or tighter is ≤6 s (retained ≤5 s), longer beats split at a reaction/blocking change; adjacent specs with identical sourceComposition and size are refused (adapt a source same-angle reset as a reverse angle or one continuous clip); G05 records gain firstLastFrameIdentity per take.",
  "why": "Identity drifts inside long generations in all three videos and ours has the longest; same-angle resets teleport props and force a dissolve; the only limit today is a flat 4–15 s.",
  "evidence": [
   "film-S16 62.4–74.4 face rounder/younger by 74 s; film-S27 172–173.2 new face at the end of a 10.3 s take; film-S22a>S22b 117.7 teleport",
   "adjacent same-size pairs 13/42, 8/43, 10/41",
   "tools/film_provider.py:127 4 ≤ duration ≤ 15; targ_film_prepare.py:109–110"
  ],
  "placement": [
   "tools/targ_film_prepare.py gen_duration cap by role/size",
   "tools/targ_film_build.py adjacent-slot check",
   "references/film-quality-meta-rules.md G05 row + F07/F11"
  ],
  "statusGuess": "new",
  "lens": "harness"
 },
 {
  "level": "macro",
  "domain": "harness/silhouette-lock",
  "rule": "H22 Passport gains silhouetteLocks[] per characterId (hair shape/colour, top colour, accessories, accepted image) valid across all present-day units and attached to every spec via preflight requiredAttachments; allowedChanges may not import source wardrobe/hair re-rolls; validate_export counts distinct hair/wardrobe descriptors per character ≤ 2 + motivated changes.",
  "why": "continuityGroups are per visit/location, so the heroine legally has two silhouettes and inherits the original's re-rolls as 'faithful'; Taras is recognisable by cardigan + bun across 45 shots.",
  "evidence": [
   "film heroine 9 'shoulder-length wavy' vs 8 bun descriptors; film-S20 93.8–101.6 hair down between bun shots S19/S21a (inherited from targ-S22); film-S16 bun morphs in-take",
   "tools/problem_quality.py:48–63 continuityGroups per unitIds; tools/targ_film_prepare.py:123 allowedChanges"
  ],
  "placement": [
   "tools/problem_quality.py continuityGroups (:48–63) add film-wide group kind",
   "tools/targ_film_prepare.py allowedChanges (:123)",
   "references/film-quality-meta-rules.md F07 (:75–81) 'each visit' → 'whole film unless motivated'"
  ],
  "statusGuess": "strengthen",
  "lens": "harness"
 },
 {
  "level": "macro",
  "domain": "harness/style-consistency",
  "rule": "H23 Passport gains stylizationReference {path, sha256} (the accepted cast sheet); each states[].image lineage, recommender and continuity image records styleConsistent: true as a reviewer judgment; per-shot export review adds styleConsistent; derived crops are re-stylized; a photoreal pop inside a stylized film is a defect even where the source has one.",
  "why": "Two of our five style seams sit exactly on the problem-zone proof shots, so the proof reads as another production; F09/F13 are prose and state-image acceptance has no stylization check.",
  "evidence": [
   "film-S15 53–56 chest/hands semi-real; film-S19 92.3–93.8 photoreal neck ECU; film-S21b 108.3–112.5; film-S23 still-life; film-S25/S26a heavier texture pass",
   "taras two seams (S17, S31), targ two (S24, S27)",
   "tools/problem_passport.py:279–288 state image checks (lineage only)"
  ],
  "placement": [
   "references/problem-passport-contract.md states acceptance (:66–72)",
   "tools/problem_passport.py state image checks (:279–288) require styleConsistent",
   "references/film-quality-meta-rules.md F09 (:93–104), F13 (:136–145) style row",
   "tools/targ_film_prepare.py proof-unit directions (:154)"
  ],
  "statusGuess": "strengthen",
  "lens": "harness"
 },
 {
  "level": "micro",
  "domain": "harness/captions",
  "rule": "H24 Caption box centre and cap height are set as ratios of the original's measured caption geometry (containerBudget.captionGeometry); geometric bold sans; ≤2 lines; max words ≤ original's max; sentence case; no terminal punctuation, guillemets or spaced dashes; min card duration 1.5 s (merge shorter); no card during a rest; tools/caption_lint.py runs on captionAnchors and the build CSS before export.",
  "why": "Both references share this grammar; F15 only asks for caption/song sync, and the build hard-codes a smaller, lower, humanist, fully punctuated caption with 1 s flashes.",
  "evidence": [
   "tools/targ_film_build.py:193 DejaVu Sans Bold, :355 bottom:108px font-size:23px (2.7 % of 864), :142–151 display_caption keeps punctuation",
   "lens-editor §0: strong punctuation 123 chars (70 '.', 22 '—', 16 «», 8 ':'), 49/67 cards end with a full stop vs 0/0; box centre ~78–88 % vs ~68–72 %; cap height 2.7 % vs ~5 %/3.5–4 %",
   "film-S34 218.4–224.07 four cards in 5.7 s"
  ],
  "placement": [
   "tools/targ_film_build.py css (:355), font asset (:193), display_caption() (:142–151), caption loop (:333–336)",
   "references/film-quality-meta-rules.md F15 (:156–166) typographic spec",
   "new tools/caption_lint.py"
  ],
  "statusGuess": "new",
  "lens": "harness"
 },
 {
  "level": "micro",
  "domain": "harness/overlays",
  "rule": "H25 Overlay inventory gate: the disclosure appears only inside the end-card range (or as a ≤3 s strip at the first application when the placement decision says so), never under story captions; placement comes from claimLimits.placement (H05).",
  "why": "The strip adds a second text layer for 90 s (37.6 % of runtime) and crowds the bottom third; neither reference has it; its placement was never decided, so the default became permanent.",
  "evidence": [
   "tools/targ_film_build.py:371 #fiction-note data-start 149.63 to end",
   "taras: one disclaimer line on S44 only; targ: none; film seg3/seg4 'crowd the bottom third'"
  ],
  "placement": [
   "tools/targ_film_build.py .disclosure clip start = card start",
   "references/problem-passport-contract.md claimLimits.placement",
   "export review overlay inventory row"
  ],
  "statusGuess": "conflict",
  "lens": "harness"
 },
 {
  "level": "macro",
  "domain": "harness/ending-block",
  "rule": "H26 Assert card duration ≥ endingEditorialOverride.sourceCardDuration; last sung word ≤ card end − 0.5 s; no caption clips starting at or after card start; presenter shot ≤ source hero range × 1.1 with one gesture on the product line; the self-worth line ends on the heroine's face.",
  "why": "The source card duration is already recorded in the plan but never asserted; ours has the shortest card of the three with lyric box + strip + CTA stacked on it and an 8.6 s pointing presenter.",
  "evidence": [
   "public-targ-film/evidence/shot-plan.json endingEditorialOverride.sourceCardDuration 5.87; tools/targ_film_build.py:337 card_start = 235.2 → 4.5 s; vocal to 239.4",
   "targ-S42 6.0 s + S43 5.9 s; taras-S43 4.2 s + S44 5.75 s; film-S36 8.63 s three gestures + S37 4.5 s"
  ],
  "placement": [
   "tools/targ_film_build.py card_start derived from total − max(sourceCardDuration, …); caption loop drops cards over the card",
   "shot-plan endingEditorialOverride gains cardMinSeconds",
   "references/film-quality-meta-rules.md F12 (:125–134) / F16 (:170–175) ending row"
  ],
  "statusGuess": "strengthen",
  "lens": "harness"
 },
 {
  "level": "micro",
  "domain": "harness/packshot",
  "rule": "H27 Any shot where the label occupies ≥10 % of frame height uses the real packshot composited in HyperFrames (tracked or as a ≤5 s insert), never generator lettering; one packshot insert ≥ the original's packshot-insert duration sits between hand-off and application; OCR/visual check of the brand text in every product shot is a G06 row.",
  "why": "Generated labels are garbled in all three videos; Taras's one clean packshot is what makes its brand legible; the packshot is attached to the generator today, not composited by the editor.",
  "evidence": [
   "film-S36 226.6–235.2 AI-garbled jar beside a clean card 3 s later; film-S25 label 'only partly' readable",
   "taras-S31 126–130.5 clean packshot vs S43 garbled; targ-S42 garbled secondary label",
   "tools/targ_film_prepare.py:215–216 packshot attached for s029/s030/s042"
  ],
  "placement": [
   "tools/targ_film_build.py packshot overlay / insert slot",
   "tools/targ_film_prepare.py packshot roles (:215–216) add 'label rendered by the editor'",
   "references/film-quality-meta-rules.md F03 (:40–46)"
  ],
  "statusGuess": "new",
  "lens": "harness"
 },
 {
  "level": "micro",
  "domain": "harness/camera",
  "rule": "H28 Every exported hold >4 s carries a slow push-in/drift (3–10 % scale over the shot) or a blocking change; a static frame with facial animation only is allowed ≤4 s; camera-move share ≥ the original's (H13). Editorial GSAP in the build, no purchase; F11's 'camera does not replace acting' stays.",
  "why": "53 % of Taras shots push in (its most consistent craft signature); our long holds are static 'animated stills'; F11 discouraged moves and no hold rule exists.",
  "evidence": [
   "camera moves 10/44 (targ), 25/47 (taras), 12/44 (film)",
   "static film-S16 12.0 s, S18 8.0 s, S22 9.9 s, S24 8.6 s, S28 10.4 s, S36 8.6 s"
  ],
  "placement": [
   "tools/targ_film_build.py per-slot tl.fromTo('#picNNN',{scale:1},{scale:1.06,…}) for slots >4 s without a source move",
   "tools/targ_film_prepare.py directions 'camera: slow push-in' for retained >4 s",
   "references/film-quality-meta-rules.md F11 (:115–121) add 'a static hold >4 s is a defect'"
  ],
  "statusGuess": "new",
  "lens": "harness"
 },
 {
  "level": "macro",
  "domain": "harness/owner-reference-ingestion",
  "rule": "H29 When the owner supplies a reference rendering for a source, run video_dna_budget.py on it and on the current best output and publish a three-column diff against the ORIGINAL's containerBudget before any new purchase; record it as execution evidence only — thresholds derive from the original, never from the reference's absolute numbers, and the reference's product/problem facts are observations.",
  "why": "AGENTS.md:23–30 only says 'watch it'; watching produced no rule until this study; the measured diff is what turns a preference into gates, and the reference here uses a different brand and problem that must not leak into product truth.",
  "evidence": [
   "references/approved-targ-adaptation-video.md ('watch it before generating', no measurement step)",
   "runs/difference-dna-v1/measurements/summary.json + ledgers + lyrics = first such diff; README.md records HANBIT/pigmentation as observation only"
  ],
  "placement": [
   "references/approved-targ-adaptation-video.md 'What to compare it against' → measured diff required",
   "AGENTS.md:23–30 reference paragraph",
   "generation_contracts.md step 2 output; tools/build_difference_report.py as the standing tool"
  ],
  "statusGuess": "new",
  "lens": "harness"
 }
]