17 KiB
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 BFFPUT.
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: strictL2MarketShopVisitSchema, bounded JPEG data URL, размеры и optionalshopVisitIdу 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 quality0.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
PUTand 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.