Document merchant HUD and location capture plan
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
# Goal: Merchant HUD And Location Capture
|
||||
|
||||
Use Krypton Execution to execute `docs/goals/merchant-hud-and-location-capture/PLAN.md`.
|
||||
|
||||
Core rules:
|
||||
- Treat `PLAN.md` as the source plan.
|
||||
- Preserve the existing recognition loop and use its `result` as the only live HUD truth.
|
||||
- Start with one Document Picture-in-Picture HUD; do not add a native wrapper unless its documented limitations block the real workflow.
|
||||
- Capture a merchant location only after an explicit user action and inline preview.
|
||||
- Store one bounded JPEG per shop visit and link item observations with the same `shopVisitId`.
|
||||
- Preserve auth, same-origin writes, catalog ownership and append-only observation batches.
|
||||
- Capture target-perspective evidence only with fresh manual-testing permission; otherwise report `implemented but visually unproven`.
|
||||
@@ -0,0 +1,159 @@
|
||||
# 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.
|
||||
|
||||
Reference in New Issue
Block a user