# Dynamic Tooltip Crop Implementation Plan **Intent:** Сделать ручную калибровку пиксель-точной при масштабе 2× и читать строки tooltip переменной ширины без OCR всего кадра. **Current Behavior:** Калибратор показывает кадр 1:1, а runtime после поиска `Price :` использует фиксированную ширину калиброванных строк названия и цены. **Expected Outcome:** Кадр размечается в масштабе 2×; найденные строки названия и итоговой цены tooltip локально подгоняются вправо по фактическим белым и жёлтым пикселям до первого устойчивого пустого промежутка, включая текст длиннее калиброванного образца. **Target-Perspective Output:** Пользователь выделяет мелкие области на увеличенном кадре и получает название и цену как для короткого, так и для длинного tooltip без постороннего текста за его правой границей. **Truth Owner:** `src/vision.js` владеет определением фактической правой границы OCR-строки; калибровка сохраняет стабильный левый край, положение и высоту строки относительно `Price :`. **Contract Boundary:** Чистый `findTextRunEnd(columns, gap)` находит конец первого текстового пробега. Canvas-wrapper `fitOcrTextRect(canvas, rect, options)` сканирует от калиброванного левого края до правой границы кадра, использует gap 12 px и padding 6 px, применяет white mask для названия и white+yellow для цены и возвращает frame-clamped rect либо `null`, если пробег не найден. **Cutover:** `analyzeFrame` подгоняет `nameRect` и `priceRect` перед `combineRects`; прямое использование фиксированной ширины прекращается. **Displaced Path:** Фиксированные ширины `itemNameRegion` и `itemPriceRegion` больше не задают окончательную границу и не используются как широкий OCR fallback; при пустой маске item OCR пропускается с диагностикой. **Value Density:** Один локальный пиксельный проход по двум тонким строкам вместо OCR широкой области или нескольких OCR-попыток. **Evidence Gate:** На реальном тестовом кадре 1920×1080 canvas отображается 3840×2160; выделение в 2× визуально совпадает с целевыми source-пикселями; реальный tooltip читается при искусственно короткой калиброванной ширине, а удалённый соседний текст не попадает в diagnostics crop/OCR. **Acceptance Evidence:** Browser geometry и screenshot выделения; границы двух реальных строк разной длины из предоставленного tooltip; diagnostics crop + parsed name/price этого tooltip при намеренно узкой калиброванной ширине; runnable-тест чистого определения конца текстового пробега; production build. **Evidence Lane:** Локальный браузер и `node --test`. **Kill Criteria:** В runtime нет второго OCR-пути по широкой области или широкого фиксированного fallback; `combineRects` не вызывается до успешной подгонки обеих строк. **Architecture Slice:** `src/main.js`, `src/vision.js`, `src/style.css`, `test/parser.test.js`, `README.md`; документация калибровки уточняет различие зоны поиска и OCR-строк. **Plan Review Gate:** Requires PRE review before execution. ## Architecture Slice - Files to create: только этот goal-пакет. - Files to modify: `src/main.js`, `src/vision.js`, `src/style.css`, `test/parser.test.js`, `README.md`. - Files to avoid: сохранённый формат калибровки, парсер текста tooltip, market outbox и backend. - Source of truth: найденный `Price :` задаёт позицию; пиксели текущего кадра задают фактическую ширину. - Read path: кадр → поиск магазина → поиск `Price :` → калиброванные строки → подгонка по пикселям → OCR → parser. - Write path: без новых сохраняемых полей; существующая калибровка остаётся совместимой. - Contract boundary: чистый `findTextRunEnd` и canvas-wrapper `fitOcrTextRect` в vision-слое. - Integration points: два прямоугольника в `analyzeFrame`, CSS-размер canvas. - Migration/cutover: немедленный, без миграции данных. - Displaced path: фиксированная ширина перестаёт быть OCR fallback; пустая маска становится наблюдаемым `not-found` в diagnostics. - Acceptance evidence gate: 2× browser geometry + screenshot точного выделения + diagnostics реального tooltip + unit test длинной строки + full build. ## Runtime Invariants - Левый край строк названия и цены стабилен относительно найденного `Price :`; переменной является правая граница. - Продукт сохраняет только название, количество и итоговую цену; остальные описательные строки tooltip не являются данными продукта. - `tooltipSearchRegion` может быть широкой горизонтальной полосой за пределами окна трейда, но исключает нижний `Price` основного окна; эта полоса используется только для template matching. - Сканирование OCR-строки начинается с её калиброванного левого края, идёт не дальше правой границы кадра, допускает внутренние промежутки меньше 12 px и добавляет не более 6 px после последнего текстового столбца. - Название использует белую маску; цена использует белую и жёлтую маски. - Если текстовый пробег не найден, OCR предмета не запускается, а diagnostics явно сообщает причину. ## Tasks 1. Последовательно изменить `src/main.js` и `src/style.css`: отображать calibration canvas ровно в 2× intrinsic size; координаты остаются корректными через существующий `canvasPoint`. - Allowed scope: только display size и crisp integer scaling. - Verification: браузер показывает intrinsic 1920×1080 и rendered 3840×2160; screenshot известного выделения совпадает с source-пикселями. 2. Затем изменить `src/vision.js` и `test/parser.test.js`: добавить чистый `findTextRunEnd` и локальную подгонку правого края с раздельными масками, gap 12, padding 6 и `null` для пустой маски. - Allowed scope: preprocessing границы; без нового OCR-прохода и без зависимости canvas в Node. - Verification: `node --test` покрывает длинный текст, внутренние пробелы, удалённый шум и пустую маску. 3. После задачи 2 интегрировать подгонку в `src/main.js` до `combineRects`; при `null` остановить item OCR и записать причину в diagnostics. - Allowed scope: существующий item OCR branch. - Verification: diagnostics показывают фактический локальный crop, а широкий search region и удалённый соседний текст не попадают в OCR. 4. Обязательно обновить `README.md`: широкая полоса ищет только `Price :`; ширины строк являются начальными левыми/вертикальными ориентирами и подгоняются runtime. - Allowed scope: только инструкции выбора `tooltipSearch`, `itemName` и `itemPrice`. - Verification: README больше не требует калибровать ширину по самому длинному предмету. 5. Прогнать прямые эквиваленты `npm test`, `npm run build`, `git diff --check` и визуальную проверку большого кадра; записать реальные результаты в `EVIDENCE.md`. - Acceptance evidence: geometry 2×, screenshot точного выделения, границы двух реальных строк, diagnostics OCR/parse и вывод проверочных команд. ## Non-goals - Не определять всю рамку tooltip и не сохранять его описательные строки, потому что продукту нужны название, количество и итоговая цена. - Не искать `Price :` по всему кадру без ограничивающей полосы. - Не добавлять настройку масштаба или новый UI-контрол в этом срезе. - Не менять формат сохранённой калибровки. ## Risks - Слишком короткий допустимый пробел обрежет текст; слишком длинный захватит соседний UI. - Горизонтальная рамка внутри строки может выглядеть как непрерывный текст; точные вертикальные калиброванные строки остаются обязательными, а пустой/неограниченный пробег должен завершаться `not-found`, не широким OCR.