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

17 KiB
Raw Blame History

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.resultsbuildHudModel(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.