Files
l2-market-parser/docs/goals/merchant-hud-and-location-capture/PLAN.md
T

160 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Merchant HUD And Location Capture Implementation Plan
**Intent:** Дать игроку спокойную оперативную обратную связь поверх окна Lineage II и позволить вручную сохранить примерное место торговца вместе с посещением магазина.
**Current Behavior:** Состояние распознавания видно только в большом окне аудита. Превью уже умеет рисовать найденные области и прогресс иконок, но при возврате в игру оно скрыто. Сервер сохраняет OCR-наблюдения и общую калибровку, но отдельного посещения магазина и изображения места нет.
**Expected Outcome:** Из аудита открывается один компактный always-on-top HUD. Он использует уже существующее состояние выбранного источника и показывает торговца, сторону сделки, прогресс предметов и последнюю распознанную позицию. Пользователь может снять текущий кадр, увидеть сжатую миниатюру и явно сохранить её как место текущего посещения магазина.
**Target-Perspective Output:** Игрок возвращается в Lineage II и поверх окна видит `Покупка · Dwa`, сетку с найденными и ожидающими иконками и последнюю цену. После `Снять место` он видит миниатюру, подтверждает сохранение и получает устойчивый статус `Место сохранено`.
**Truth Owner:** Браузерный `result` владеет только живым состоянием HUD и текущим `shopVisitId`. PostgreSQL в `home-service` владеет сохранённым посещением и его JPEG. Каталог `home-service` остаётся владельцем канонических имён и иконок предметов.
**Contract Boundary:** Parser отправляет authenticated same-origin JSON с UUID посещения, торговцем, стороной, временем, исходными размерами и JPEG data URL. Каждое OCR-наблюдение получает необязательный `shopVisitId`, чтобы связать товары с тем же посещением.
**Cutover:** HUD читает тот же `result`, что текущие source preview и results; отдельного второго состояния распознавания не создаётся. После появления HUD существующие маленькие status chips в source preview остаются fallback внутри страницы, но перестают быть основным игровым feedback path.
**Displaced Path:** Постоянное переключение из игры в большую вкладку `Результат` для контроля процесса демотируется до подробной диагностики. Автоматическая или покадровая запись мест не добавляется.
**Value Density:** Первый срез использует нативный Document Picture-in-Picture вместо Electron/Tauri и сохраняет только один подтверждённый сжатый кадр на посещение.
**Acceptance Evidence:** В поддерживаемом Chrome/Edge игрок открывает HUD, возвращается в игру, видит неизменного торговца при tooltip, наблюдает переход одного слота из ожидания в найденный, снимает место и после server acknowledgement видит сохранённый статус. В PostgreSQL находится одна строка посещения с тем же `shopVisitId`; связанное наблюдение содержит тот же ID.
**Evidence Lane:** Автоматически: parser unit tests, HUD model tests, contracts/backend route tests, migration smoke, parser/frontend/backend builds и `git diff --check`. Вручную только после отдельного разрешения: реальное always-on-top поведение над Lineage II, читаемость и полезность сжатого снимка.
**Kill Criteria:** Не создавать второй recognition loop, не копировать OCR-состояние в независимый store, не сохранять full-resolution кадры, не снимать экран автоматически, не добавлять native wrapper до доказанного ограничения PiP. Если требуется несколько независимых overlay одновременно или click-through поверх каждого окна игры, остановить web-срез и отдельно спланировать Windows companion.
**Architecture Slice:** `l2-market-parser` владеет HUD, visit lifecycle и компрессией кадра; shared contract, BFF, Nest service и PostgreSQL table живут в `home-service`; ingress меняется только для bounded screenshot body.
**Plan Review Gate:** Requires PRE review before execution.
## Physical Scene And Interface Contract
Игрок работает вечером на большом мониторе с несколькими оконными клиентами Lineage II. HUD лежит поверх текущего клиента и должен читаться на любом игровом фоне, не требуя переключения внимания.
- Один solid dark utility surface примерно `320 × 420`, без прозрачного стекла, игровых рамок и декоративной анимации.
- Верхняя строка: выбранный источник и состояние сбора.
- Главная строка: `Покупает/Продаёт · торговец`. Она занимает фиксированное место и не прыгает при OCR.
- Сетка `6 × 3`: каноническая иконка для найденного предмета, нейтральная ячейка для ожидающего, понятный `?` для неоднозначного.
- Последний результат: название, количество, цена и короткий статус совпадения с каталогом.
- Действие `Снять место` создаёт inline-миниатюру. Рядом появляются `Сохранить` и `Переснять`; modal не используется.
- Успех, ошибка и ожидание меняют текст/цвет в фиксированном status slot. Движение только для смены состояния, 150–200 ms, без layout animation.
- Document PiP открывается только по нажатию пользователя и только в secure context. Если API недоступен, большая страница продолжает работать и объясняет, что нужен актуальный Chrome/Edge.
- Web-страница не может задавать позицию PiP и поддерживает только одно такое окно. Пользователь размещает его сам; HUD показывает выбранный источник.
## Architecture Map
### Files to create
`l2-market-parser`:
- `src/market-hud.js`: одно представление HUD из существующего `result`, открытие/закрытие Document PiP и fallback-status.
- `test/market-hud.test.js`: модель торговца, прогресс слотов, последний предмет и unsupported fallback.
`home-service`:
- Следующая свободная `backend/migrations/*.sql`: таблица `l2_market_shop_visits`.
- Соответствующий `backend/migrations/meta/*_snapshot.json` и `_journal.json` только через текущий Drizzle workflow.
- `frontend/src/app/api/l2/market/visits/route.ts`: authenticated same-origin BFF `PUT`.
### Files to modify
`l2-market-parser`:
- `index.html`: кнопка `Поверх игры` и минимальные fallback/status hooks.
- `src/main.js`: передача выбранного `result` в HUD, lifecycle одного `shopVisitId`, захват текущего источника и явное сохранение миниатюры.
- `src/frame-analyzer.js`: новый visit ID при подтверждённой смене/повторном открытии магазина; очистка ID вместе с transient shop progress; добавление ID в observation.
- `src/style.css`: общие HUD tokens и компактная layout-сетка без отдельной визуальной системы.
- `src/parser.js`: чистый helper экспорта HUD/visit данных, только если он уменьшает дублирование.
`home-service`:
- `contracts/src/l2.ts`: strict `L2MarketShopVisitSchema`, bounded JPEG data URL, размеры и optional `shopVisitId` у observation.
- `backend/src/database/schema.ts`: `l2MarketShopVisits` с UUID, временем, source, side, merchant, dimensions, bounded image text и indexes.
- `backend/src/l2/market/l2-market.controller.ts`: `PUT /l2/market/visits/:id`.
- `backend/src/l2/market/l2-market.service.ts`: idempotent insert/update одного пользовательски подтверждённого кадра посещения.
- `backend/src/l2/market/l2-market.service.spec.ts` и controller spec: strict validation, idempotency и linkage ID.
- `frontend/src/lib/l2-session.spec.ts`: unauthenticated/cross-origin rejection и authenticated forwarding.
- `docs/goals/l2-craft-farm-planner/l2-vault.nginx`: exact visits route с bounded body limit только если итоговый JSON превышает текущие 16 KiB.
- `frontend/public/l2/market-audit/**`: только сгенерированный production bundle после parser build.
### Files to avoid
- Не менять catalog matching, OCR, calibration geometry и quote/snapshot semantics.
- Не хранить изображение внутри каждого item observation или каждого retry batch.
- Не трогать другие home-service сайты, admin access и backend public port.
- Не добавлять Electron, Tauri, React/state library, object storage или image processing dependency.
### Source of truth and paths
- **Read path:** `state.results``buildHudModel(result)` → один PiP document. Каталожные имена/иконки уже приходят через существующий fuzzy resolver.
- **Screenshot path:** выбранный source video → canvas downscale → JPEG quality reduction → inline preview → explicit confirm → BFF → Nest → PostgreSQL.
- **Observation link:** `activateShop` создаёт `shopVisitId`; каждое последующее observation этого магазина несёт ID; закрытие магазина после существующего miss policy очищает ID.
- **Compression ceiling:** начать с max width `640`, JPEG quality `0.35`; при превышении contract size уменьшать quality/width до bounded payload. Сохранять исходные и итоговые размеры.
- **Write semantics:** повторный `PUT` с тем же visit ID заменяет только подтверждённую миниатюру и capture metadata; OCR observations остаются append-only batch payloads.
## Tasks
### 1. Зафиксировать visit lifecycle и shared contracts
**Allowed scope:** `src/frame-analyzer.js`, parser tests, `contracts/src/l2.ts`, market service tests.
- Добавить `shopVisitId` и `shopVisitStartedAt` в transient result.
- Создавать ID только при первом подтверждённом заголовке после reset или реальной смене shop key.
- Не менять ID, пока tooltip закрывает заголовок.
- Добавить optional ID в observation и strict visit screenshot contract.
**Verification:** `npm test`; focused contracts build and market specs.
**Acceptance:** тест воспроизводит header → tooltip → несколько items и доказывает один visit ID и неизменного торговца.
### 2. Построить один PiP HUD из существующего result
**Allowed scope:** `market-hud.js`, `main.js`, `index.html`, `style.css`, HUD test.
- Открывать Document PiP только по явному нажатию.
- Подключить модель выбранного source result без второго timer/recognition loop.
- Рендерить merchant, side, slot progress, icons, unresolved state и last item.
- Перерисовывать HUD после текущего `drawSourceOverlay`/result update.
- Закрытие PiP не останавливает сбор; закрытие основной страницы закрывает PiP нативно.
**Verification:** parser unit tests and production build.
**Acceptance:** mocked supported/unsupported window tests; модель сохраняет фиксированную геометрию при idle/found/error.
### 3. Добавить ручной capture с inline preview
**Allowed scope:** parser HUD/main code and tests.
- `Снять место` доступно только при active source и подтверждённом merchant/visit ID.
- Захватить один текущий frame, downscale/compress без новой зависимости.
- До отправки показать миниатюру, размер и `Сохранить/Переснять`.
- После ошибки оставить миниатюру для повторного сохранения; не делать background screenshots.
**Verification:** deterministic canvas compression test with a generated fixture; payload always satisfies contract limit.
**Acceptance:** одна кнопка создаёт один preview; cancel/reshoot не пишет сервер.
### 4. Сохранить место торговца в home-service
**Allowed scope:** migration/schema, shared contract, market controller/service, exact BFF route and tests.
- Создать bounded visit table and index by merchant/seenAt.
- Реализовать authenticated same-origin `PUT` and idempotent backend write.
- Не принимать PNG, произвольный MIME, oversized base64 или mismatched path/body ID.
- Не возвращать изображение из unrelated snapshot/catalog endpoints.
**Verification:** Drizzle migration generation/check, market service/controller specs, frontend BFF test, contracts/backend/frontend builds.
**Acceptance:** database smoke verifies one replaceable visit row and observation payload with the same `shopVisitId`.
### 5. Интегрировать и доказать cutover
**Allowed scope:** generated audit bundle, relevant README/evidence, exact ingress body rule if required.
- Пересобрать parser directly into `frontend/public/l2/market-audit`.
- Проверить bundle на отсутствие embedded tokens/full-resolution fixtures.
- Programmatic public route check remains password-gated.
- Не объявлять игровой overlay доказанным без отдельного manual permission.
**Verification:** both repository builds, focused tests, `git diff --check`, migration smoke.
**Acceptance:** automated lane green; target lane recorded as either proven over a real game window or `implemented but visually unproven`.
## PRE Self-Review
- **Mode:** PRE
- **Verdict:** aligned
- **Blockers:** none for the web prototype.
- **Major constraint:** Document PiP gives one user-positioned always-on-top window, not multiple click-through overlays attached to game windows. This is explicit in UX and kill criteria.
- **Major security constraint:** screenshots may contain chat/account information; capture stays manual with inline preview, strict JPEG/size validation and authenticated storage.
- **Smallest next gate:** owner accepts the one-PiP limitation and manual screenshot confirmation, then execution starts with Task 1.