26 KiB
Store Icon Progress Overlay Implementation Plan
Intent: Показывать прогресс обхода открытого магазина прямо поверх захваченного игрового кадра: занятые иконки ожидают tooltip в жёлтой рамке и становятся зелёными после успешного распознавания и привязки к слоту.
Current Behavior: Overlay показывает только служебные области текущего кадра и общий статус одного tooltip; приложение не знает список занятых слотов и не хранит прогресс по каждой иконке.
Expected Outcome: После распознавания магазина приложение находит занятые слоты его Buy/Sell-сетки, показывает их жёлтыми, сохраняет чистый снимок сетки без tooltip и переводит точный слот в зелёный после OCR предмета и совпадения его каталоговой иконки.
Target-Perspective Output: В развёрнутом превью выбранного окна (не уже 640 CSS px на desktop, когда позволяет viewport) виден реальный магазин с компактным чек-листом поверх его иконок: жёлтый означает «наведи курсор», зелёный — «этот слот уже прочитан»; рядом показан счётчик 2/5 иконок.
Truth Owner: Активный result источника владеет текущим магазином и его iconSlots; src/vision.js владеет определением занятых ячеек и сравнением визуальной иконки, а drawSourceOverlay только рисует состояние.
Contract Boundary: Калибровка хранит необязательные sellItemGridRegion и buyItemGridRegion относительно найденного маркера магазина. detectOccupiedSlots(frame, absoluteGridRect) возвращает {slotId,row,column,frameRect,score}[]. После resolveCatalogItem matchCatalogIconToSlot(slots, icon) возвращает ровно один slotId только при прохождении абсолютного порога и отрыва от второго результата; иначе возвращает null.
Cutover: Существующие diagnostics boxes продолжают показывать pipeline, но прогресс иконок рисуется из отдельного устойчивого result.iconSlots; общий статус «Предмет ожидает» заменяется счётчиком слотов, когда сетка доступна.
Displaced Path: Не использовать ближайший к tooltip слот, порядок наведения или «первый жёлтый» как источник истины — эти эвристики могут покрасить не ту иконку.
Value Density: Две необязательные области калибровки, один лёгкий пиксельный проход по 18 ячейкам при открытии магазина и одно сравнение с pending slots после нового предмета; без OCR всех иконок и без нового backend-контракта.
Evidence Gate: Все 17 кадров из data/ описаны в human-reviewed manifest и прогоняются тем же analyzer path, что live capture; на полном Sell-кадре занятые слоты выделены, пустые не выделены; на полном Buy-кадре работает отдельная область; после связанного tooltip ровно соответствующий слот меняет жёлтый на зелёный, а остальные остаются жёлтыми.
Acceptance Evidence: npm run test:fixtures сравнивает merchant/side/occupied slots/tooltip item с manifest для каждого файла и проверяет связанные store sequences; browser screenshots до/после tooltip для Sell и Buy при фактическом размере source card; unit tests occupied-slot classifier, unique-best/tie и active-shop reset; production build; calibration PUT→GET round-trip; отсутствие slot-данных в clipboard export/outbox.
Evidence Lane: Human-reviewed data/fixtures.json, локальный browser fixture runner, node --test, production build.
Kill Criteria: Нет второго overlay canvas, нет позиционного угадывания hovered slot, нет обязательной миграции старой калибровки, нет отправки thumbnail/data URL в backend; inline analyzeFrame удалён из main.js, и live capture/fixture runner импортируют один analyzeFrame из src/frame-analyzer.js.
Architecture Slice: data/*.jpg, data/fixtures.json, index.html, src/main.js, выделяемый production analyzer seam, src/vision.js, src/parser.js, fixture tests, package.json, README.md, src/style.css.
Plan Review Gate: Requires PRE review before execution.
Product Direction
- Domain: private store, Buy/Sell grid, occupied slot, tooltip, catalog icon, captured frame, scan progress.
- Color world: тёмный игровой кадр, приглушённый amber ожидания, зелёный подтверждения, красный ошибки, нейтральный синий diagnostics.
- Signature: живой чек-лист непосредственно на слотах Lineage II, а не отдельный dashboard со списком.
- Rejected defaults: отдельная карточка каждого pending slot; анимированные пульсирующие рамки; окрашивание всех пустых клеток сетки.
- Direction: сохранить реальный кадр главным слоем и добавить только семантические рамки и компактный счётчик.
Component Checkpoint
- Intent: владелец инструмента быстро видит, какие предметы в конкретном магазине ещё нужно обойти; интерфейс остаётся спокойным и утилитарным.
- Hierarchy: сам кадр и рамки слотов — focal point; textual status вторичен.
- Palette: существующие
pendingamber иfoundgreen; новые декоративные цвета не добавляются. - Depth: borders-only поверх видео, без теней и glow.
- Surfaces: существующее preview surface; новый контейнер не создаётся.
- Typography: существующий маленький overlay status с tabular counter.
- Spacing: существующая плотность 4/8 px; рамка не перекрывает содержимое иконки.
- Readability: активное source preview разворачивается минимум до 640 CSS px на desktop; на узком viewport занимает доступную ширину без отдельного уменьшенного дубликата.
Architecture Slice
- Files to create:
data/fixtures.json,data/fixture-calibration.json, reviewed template/catalog assets underdata/fixture-assets/,src/frame-analyzer.js, Playwright fixture regression spec and browser evidence images. - Files to modify:
index.html,src/main.js,src/vision.js,src/parser.js, fixture/unit tests,package.json,README.md,src/style.css; hosted calibration endpoint/schema добавляется в scope только если round-trip отбрасывает optional regions. - Files to avoid:
src/market-outbox.js, import proxy, backend payload, catalog API contract. - Source of truth:
data/fixtures.jsonвладеет reviewed expected result/initial state/slot transitions каждого screenshot sequence;data/fixture-calibration.jsonиdata/fixture-assets/владеют reviewed test geometry/templates/catalog bytes; production analyzer владеет фактическим результатом кадра;result.activeShopKey+result.iconSlotsвладеют live progress. - Read path: fixture JPG + fixture calibration → тот же production analyzer, что live frame → actual result → semantic comparison with manifest; live frame → sale anchor → header side/merchant → side-specific grid → occupied slots → yellow overlay; tooltip → parsed item → catalog icon → saved grid snapshot → matched slot → green overlay.
- Write path: fixture manifest/calibration/assets редактируются только при добавлении или ручной перепроверке screenshots; тесты не переписывают expected values автоматически; calibration дополняется двумя optional regions; runtime slot state остаётся только в памяти источника.
- Contract boundary:
src/frame-analyzer.jsэкспортирует единственныйanalyzeFrame({frame,result,calibration,templates,source,services}) -> Promise<result>; production OCR/OpenCV/parser импортируются внутри этого модуля или передаются только через production adapters, аservices.catalogявляется единственной fixture substitution;detectOccupiedSlots(frame, absoluteGridRect) -> [{slotId,row,column,frameRect,score}];matchCatalogIconToSlot(slots, icon) -> slotId|null; canvas wrappers live invision.js. - Integration points: fixture manifest loader/schema, production
analyzeFrame,emptyCalibration, calibration UI/addRegion/rendering,resolveCatalogItemresult,drawSourceOverlay, package verification scripts. - Migration/cutover: existing v4 calibration is read unchanged; missing grid region disables only icon progress for that side.
- Displaced path: общий item status остаётся fallback только при отсутствии grid calibration.
- Acceptance evidence gate: full-window Buy and Sell fixtures plus tooltip/item catalog icon.
Fixture Corpus and Ground Truth
Все кадры имеют размер 1560 x 1360 и сняты персонажем Deela. merchant ниже — персонаж открытого магазина. Номер slot считается с нуля слева направо по верхней строке. Значения unknown не угадываются по иконке: такой fixture проверяет заголовок и занятость слота, но не название предмета.
| File | Scene | Expected store | Visible/hovered contents |
|---|---|---|---|
Discord_2KrJ9GGjCP.jpg |
Buy + tooltip | buy:KapayJI, occupied [0] |
slot 0: Ancient Adena, quantity 0, each 2 Adena |
Discord_7xmx8OSuCH.jpg |
Sell, clean | sell:Domestos, occupied [0] |
slot 0: unknown |
Discord_ARWzEIaayl.jpg |
Sell + tooltip | sell:Gnumli, occupied [0,1] |
slot 0: Soulshot: C-grade, quantity 36,370, price 20 Adena |
Discord_dxpCzaJEWD.jpg |
Sell + tooltip | sell:Boroda4, occupied [0,1] |
slot 1: Brigandine Helmet, quantity 1, price 800,000 Adena |
Discord_ezvlzpNHRg.jpg |
Sell + tooltip | sell:shotD, occupied [0,1] |
slot 1: Dimensional Fragment, quantity 687, price 3,500 Adena |
Discord_fb9f61z8Ek.jpg |
Sell + tooltip | sell:shotD, occupied [0,1] |
slot 0: Tutorial Guide, quantity 1, price 5,000,000 Adena |
Discord_fFbzX9vDhW.jpg |
Sell + tooltip | sell:1SHOP, occupied [0] |
slot 0: Ancient Adena, quantity 1,793,000, price 3 Adena |
Discord_ikzA9VhROZ.jpg |
Buy + tooltip | buy:RAKOT, occupied [0,1,2] |
slot 2: Coarse Bone Powder, quantity 0, each 1 Adena |
Discord_K9RkTrU6B2.jpg |
Sell, clean | sell:Crom, occupied [0,1] |
slots 0–1: unknown |
Discord_KL0fnDcvr7.jpg |
Sell + tooltip | sell:Boroda4, occupied [0,1] |
slot 0: Spellbook: Prominence, quantity 2, price 50,000 Adena |
Discord_qy3EYIEcMV.jpg |
Sell + tooltip | sell:Gnumli, occupied [0,1] |
slot 1: Blessed Spiritshot: D-Grade, quantity 7,245, price 58 Adena |
Discord_sSJAGZLHvm.jpg |
Sell, clean | sell:Boroda4, occupied [0,1] |
paired contents: Spellbook: Prominence, Brigandine Helmet |
Discord_tJFxvFi7DM.jpg |
Sell, clean | sell:1SHOP, occupied [0] |
paired content: Ancient Adena |
Discord_TxbA20ZOQB.jpg |
Buy + tooltip | buy:RAKOT, occupied [0,1,2] |
slot 0: Scroll: Enchant Weapon (D), quantity 0, each 200,000 Adena |
Discord_uqGcQhxIWc.jpg |
Sell, clean | sell:shotD, occupied [0,1] |
paired contents: Tutorial Guide, Dimensional Fragment |
Discord_w3Smu4bOQK.jpg |
Sell, clean | sell:Gnumli, occupied [0,1] |
paired contents: Soulshot: C-grade, Blessed Spiritshot: D-Grade |
Discord_wQc5FDayAK.jpg |
Buy, clean | buy:RAKOT, occupied [0,1,2] |
slot 0: Scroll: Enchant Weapon (D); slot 1: unknown; slot 2: Coarse Bone Powder |
Manifest records both human-facing displayName and parser-facing normalized name, for example Spellbook: Prominence → Spellbook Prominence. It also records viewerCharacter, groupId, scene, standalone vs contextual assertion mode, side, merchant, occupiedSlots, optional hoveredSlot, optional reviewed initialState, expectedPendingSlots, expectedFoundSlots, catalogAssetId, and optional {displayName,name,quantity,priceAdena,priceMode}. Every data/*.jpg must appear exactly once; missing files and stale manifest entries fail before OCR starts.
data/fixture-calibration.json is human-reviewed versioned truth for this 1560 x 1360 corpus: sale anchor, store header, tooltip search/anchor, name/price regions and Buy/Sell grid regions. Template images are committed as explicit files under data/fixture-assets/; runner never derives calibration from actual results. Catalog mappings point to committed API JSON plus original icon bytes by catalogAssetId/path, not to manifest-derived generated icons.
Sequence groups:
sell-gnumli: clean → Soulshot tooltip → Blessed Spiritshot tooltip.sell-boroda4: clean → Spellbook tooltip → Brigandine Helmet tooltip.sell-shotd: clean → Tutorial Guide tooltip → Dimensional Fragment tooltip.sell-1shop: clean → Ancient Adena tooltip.buy-rakot: clean → Scroll tooltip → Coarse Bone Powder tooltip; middle slot intentionally remains unknown/pending.sell-domestos,sell-crom: standalone clean frames guarding the confirmed header and occupancy fields.buy-kapayji: tooltip-only partial group with human-reviewedinitialStateforbuy:KapayJIand slot 0 pending; it validates tooltip parsing/transition only and does not claim standalone header/grid recognition.
Fixture Test Contract
- Unit/schema lane (
node --test): manifest validity, exact JPG coverage, unique ids, slot ranges, paired group consistency, normalization and pure state transitions. - Screenshot lane (
npm run test:fixtures):@playwright/teststarts the Vite fixture page in bundled Chromium, loads real JPGs, applies committed fixture calibration, callssrc/frame-analyzer.js#analyzeFrame, and compares only declared expected fields. It uses real production OpenCV/Tesseract/parser; only/api/l2/itemstransport is fulfilled from committed catalog JSON/icon bytes. - Sequence lane: feed group frames in manifest order and assert remembered
side:merchant, item aggregation and explicitexpectedPendingSlots/expectedFoundSlotsafter every frame. Contextual-only fixtures start from their reviewedinitialState; they are never reported as standalone full-frame passes. - Diagnostics on failure: write an actual-vs-expected JSON report plus crop/overlay artifacts under an ignored test-output directory; never overwrite the manifest or source JPGs.
- Stable assertions: exact semantic merchant/side/name/quantity/price and slot ids; no pixel-perfect full-screen snapshots and no assertion on OCR confidence unless a threshold regression is specifically under test.
- Three-miss reset stays in the pure state-transition unit lane because the current corpus has no three-frame no-store sequence.
- Verification command: add
npm run checkfor unit tests, all 17 fixture cases, production build andgit diff --check; hosted calibration PUT→GET remains a separately recorded environment evidence gate because it requires the deployed backend.
Runtime Invariants
- Buy и Sell используют разные calibrated grid regions; side выбирается только из распознанного заголовка.
- Grid region делится на фиксированную Interlude-сетку 6 x 3; пользователь выделяет внешний прямоугольник клеток без заголовка и scroll buttons.
- Пустые ячейки не получают рамку; занятые начинаются как
pending. - Occupancy считается только внутри фиксированного inset каждой ячейки, исключающего border; метрика и порог выбираются по измеренному разрыву между occupied/empty примерами обоих типов магазина и фиксируются константой с fixture-тестом.
- Grid snapshot обновляется только на кадре с распознанным заголовком и без найденного tooltip, чтобы tooltip не закрыл иконки.
- Смена
side:merchantполностью сбрасывает runtime slots, но не исторические parsed items. - Зелёный статус ставится только если лучший visual match превышает измеренный threshold и опережает второй результат не меньше чем на измеренный margin; tie/identical icons возвращают
null, остаются жёлтыми и получают статус «неоднозначно». - Повторный OCR того же предмета не перекрашивает другой одинаковый slot без нового однозначного совпадения.
- Slot thumbnails, grid snapshots и match scores не попадают в outbox или exported JSON.
- После трёх последовательных кадров без
saleAnchor/нижнего маркера магазина (существующийresult.misses) очищаютсяactiveShopKey, grid snapshot и slots; отсутствие header OCR при видимом маркере, в том числе из-за tooltip, прогресс не сбрасывает; повторное открытие того же merchant строит состояние заново.
Tasks
- Зафиксировать corpus в
data/fixtures.jsonи добавить schema/coverage tests.- Allowed scope: существующие 17 JPG остаются неизменными; expected values вводятся вручную по таблице выше; unknown fields остаются явно unknown.
- Expected output: каждый JPG описан ровно один раз, связанные кадры объединены
groupId, human display и parser-normalized item names разделены. - Verification:
node --testvalidates schema, coverage and group consistency. - Parallel: no; establishes regression truth.
- Выделить
src/frame-analyzer.js#analyzeFrameи добавить Playwright browser fixture runner без дублирования pipeline.- Allowed scope: перенести только ownership анализа кадра из DOM-heavy
main.js; удалить inline implementation; live capture и fixtures импортируют один symbol; runner использует production OCR/OpenCV/parser, подменяя только внешний catalog transport committed fixture-ответом. - Expected output:
npm run test:fixturesобрабатывает JPGs, сравнивает только declared fields и сохраняет понятные failure artifacts. - Verification: source search находит одно определение
analyzeFrame; намеренно испорченный expected field даёт targeted diff; после возврата manifest corpus проходит. - Parallel: no; establishes executable evidence lane.
- Allowed scope: перенести только ownership анализа кадра из DOM-heavy
- Добавить optional Sell/Buy grid calibration в
index.html,src/main.js,src/parser.js.- Allowed scope: два поля относительно sale anchor; существующие шесть остаются обязательным минимумом.
- Expected output: старые сохранения работают; grid fields можно выбрать, удалить, сохранить и увидеть на calibration canvas.
- Verification: parser/calibration tests, browser selection smoke и hosted PUT→GET round-trip; если поля теряются, минимально расширить серверную схему.
- Parallel: no; extends the contract.
- Добавить в
src/vision.jsминимальные slot helpers: 6 x 3 geometry, occupied-cell classifier и unique-best matching catalog icon к pending slots.- Allowed scope: локальная grid image analysis; использовать существующий OpenCV loader/matcher.
- Expected output: provided Sell fixture returns exactly its occupied cells; empty grid returns none.
- Verification: Node tests для geometry/classifier seam, absolute threshold, second-best margin, identical-icon tie и browser fixture for canvas wrapper.
- Parallel: no; consumes tasks 1-3 contracts and uses both Sell/Buy fixture corpus.
- Интегрировать runtime state в
src/main.js.- Allowed scope: active-shop reset, clean grid snapshot, slot match after
resolveCatalogItem, no backend writes. - Expected output: stable pending/found slots persist across frames and reset on shop change/close.
- Verification: focused state-transition tests, three-miss close/reset, reopening same merchant and two-frame browser flow; assert no slot/snapshot fields in clipboard export and outbox.
- Parallel: no; consumes tasks 3-4.
- Allowed scope: active-shop reset, clean grid snapshot, slot match after
- Развернуть активное live-превью и расширить существующий
drawSourceOverlay/status copy.- Allowed scope: selected source preview минимум 640 CSS px на desktop; draw slot rects before transient diagnostics; reuse
boxColor(pending/found). - Expected output: игровые слоты читаемы в карточке источника, yellow/green outlines и
parsed/totalcounter; no new canvas or motion. - Verification: desktop screenshot именно при фактическом размере source card, narrow viewport screenshot, squint/token/state checks.
- Parallel: can start after runtime contract is fixed.
- Allowed scope: selected source preview минимум 640 CSS px на desktop; draw slot rects before transient diagnostics; reuse
- Обновить
README.md, добавить единыйnpm run check, прогнать tests/build/diff-check и записать browser evidence вEVIDENCE.md.- Acceptance evidence: manifest coverage report
17/17, Sell and Buy yellow states, exact green transition, shop reset. - Parallel: no.
- Acceptance evidence: manifest coverage report
Non-goals
- Автоматически кликать или наводить курсор в игровом окне.
- OCR названий прямо с 32 px icon.
- Сохранять slot progress между перезапусками приложения.
- Менять каталог, import payload или исторический список результатов.
- Угадывать слот по положению tooltip или порядку обхода.
Required Input Before Execution
- Реальный ответ
/api/l2/itemsдля одного известного предмета из Sell fixture, включаяiconUrl, и доступные по этому URL bytes (либо контролируемый локальный/hosted fixture). Same-origin сам по себе не доказывает, что catalog artwork, alpha и размер совпадают с игровой иконкой. - Buy fixture теперь есть:
Discord_wQc5FDayAK.jpg; по нему и clean Sell fixtures во время исполнения измерить occupied/empty metric gap и только затем зафиксировать cell inset/threshold. - По реальной паре slot crop ↔ catalog icon измерить absolute match threshold и best-vs-second margin; до этого зелёная привязка считается непроверенной.
Risks
- Кастомный клиент может изменить размер/число slot cells; фиксированная 6 x 3 геометрия тогда потребует отдельной настройки.
- Catalog icon может отличаться рамкой, альфой или масштабом от клиентского asset; evidence должен зафиксировать реальный match score до выбора порога.
- Одинаковые иконки в нескольких слотах требуют дополнительного различителя; до него неоднозначные совпадения должны оставаться жёлтыми.