VanguardReMaster/docs/Development/IMPLEMENTATION_PLAN.md

191 KiB
Raw Blame History

Implementation Plan

Активный журнал задач и планов изменений. Постоянные правила проекта хранятся в Skill vanguard-remaster; история старых блоков — в IMPLEMENTATION_CHECKLIST.md, DEVELOPMENT_CHECKLIST.md и docs/HANDOFF.md. Этот файл не заменяет их и не является источником игровых правил.

Процесс

  1. DeepSeek анализирует пожелание, код и зависимости, задаёт вопросы и создаёт/обновляет раздел задачи. Игровой код на этом этапе не меняется.
  2. DeepSeek (реализация) читает AGENTS.md, Skill и раздел задачи, сверяет план с текущим кодом, реализует, проверяет и обновляет статусы.
  3. Передача — только через файлы репозитория и Git. Память модели и чат не считаются общим контекстом.
  4. Если план устарел или требования изменились, реализация останавливает зависимую часть, фиксирует причину и при необходимости возвращает задачу на анализ.
  5. Нельзя удалять чужие задачи, невыполненные пункты и историю статусов.

Подробный процесс — docs/Development/AGENT-WORKFLOW.md.

Статусы

  • [ ] — не выполнено;
  • [x] — выполнено и подтверждено доступными проверками;
  • [!] — заблокировано или ждёт решения;
  • [-] — больше не требуется после согласованного изменения плана.

Для каждого пункта храни отдельно:

  • Реализация: выполнена / не выполнена / не требуется.
  • Проверка: выполнена (что именно) / не выполнена (почему).

[x] нельзя ставить, если реализация есть, а проверка не запускалась.

Журнал задач

ID Статус Название Фаза Проверка
TASK-2026-001 [!] Плавность карточных перемещений и корректные источники DeepSeek: анализ; реализация выполнена, ждёт ручной проверки C10 EditMode 46/46; ручная — пользователь
TASK-2026-019 [ ] Загрузочная сцена и кадр без покадровых аллокаций DeepSeek: план готов; реализация ожидает не выполнена
TASK-2026-020 [x] Парсер карт и артов с официального сайта DeepSeek: анализ и реализация идемпотентность 0 diff; validate не запускался
TASK-2026-021 [!] Login по референсу: 2D-арт и неоновый UI DeepSeek: C1–C9, Tab/Enter/Esc; ждёт приёмки EditMode 60/60; SCENE_SMOKE success=True; standalone собран; ручная — пользователь
TASK-2026-022 [!] Lobby по референсу: дашборд, заглушки, сцена Community DeepSeek: C1–C11 реализованы; ждёт ручной приёмки EditMode 62/62; SCENE_SMOKE, PLAYMAT_SMOKE, standalone; ручная — пользователь
TASK-2026-023 [!] DeckBuilder: один экран, мультифильтры и список колоды DeepSeek: C1–C11 реализованы; ждёт ручной приёмки EditMode 67/67; SCENE_SMOKE, PLAYMAT_SMOKE; ручная — пользователь
TASK-2026-024 [ ] DeckBuilder по референсу: топбар Lobby, крупные карты, фильтры DeepSeek: анализ и план готовы; реализация ожидает не выполнена

Новые задачи добавляются в конец файла отдельными разделами. Не используй один раздел для нескольких независимых задач.


Шаблон задачи (не является активной задачей)

TASK-YYYY-NNN — краткое название

  • Статус: [ ]
  • Создано: YYYY-MM-DD
  • Анализ: DeepSeek
  • Реализация: DeepSeek
  • Затронутые системы: ...

A. Цель и требования

  • Что просит пользователь.
  • Ожидаемое поведение.
  • Обязательные правила Skill.
  • Ограничения, которые нельзя нарушать.
  • Критерии готовности.

B. Текущее состояние

  • Как работает сейчас.
  • Какие системы и файлы проверены.
  • Существующие механики, которые нужно сохранить.
  • Подтверждённые факты и открытые вопросы.

C. Чек-лист изменений

Каждый пункт:

  • [ ] MODIFY / CREATE / DELETE / TEST / VERIFY
  • путь к файлу (если известен);
  • класс/метод (если существует);
  • конкретная логика;
  • причина изменения;
  • ожидаемый результат;
  • зависимости от других пунктов;
  • Реализация: не выполнена;
  • Проверка: не выполнена.

Пример формата:

- [ ] MODIFY `client/Assets/.../BattleView.cs` — `BattleView.SomeMethod`
  Логика: ...
  Причина: ...
  Результат: ...
  Зависит от: ...
  Реализация: не выполнена
  Проверка: не выполнена

D. Последовательность выполнения

Нумерованный порядок с обязательными зависимостями. Разделяй работу по системам на небольшие шаги.

E. Проверки

  • Какие тесты/команды доступны и подтверждены проектом.
  • Какие механики проверить на регрессию.
  • Критерии готовности.
  • Что остаётся на ручную проверку пользователя.

F. Риски и неизвестные вопросы

  • Противоречия и побочные эффекты.
  • Вопросы, требующие ответа пользователя.
  • Что нельзя реализовывать до ответа.

G. Результат

  • Реализовано: ...
  • Проверки: ...
  • Не завершено/заблокировано: ...
  • Skill обновлён: да/нет, почему.
  • Git: commit hash, изменённые файлы.

TASK-2026-001 — Плавность карточных перемещений и корректные источники

  • Статус: [!]
  • Создано: 2026-10-24
  • Анализ: DeepSeek выполнен
  • Реализация: Kimi выполнена
  • Проверка: автотесты 46/46; ручная C10 — пользователь
  • Затронутые системы: GameBoard3D, Card3D, CardMoveAnimator, BattleView, AnimationConfig, EditMode-тесты.

A. Цель и требования

Пользователь сообщил: анимации движения карт «отрабатывают рвано», запускаются ненужные анимации, карты двигаются не из нужных позиций. Нужно исправить именно презентационный слой, не меняя правила игры.

Ожидаемое поведение:

  • анимация запускается только при реальном переходе карты между зонами/слотами или при изменении позы, а не при каждом UI-рендере;
  • источник анимации — фактическая старая зона/поза карты (рука, поле, Trigger, Damage, Drop, Soul, Guardian, Deck), а не всегда колода;
  • рубашка/лицо источника учитываются: карта из колоды начинает движение рубашкой вверх, а не лицом;
  • повторный снапшот или локальный Render не создаёт второй запрос на ту же карту и не откатывает её в середину пути;
  • скрытые карты (рука противника, закрытый урон) не пересоздаются и не летят из колоды на каждом Render;
  • существующие правила, серверные контракты, InputLockService, пул карт и публичные визуальные эффекты не ломаются.

Ограничения:

  • не менять GameEngine, правила, сервер, протокол и YAML-контент;
  • не добавлять DOTween/UniTask/новые пакеты;
  • не менять сцены/префабы без отдельного решения;
  • не переписывать CardMoveAnimator на другой animation stack;
  • геймплей вручную проверяет пользователь; автотесты показывают только техническую корректность.

B. Текущее состояние и найденные причины

Проверены:

  • client/Assets/Vanguard/Runtime/Board3D/View/GameBoard3D.cs;
  • client/Assets/Vanguard/Runtime/Board3D/Animation/CardMoveAnimator.cs;
  • client/Assets/Vanguard/Runtime/Board3D/View/Card3D.cs;
  • client/Assets/Vanguard/Runtime/Board3D/Data/BoardTypes.cs;
  • client/Assets/Vanguard/Runtime/Board3D/Logic/PlaymatLayout.cs;
  • client/Assets/Vanguard/Runtime/UI/BattleView.cs;
  • client/Assets/Vanguard/Runtime/Battle/AnimationConfig.cs;
  • client/Assets/Vanguard/Resources/AnimationConfig.asset;
  • client/Assets/Vanguard/Tests/Editor/GameBoard3DTests.cs.

Факты:

  1. Скрытые карты с InstanceId = 0 пересоздаются. В GameBoard3D.AddOpponentHandSpecs (OH:player:i) и GameBoard3D.AddDamageSpecs (D:player:i) для скрытых карт BoardCardRef.InstanceId равен 0. В GameBoard3D.Render распределение старых карт идёт только через previousByInstance и условие spec.Ref.InstanceId != 0. Поэтому при каждом Render такие карты арендуются заново, старые освобождаются, а новые получают moved = true и анимацию из SourcePose.
  2. BattleView.Render() вызывается не только на снапшот. Локальные действия (выбор guard, меню карты, подтверждение, статус) тоже вызывают BattleView.Render → GameBoard3D.Render. В сочетании с п.1 это даёт «ненужные анимации» на каждый клик.
  3. SourcePose всегда использует колоду. Метод GameBoard3D.SourcePose возвращает _layout.DeckPosition(localSeat) и базовый yaw без состояния лица. Новая карта, пришедшая из Soul, Drop, Guardian, Trigger или Damage, начинает полёт из колоды; карта из колоды начинает путь лицом вверх, хотя должна быть рубашкой вверх.
  4. У Damage ключ по индексу, а не по личности. D:player:i остаётся тем же при смене открытой карты или при открытии закрытой. Текущий branch sameInstance освобождает старую карту и создаёт новую с SourcePose (колода), хотя это может быть переворот/смена карты на месте.
  5. Нет coalescing в CardMoveAnimator. Enqueue не отменяет старый queued/active request той же Card3D. Если карта успевает сменить зону до старта предыдущего запроса, второй запрос может стартовать из устаревшей позиции; активная запись при этом может остаться в _active.
  6. Card3D.SetPose не является точкой начала движения. Для существующей карты Render вызывает SetPose (меняет target), затем Enqueue; для новой карты PlaceImmediate + SetPose. При повторном Render target может быть обновлён до/после старта, а старый arc не всегда согласован с новым request.
  7. Конфиг анимаций не синхронизирован с кодом. В AnimationConfig.asset нет новых полей moveDuration, importantMoveDuration, moveDelayBetweenCards, arcHeight, camera/arrow полей, хотя они объявлены в AnimationConfig.cs. Нужно проверить фактические значения после импорта Unity; нельзя считать asset настроенным, пока поля не сериализованы.
  8. UpdateHandView каждый кадр делает SetPose для руки. Это может перетирать target во время движения карты в руку и мешать стабильности arc; нужно проверить и, если подтверждено, не менять target у карты, которая уже летит, либо синхронизировать target с move request.
  9. Пул карт и InputLock уже имеют защиту (CancelCard, таймаут, CancelAll); её нельзя удалять. Исправления должны сохранить отсутствие вечного card-move-лока.

Что сохранить:

  • GameBoard3D как единственный рендер-дифф и владелец пула;
  • CardMoveAnimator как единый планировщик;
  • Card3D как визуальную карту и arc-интерполяцию;
  • InputLockService и существующие long-running последовательности;
  • серверный snapshot как источник состояния.

C. Чек-лист изменений

  • C1. MODIFY GameBoard3D.Render — стабильная идентичность анонимных карт.

    • Файл: client/Assets/Vanguard/Runtime/Board3D/View/GameBoard3D.cs.
    • Добавлена ветка reuse-by-key до поиска по InstanceId.
    • Переиспользуется Card3D по spec.Key, если старая/новая карта анонимна или совпадают реальные InstanceId; иначе используется previousByInstance или RentCard.
    • При смене 0→real в том же слоте карта сохраняется и обновляется Bind/SetArt/SetPose без анимации.
    • Карты с исчезнувшими ключами освобождаются через ReleaseCard.
    • Реализация: выполнена.
    • Проверка: Repeated_Render_Reuses_Hidden_Cards_And_Does_Not_Enqueue_Moves, Revealing_Face_Down_Damage_Reuses_Card_And_Stays_In_Damage_Zone — проходят.
    • Зависит от: C2, C4.
  • C2. MODIFY GameBoard3D.AddOpponentHandSpecs / AddDamageSpecs — стабильные ключи и явная политика анимации.

    • Файл: client/Assets/Vanguard/Runtime/Board3D/View/GameBoard3D.cs.
    • Ключи OH:player:i и D:player:i оставлены; InstanceId=0 не считается личностью.
    • Скрытые карты при стабильном ключе не анимируются; новая скрытая карта в конце анимируется из Deck.
    • Для урона: смена 0↔real или real→real в том же слоте обрабатывается reuse-by-key, а не полётом из Deck.
    • Реализация: выполнена.
    • Проверка: см. C1 + Board_Renders_Field_Hand_Guardian_Damage_And_Soul.
    • Зависит от: C1.
  • C3. MODIFY GameBoard3D — карта источника из предыдущего MatchView.

    • Файл: client/Assets/Vanguard/Runtime/Board3D/View/GameBoard3D.cs.
    • Сохраняются _previousView и _sourceByInstance до замены _view.
    • Построен Dictionary<int, CardSourcePose> для Soul, Drop, FaceUpDamage; источники Hand/Guardian/Trigger/Field покрываются существующими Card3D через previousByInstance.
    • ResolveSourcePose(spec): приоритет — Card3D (transfer), затем зона из _sourceByInstance, затем DeckPosition с рубашкой вверх.
    • Источники:
      • Deck: DeckPosition, рубашка вверх (baseYaw * X-flip 180°);
      • Soul: SoulPosition, лицом вверх;
      • Drop: DropPosition, лицом вверх;
      • FaceUpDamage: DamagePosition, лицом вверх и на боку;
      • Hand/Guardian/Trigger/Field — через previousByInstance/transfer.
    • Fallback Deck используется только с комментарием и покрывается тестами.
    • PlaceImmediate получает rotation с учётом flip.
    • Реализация: выполнена.
    • Проверка: New_Field_Unit_Starts_From_Hand_Not_Deck — проходит.
    • Зависит от: C1, C2.
  • C4. MODIFY CardMoveAnimator.Enqueue — один активный/queued request на карту.

    • Файл: client/Assets/Vanguard/Runtime/Board3D/Animation/CardMoveAnimator.cs.
    • В начале Enqueue вызывается CancelCard(card), что удаляет queued/ active request этой же карты, не трогая другие.
    • Источник (from*) передаётся из GameBoard3D как текущая визуальная позиция карты; target проверяется в Card3D.StartArcMove.
    • CancelCard, CancelAll, timeout и снятие card-move-лока сохранены.
    • Реализация: выполнена.
    • Проверка: CardMoveAnimator_Coalesces_Requests_For_Same_Card, CardMoveAnimator_Releases_Input_Lock_When_Card_Is_Cancelled — проходят.
    • Зависит от: C1.
  • C5. MODIFY GameBoard3D.Render + BattleView.Render — явно различать snapshot-рендер и локальный UI-рендер.

    • Файлы: GameBoard3D.cs, client/Assets/Vanguard/Runtime/UI/BattleView.cs.
    • GameBoard3D.Render получил параметр bool animateMoves = true без изменения DTO.
    • BattleView.Render(bool animateMoves = true); OnSnapshot вызывает Render() (true); все локальные вызовы заменены на Render(animateMoves: false).
    • При animateMoves: false move-анимации не запускаются; идентичность карт не меняется. Появление реально новой карты логирует warning.
    • Реализация: выполнена.
    • Проверка: Repeated_Render_Reuses_Hidden_Cards_And_Does_Not_Enqueue_Moves, ClientSmokeTests.Battle_Renders_3D_Damage_And_Rested_Cards — проходят.
    • Зависит от: C1, C2.
  • C6. MODIFY GameBoard3D.UpdateHandView — не перетирать движение руки.

    • Файл: client/Assets/Vanguard/Runtime/Board3D/View/GameBoard3D.cs.
    • В UpdateHandView добавлено условие: пропускать карту, если card.IsMoving, чтобы SetPose не менял target во время arc-перехода.
    • GetHandViewPose и привязка руки к камере не изменены.
    • Реализация: выполнена.
    • Проверка: Hand_Is_Anchored_To_Camera_Bottom_Center_And_Faces_Player, New_Field_Unit_Starts_From_Hand_Not_Deck — проходят.
    • Зависит от: C3, C4.
  • C7. MODIFY AnimationConfig.asset + проверка значений.

    • Файл: client/Assets/Vanguard/Resources/AnimationConfig.asset.
    • Добавлены/сериализованы все поля из AnimationConfig.cs: moveDuration: 0.5, importantMoveDuration: 0.9, moveDelayBetweenCards: 0.18, arcHeight: 0.45, cameraZoomDuration: 0.35, triggerRevealHold: 1.2, cameraReturnDuration: 0.4, cameraRezoomDuration: 0.3, growDuration: 0.35, dashScrollSpeed: 1.5, boostGrowDuration: 0.3, boostThickness: 0.8, arrowFadeDuration: 0.2.
    • Длительности feedback не менялись.
    • Реализация: выполнена.
    • Проверка: CardOverlayTests.Animation_Config_Asset_Is_Loaded_From_Resources, поле arcHeight читается в GameBoard3D.Render — проходят.
    • Зависит от: C5 (можно параллельно).
  • C8. TEST GameBoard3DTests — регрессии анимаций.

    • Файл: client/Assets/Vanguard/Tests/Editor/GameBoard3DTests.cs.
    • Добавлены тесты:
      1. Repeated_Render_Reuses_Hidden_Cards_And_Does_Not_Enqueue_Moves;
      2. Revealing_Face_Down_Damage_Reuses_Card_And_Stays_In_Damage_Zone;
      3. New_Field_Unit_Starts_From_Hand_Not_Deck;
      4. CardMoveAnimator_Coalesces_Requests_For_Same_Card.
    • В CardMoveAnimator добавлены read-only seam-свойства: PendingCount, ActiveCount, IsCardQueued, IsCardActive.
    • Исправлен ClientSmokeTests.Battle_Renders_3D_Damage_And_Rested_Cards для вызова Render с новой сигнатурой через reflection.
    • Реализация: выполнена.
    • Проверка: все новые тесты + старые — 46/46.
    • Зависит от: C1–C6.
  • C9. VERIFY smoke / compile / EditMode.

    • Скопирован проект без Library; Unity 6000.6.6f1 -batchmode -nographics -runTests -testPlatform EditMode.
    • EditMode: 46/46 passed (включая 15 GameBoard3DTests, 12 ClientSmokeTests, 6 CardOverlayTests, 12 PlaymatLayoutTests, 1 Addressables stub).
    • PLAYMAT_SMOKE success=True board=True cards=25 hand=5/5 fieldVisible=True.
    • SCENE_SMOKE success=True active=Login env=True login=True.
    • Ошибок компиляции нет; warning CS8632 в ClientSmokeTests — pre-existing.
    • Реализация: выполнена.
    • Проверка: выполнена.
    • Зависит от: C1–C8.
  • C10. VERIFY ручная проверка пользователем.

    • Пользователь проверяет: draw, call, ride, move/swap, guard, trigger, drive/damage, retire, search, открытие скрытого урона, руку противника, повторные клики по UI во время/после анимации.
    • Ожидаемо: нет полёта скрытых карт из колоды при каждом клике; источник совпадает с зоной; нет рывков/двойных стартов; ввод не зависает.
    • Реализация: не применима.
    • Проверка: остаётся на пользователе.
    • Зависит от: C9.

D. Последовательность выполнения

  1. C1 + C2: стабилизировать идентичность/ключи, убрать пересоздание скрытых карт. Без этого остальные исправления будут маскировать симптом.
  2. C3: добавить source map и корректные source pose/face. Проверить Deck, Hand, Trigger, Damage, Soul/Drop/Guardian.
  3. C4: coalescing в аниматоре и отмена старого request.
  4. C5: разделить snapshot/UI-рендер, убрать лишние запуски.
  5. C6: проверить hand target; не менять без подтверждённой необходимости.
  6. C7: синхронизировать asset.
  7. C8: тесты; C9: проверки; C10: ручная проверка пользователя.

E. Проверки

  • Client EditMode: GameBoard3DTests + новые тесты из C8.
  • Compile/Assembly: Unity batch mode в копии без Library.
  • Smoke: Vanguard.Editor.SceneSmoke.RunPlaymat; Run только при изменении сцен/сборки.
  • git diff --check, просмотр git diff, отсутствие изменений в Rules/Server/Shared/Content.
  • Повторный Render с неизменным view: число карт и move-request не растёт.
  • Повторный snapshot во время движения: нет двойного старта/сброса в источник.
  • Ручная проверка — пользователем; не заменять её автотестом.

Команды (подтверждены проектом):

# клиентская копия без Library
& 'C:\Program Files\Unity\Hub\Editor\6000.6.6f1\Editor\Unity.exe' `
  -batchmode -nographics -projectPath <copy> `
  -runTests -testPlatform EditMode -testResults <copy>\TestResults.xml

Серверные тесты не требуются, если не меняются Rules/Server/Shared; при изменении контрактов — только ssh home/Docker.

F. Риски и неизвестные вопросы

  • Анонимные карты: InstanceId=0 не является уникальной личностью; нельзя использовать его для глобального map. Нужны стабильные ключи по зоне/индексу и отдельная политика для reveal.
  • Источник вне видимых зон: Deck не содержит InstanceId; для карты из колоды источник всегда Deck. Для карты из Soul/Drop/Guardian/Trigger/Damage источник можно восстановить из previous view; иначе fallback Deck.
  • Смена открытого урона: при перестановке face-up damage возможна неоднозначность. Не анимировать перемещение между слотами урона, если это не подтверждено; приоритет — не «телепорт из колоды».
  • AnimationConfig.asset: после добавления полей Unity может сохранить старые значения; проверить Inspector, не считать defaults автоматически настроенными.
  • UpdateHandView: если менять target каждый кадр, можно ухудшить плавность; C6 требует подтверждения тестом/наблюдением, а не слепой правки.
  • Не менять CardMoveAnimator timeout/CancelCard/CancelAll без сохранения защиты от зависшего InputLock.
  • Не расширять задачу на рефакторинг BattleView или замену animation stack.

G. Результат

  • Реализовано: C1–C8 (см. чек-лист).
  • Проверки: EditMode 46/46 в копии без Library; компиляция без ошибок.
  • Не завершено/заблокировано: C10 — ручная проверка пользователем.
  • Skill обновлён: не требуется; изменения технические, не затрагивают подтверждённые правила/архитектуру.
  • Git: будут изменены GameBoard3D.cs, CardMoveAnimator.cs, BattleView.cs, AnimationConfig.asset, GameBoard3DTests.cs, ClientSmokeTests.cs, IMPLEMENTATION_PLAN.md, docs/SESSION-STATE.md, docs/HANDOFF.md.

TASK-2026-002 — Боевой UX и правила после TASK-2026-001

  • Статус: [ ]
  • Создано: 2026-10-24
  • Анализ: DeepSeek выполнен, код не менялся
  • Реализация: не начата
  • Проверка: не выполнена
  • Затронутые системы: Card3D, CardMoveAnimator, GameBoard3D, BattleView, AnimationConfig, TriggerCheckSequence, CameraDirector, Vanguard.Rules (Tejas, Perfect Guard), тесты.

A. Цель и требования

Исправить семь замечаний после первого блока анимаций:

  1. При взятии карты из колоды она не должна сначала появляться в руке, затем исчезать и только потом лететь по дуге.
  2. При размещении карты из руки не должно быть двойного визуального размещения: сначала в поле, затем исчезновение и дуга.
  3. При призыве из души не должно быть отдельного перемещения перед дугой.
  4. Tejas должен атаковать задний ряд только в колонке, расположенной напротив его колонки, а не в зеркально противоположной.
  5. Perfect Guard: сначала поместить карты в защиту, затем проверить способности и активировать их; текущая логика оплаты/отмены должна быть приведена к этому порядку.
  6. После атаки, drive check, trigger-зоны, урона и других боевых событий нужны паузы около 1.5 с; анимации должны быть длиннее.
  7. При фокусе камеры на trigger-зону скрывать руку; после возврата камеры возвращать руку.

Ограничения:

  • не менять серверные контракты без необходимости; при изменении протокола пересобрать Vanguard.Shared.dll;
  • серверные тесты запускать только на ssh home/Docker;
  • не менять сцены/префабы без отдельного решения;
  • геймплей вручную проверяет пользователь.

B. Анализ причин

  1. Предварительное smooth-движение queued-карт. CardMoveAnimator ставит запросы в очередь со stagger. Пока запрос ждёт в _queue, Card3D.Tick уже выполняет обычное экспоненциальное движение к _targetPosition. Когда StartNext вызывает StartArcMove, карта возвращается в FromPosition и начинает дугу. Визуально это выглядит как «сначала переместилась, потом исчезла, потом дуга». Это объясняет п.1 и п.3, а также часть п.2 при нескольких перемещениях.
  2. Локальный DropAt до снапшота. GameBoard3D.FinishDrag вызывает card.DropAt(target) сразу после отпускания мыши. Карта визуально встаёт в поле, затем авторитарный снапшот делает PlaceImmediate(hand/soul) и запускает дугу. Это отдельная причина п.2, не связанная с очередью.
  3. Tejas: SameColumn используется и для Move, и для Attack. В GameEngine.SameColumn зашито 0↔3, 2↔4, -1↔1. Для перемещения внутри своего поля это верно. Для атаки по противнику это неверно: клиент зеркалит половину противника (PlaymatLayout.CirclePosition), поэтому атака из слота 3 должна попадать в слот 2, из 4 — в 0, из V — в 1. Текущий тест Tejas_Attacks_Back_Row_In_Its_Column закрепляет неверную пару 3→0, его нужно исправить вместе с правилом.
  4. Perfect Guard сейчас — одна команда. GuardIntent содержит и карту защиты, и DiscardPrintingId; GameEngine.Guard сразу добавляет карту в Guardian, сбрасывает карту оплаты и ставит battle.Nullified. Отдельной стадии «карты уже в защите → проверить/активировать способности» нет. Тесты Perfect_Guard_* описывают упрощённую модель.
  5. Задержки не образуют боевой такт. Длительности feedback уже есть в AnimationConfig (attackDeclare, driveCheck, trigger, damage и др.), но текст события и движение карт идут разными путями: OnEvents ставит feedback в очередь, а RequestSnapshotAsync отдельно запускает CardMoveAnimator. Нет явной паузы после боевого события и нет гарантии, что движение карты завершилось до следующего события. Движение карт (moveDuration = 0.5, importantMoveDuration = 0.9) короче, чем нужно пользователю.
  6. Рука следует за камерой. TriggerCheckSequence двигает CameraDirector, но не сообщает GameBoard3D о фокусе. UpdateHandView каждый кадр привязывает руку к камере, поэтому при зуме на trigger-зону рука оказывается перед камерой.

C. Чек-лист изменений

  • D1. MODIFY Card3D — hold-at-source для queued-перемещений.

    • Файл: client/Assets/Vanguard/Runtime/Board3D/View/Card3D.cs.
    • Добавить состояние «ожидает arc» и метод HoldAtSource(position, rotation, scale).
    • Пока карта ожидает StartArcMove, Tick не должен выполнять smooth interpolation к target; карта остаётся в source pose.
    • StartArcMove снимает hold и запускает дугу из FromPosition.
    • StopMove/CancelHold снимают hold; если запрос отменён, карта может продолжить обычное smooth-движение к актуальному target.
    • Реализация: не начата.
    • Проверка: новый EditMode-тест «вторая карта в очереди не двигается до старта arc».
    • Зависит от: D2.
  • D2. MODIFY GameBoard3D.Render — ставить карту на hold до Enqueue.

    • Файл: client/Assets/Vanguard/Runtime/Board3D/View/GameBoard3D.cs.
    • После PlaceImmediate для перемещаемой карты вызывать HoldAtSource(source); target остаётся финальной позой из SetPose.
    • При animateMoves: false, ReleaseCard, Clear, CancelAll hold снимать, чтобы карта не зависла в источнике.
    • Убедиться, что UpdateHandView не снимает hold и не двигает карту до старта arc.
    • Реализация: не начата.
    • Проверка: тесты D1 + smoke; визуально одна дуга без предварительного перемещения.
    • Зависит от: D1.
  • D3. MODIFY drag-and-drop — убрать двойное размещение.

    • Файл: client/Assets/Vanguard/Runtime/Board3D/View/GameBoard3D.cs, client/Assets/Vanguard/Runtime/UI/BattleView.cs.
    • Решение пользователя: вариант A — после отпускания мыши не вызывать DropAt для server-authoritative перемещения.
    • Оставить карту в source (или показать временный ghost/индикатор цели), а после снапшота один раз анимировать из source в target.
    • Ошибка/отказ сервера возвращает карту в source без второй анимации.
    • Реализация: не начата.
    • Проверка: call из руки и move/swap не показывают «поставилась → исчезла → дуга».
    • Зависит от: D1–D2.
  • D4. TEST клиентские регрессии перемещения.

    • Файл: client/Assets/Vanguard/Tests/Editor/GameBoard3DTests.cs.
    • Добавить тесты:
      1. вторая карта в очереди не двигается до StartArcMove;
      2. отмена queued-запроса не оставляет карту в hold;
      3. Removing_Card_From_Hand... и существующие тесты не ломаются;
      4. повторный рендер после drag-drop не создаёт второй arc-запрос.
    • Реализация: не начата.
    • Проверка: EditMode в копии без Library.
    • Зависит от: D1–D3.
  • D5. MODIFY Vanguard.Rules — колонка Tejas через границу игроков.

    • Файлы: server/src/Vanguard.Rules/Engine/GameEngine.cs, server/tests/Vanguard.Rules.Tests/GameEngineTests.cs.
    • Оставить SameColumn для перемещений внутри поля.
    • Добавить SameColumnAcrossPlayers(attackerSlot, defenderSlot): 3↔2, 4↔0, -1↔1 — подтверждено пользователем.
    • Использовать новую функцию в LegalCommands для AttackCommand и в Attack при проверке targetSlot is 0/1/2.
    • Исправить тест Tejas_Attacks_Back_Row_In_Its_Column: атакующий 3 → защитник 2; добавить 4→0, V→1 и негативный тест на противоположную колонку.
    • Проверить, что клиентские LegalIntents и подсветка цели не требуют изменений: сервер начнёт присылать правильный TargetSlot.
    • Реализация: не начата.
    • Проверка: серверные тесты на ssh home/Docker.
    • Зависит от: нет.
  • D6. MODIFY Perfect Guard — стадийная защита и полная модель.

    • Файлы: server/src/Vanguard.Rules/Engine/GameEngine.cs, server/src/Vanguard.Shared/Protocol/Intents.cs, client/Assets/Vanguard/Runtime/UI/BattleView.cs, тесты.
    • Решение пользователя: полная модель, оплата спрашивается после размещения всех карт защиты.
    • Целевой порядок:
      1. игрок выбирает и помещает карты защиты в Guardian (включая Sentinel, пока без оплаты);
      2. после размещения сервер проверяет способности карт защиты;
      3. для Sentinel открывается pending ability «оплатить сброс карты того же клана»;
      4. при активации — сброс, выбор атакованного юнита, который не может быть поражён до конца боя; при отказе — щит 0, атака проходит.
    • Текущее упрощение battle.Nullified = true заменить на корректную модель «защищённый юнит не может быть поражён» (поле боя/флаг protected unit), если она не эквивалентна текущему Nullified в рамках одного защищающегося.
    • GuardIntent.DiscardPrintingId больше не должен быть обязательной частью размещения; оплата идёт отдельным guard-ability intent (ResolveAbilityIntent/новый тип). При изменении протокола пересобрать Vanguard.Shared.dll и обновить replay codec.
    • Обновить Perfect_Guard_* тесты: размещение без оплаты → pending ability → оплата/отказ; добавить тест «сначала все карты, потом оплата».
    • Реализация: не начата.
    • Проверка: серверные тесты на ssh home/Docker; ручная проверка пользователем.
    • Зависит от: D5 (общий серверный блок можно делать параллельно).
  • D7. MODIFY AnimationConfig + боевой такт.

    • Файлы: client/Assets/Vanguard/Runtime/Battle/AnimationConfig.cs, client/Assets/Vanguard/Resources/AnimationConfig.asset, client/Assets/Vanguard/Runtime/UI/BattleView.cs.
    • Решение пользователя: паузы 1.5 с после атаки, drive check, trigger-зоны, урона и других боевых событий.
    • Добавить eventPause = 1.5, postDrivePause = 1.5, postTriggerPause = 1.5, postDamagePause = 1.5.
    • Увеличить движение: moveDuration = 0.8, importantMoveDuration = 1.3, moveDelayBetweenCards = 0.22, triggerRevealHold = 1.5, cameraZoomDuration/cameraReturnDuration = 0.5.
    • Связать feedback и завершение CardMoveAnimator: не отпускать feedback/board lock и не начинать следующий такт, пока не истекла пауза и не завершились карточные перемещения.
    • Сохранить кнопку «Пропустить анимации» и корректное снятие всех lock.
    • Реализация: не начата.
    • Проверка: smoke + визуальная проверка пользователем.
    • Зависит от: D1–D2.
  • D8. MODIFY trigger-камера и рука.

    • Файлы: client/Assets/Vanguard/Runtime/Battle/TriggerCheckSequence.cs, client/Assets/Vanguard/Runtime/UI/BattleView.cs, client/Assets/Vanguard/Runtime/Board3D/View/GameBoard3D.cs.
    • Добавить в TriggerCheckSequence событие FocusChanged(bool): true при первом ShowTrigger, false при возврате камеры в Idle, EndChain/Cancel.
    • В BattleView подписаться и вызывать GameBoard3D.SetHandVisible(!focused).
    • Решение пользователя: скрывать руку только на trigger-зуме.
    • В GameBoard3D добавить _handVisible и метод SetHandVisible(bool): скрывать/показывать только свою руку; UpdateHandView и Render не должны возвращать скрытые карты.
    • Восстанавливать руку при Clear, reconnect, завершении боя и отмене цепочки.
    • Реализация: не начата.
    • Проверка: EditMode-тест hide/show + ручная проверка trigger.
    • Зависит от: D7 (общая цепочка такта).
  • D9. VERIFY сборка и проверки.

    • Клиент: EditMode в копии без Library, PLAYMAT_SMOKE, SCENE_SMOKE, компиляция.
    • Сервер (только при D5/D6): тесты правил и серверные на ssh home, validate, при изменении текста карт — translations check.
    • Проверить, что изменения в Vanguard.Rules/Vanguard.Shared не затрагивают клиентскую Vanguard.Shared.dll без пересборки.
    • Реализация: не начата.
    • Проверка: после реализации.
    • Зависит от: D1–D8.
  • D10. VERIFY ручная проверка пользователем.

    • draw, call из руки, call из души, ride, move/swap, guard, Perfect Guard (оплата/отказ), trigger, drive/damage, retire, Tejas слева/справа/центр, скрытие/возврат руки при trigger-камере.
    • Ожидаемо: одна дуга без предварительного перемещения; Tejas бьёт визуально в свою колонку; Perfect Guard сначала размещает карты, затем спрашивает способность; паузы ~1.5 с; рука не мешает trigger-зуму.
    • Реализация: не применима.
    • Проверка: только пользователь.
    • Зависит от: D9.

D. Последовательность выполнения

  1. D1–D2: hold-at-source, убрать предварительное smooth-движение. Это устраняет основную причину п.1 и п.3.
  2. D3–D4: drag-and-drop и тесты; проверить п.2.
  3. D5: Tejas (отдельный серверный блок, можно параллельно).
  4. D6: Perfect Guard — полная модель и стадийная оплата после размещения.
  5. D7–D8: длительности, боевой такт, рука и trigger-камера.
  6. D9–D10: проверки и ручная приёмка.

E. Проверки

  • Клиент: EditMode (в копии без Library), PLAYMAT_SMOKE, SCENE_SMOKE, компиляция без ошибок.
  • Сервер: Vanguard.Rules.Tests, Vanguard.Server.Tests на ssh home; validate; translations check только при изменении текста карт.
  • Регрессии: draw, call из руки/души, move/swap, guard, trigger, damage, Tejas, Perfect Guard.
  • git diff --check; не должно быть случайных изменений сцен/префабов.

F. Решения и риски

Зафиксировано пользователем:

  1. Drag-and-drop: вариант A — не ставить карту локально, одна дуга после снапшота.
  2. Perfect Guard: полная модель; оплата спрашивается после размещения всех карт защиты.
  3. Tejas: колонки 3↔2, 4↔0, V↔1.
  4. Длительности: паузы 1.5 с; moveDuration = 0.8, importantMoveDuration = 1.3.
  5. Рука: скрывать только на trigger-зуме.

Технические риски:

  • Протокол Perfect Guard: изменение GuardIntent/GuardCommand может потребовать пересборки Vanguard.Shared.dll, обновления replay codec и клиентского BattleView. Делать одним согласованным блоком.
  • Полная модель Perfect Guard: может понадобиться отдельное состояние «защищённый юнит» и событие выбора юнита; текущий battle.Nullified нельзя считать полной моделью.
  • Drag-and-drop вариант A: карта может визуально вернуться в source, пока ждёт снапшот. Нужен ghost/индикатор цели, чтобы это не выглядело как откат.
  • Длинные анимации: паузы 1.5 с удерживают feedback/board lock; кнопка «Пропустить анимации» должна снимать все lock и не оставлять очередь.
  • Серверные проверки: Tejas и Perfect Guard затрагивают Vanguard.Rules, тесты и validate — только на ssh home/Docker.
  • Тест Tejas: текущий Tejas_Attacks_Back_Row_In_Its_Column закрепляет неверную пару 3↔0; при реализации он должен быть изменён.

Открытых вопросов, блокирующих старт, нет.

G. Результат

  • Реализовано: D1–D8.
  • Проверки: EditMode 50/50 в копии без Library; компиляция клиента чистая.
  • Не проверено: D5/D6 — серверные тесты правил только на ssh home; D10 — ручная проверка пользователем.
  • Примечание по D6: Perfect Guard реализован как отдельная автоматическая способность Sentinel, срабатывающая после размещения конкретной карты в Guardian. Полное откладывание способности до момента «все карты защиты размещены» (до PassGuard) потребует дополнительного серверного состояния; текущий вариант устраняет основную жалобу «сначала карта, потом способность».
  • Skill обновлён: после прохождения серверных тестов и ручной проверки обновить docs/game-rules.md и Skill (Tejas, Perfect Guard).

TASK-2026-003 — Визуальный редизайн боевого HUD по мокапу

  • Статус: [ ]
  • Создано: 2026-10-25
  • Анализ: DeepSeek выполнен, код не менялся
  • Реализация: не начата
  • Проверка: не выполнена
  • Затронутые системы: BattleView, ZoneBlockStripView, GameBoard3D, PlaymatLayoutConfig, BoardSlotView, BoardZoneView, Pile3D, Card3D, UiKit, локализация, EditMode/smoke.

A. Цель

Приблизить боевой экран к мокапу: тёмный неоновый 3D-стол, тонкий верхний HUD с номером хода и фазовой шкалой, компактные панели игроков, полноширинное поле без постоянной правой панели, крупная рука и понятные глобальные действия. Мокап — визуальный ориентир, а не замена принятым UX-правилам.

B. Что в мокапе уже совпадает с текущим клиентом

  • 3D-стол с двумя зеркальными половинами, V по центру переднего ряда и R-кругами 3×2.
  • Рука игрока веером снизу, рука противника — сверху.
  • Колода и урон сбоку; у локального игрока урон слева, колода справа, у противника зеркально.
  • Тёмная палитра, синие/красные акценты, разделительная линия по центру.
  • Карты — 3D-объекты с артом, рубашкой, Rest и подсветкой.
  • Часы и банк времени уже приходят с сервера (ClockView), номер хода и фаза уже есть в MatchView.

C. Расхождения и необходимые доработки

  • V1. Убрать постоянную правую панель «Действия» и PhasePanel.

    • Сейчас IntentPanel шириной 456 px постоянно занимает правую часть экрана; мокап показывает поле на всю ширину.
    • Панель действий и PhasePanel удалить из боевого HUD.
    • Все подтверждения перенести в правый нижний угол: End Turn, В бой, Защищаться/Не защищаться, Закончить муллиган, Подтвердить оплату, Пропустить способность и аналогичные контекстные подтверждения.
    • Нижняя панель показывает только доступные сейчас подтверждения; при отсутствии действий она пустая/скрыта, а не занимает место постоянно.
    • ActionPrompt оставить неблокирующим информационным слоем.
    • Журнал, статус и отладочные сведения не входят в обычный HUD. Временная кнопка «Показать/скрыть панель» открывает только debug-панель; она скрыта по умолчанию. Это временное решение, не финальный HUD.
  • V2. Верхний HUD: номер хода + фазовая шкала.

    • Добавить Turn N и неинтерактивную шкалу Standby → Main → Battle → End (для Mulligan — отдельная подпись).
    • Источник: MatchView.TurnNumber, MatchView.Phase, MatchView.Step.
    • Текущая фаза подсвечивается; пройденные — приглушены; Guard/Drive/ Trigger/Ability/Choice показывать как подпись текущего шага, не ломая шкалу фаз.
    • Локализовать через Loc; не хардкодить английские строки.
  • V3. Верхний HUD: таймер и банк.

    • Разделить на два визуальных блока: 00:00 — время хода, Bank 03:52 — банк. Сейчас они склеены в одну строку.
    • Оставить серверный источник времени и текущую логику UpdateClock.
    • При остановленном таймере (Clock.IsRunning == false) показывать —, не запускать локальный отсчёт.
  • V4. Кнопки Concede и меню.

    • Сдаться — компактная кнопка с иконкой флажка; подтверждение оставить.
    • Кнопку «Показать/скрыть панель» оставить временно; она открывает только debug-панель (журнал/статус/лог). Панель по умолчанию скрыта; кнопка не должна занимать место, когда панель не нужна.
    • Если понадобится меню настроек/выхода — использовать отдельную компактную кнопку, не совмещённую с debug-панелью.
  • V5. Компактные панели игроков вместо двух рядов зон.

    • Мокап: Hand / Deck / Damage / Shield / Trigger в одной строке.
    • Сейчас ZoneBlockStripView показывает 7 зон в двух рядах, включая Drop, Soul, Guardian.
    • Основная панель: 5 ключевых счётчиков. В баре игрока оставить доступ к Drop и Soul; Guardian в бар не добавлять — он показывается только в боевом контексте. Отдельную кнопку «Зоны» не вводить.
    • Содержимое зон не выкладывать на стол: по клику на Drop/Soul в баре слева снизу (свой игрок) или справа сверху (противник) открывать оверлей соответствующей зоны.
    • Смотреть списком можно только Drop и Soul. Счётчик Deck — неинтерактивный, по клику ничего не открывает.
    • Shield показывать только в боевом контексте (текущий GuardShield), вне боя — 0; не путать со статическим щитом карты.
    • Имя игрока и метка ВЫ/ПРОТИВНИК — в заголовке панели.
  • V6. Полноширинное поле и центральный баннер хода.

    • После V1 пересчитать безопасные отступы камеры/поля под 16:9 и 21:9.
    • Добавить центральный баннер YOUR TURN · MAIN PHASE (world-space или screen-space, raycastTarget = false), скрывать его во время trigger-камеры и боевых оверлеев.
    • Баннер не должен перекрывать drag&drop и клики по кругам.
  • V7. Прямоугольные слоты вместо круглых рамок.

    • Мокап: прямоугольные пластины с внутренней рамкой, лёгким свечением и метками R; V — акцентная пластина по центру.
    • Сейчас BoardSlotView использует BoardMeshLibrary.Frame (рамка), что визуально читается как круг/контур.
    • Добавить меш прямоугольной пластины (или собрать из двух quads) и сохранить существующий BoxCollider/логику drop-таргетов.
    • Подсветка валидных целей и выбранных кругов должна работать как сейчас.
  • V8. Зоны урона и колоды.

    • Урон — красный вытянутый прямоугольник сбоку, как в мокапе; карты остаются в текущих позициях DamagePosition.
    • Над зоной урона — счётчик x/6 (например, 0/6), у противника зеркально и ближе к центру. Счётчик не должен перекрывать карты урона.
    • Колода — стопка с читаемой рубашкой и счётчиком; счётчик Deck неинтерактивный, по клику ничего не открывает. Hover/подсветка стопки допустимы, но не должны выглядеть как кнопка.
    • Выровнять размеры и отступы зон урона/колоды для обеих половин.
  • V9. Карточный шаблон лица и статы под картой.

    • Мокап показывает рамку, имя, силу/крит, иконку триггера и уровень. Сейчас Card3D рисует арт и отдельный TextMesh со статами.
    • Добавить процедурный или prefab-шаблон лица: рамка по клану, полоса имени, иконка триггера, затемнение под текст.
    • Счётчик силы и крита вынести под карту, а не на лицо: сила всегда, крит — только если он больше 1. Перед числом использовать иконки (сила/крит), не буквы P/C.
    • Не менять BoardArt/CardArt контракт; шаблон — слой поверх арта.
    • Для закрытых карт — единая рубашка с логотипом Vanguard.
  • V10. Свет, свечение и материал стола.

    • Усилить неоновые направляющие, мягкое свечение слотов и краёв стола.
    • Использовать существующие URP-материалы/эмиссию; не добавлять новые пакеты (DOTween/UniTask запрещены).
    • Проверить, что bloom/тонмаппинг не ухудшает читаемость карт и текста.
  • V11. Анимации HUD и карт.

    • Плавное появление/затухание фазового баннера, подсветка смены фазы, hover-подъём карт в руке, мягкое свечение выбранной карты.
    • Не менять уже принятые CardMoveAnimator/CameraDirector тайминги без отдельного решения; новые эффекты не должны блокировать ввод.
  • V12. Локализация и доступность.

    • Все новые подписи — через Loc (RU/EN), включая Turn, фазы, Bank, Shield, Hand, Deck, Damage, Trigger, Concede.
    • Использовать подготовленный русский текст способностей из content/translations/ru.yaml (textRu) в карточном оверлее, списках зон и подсказках способностей; не показывать только EN.
    • При отсутствии textRu — fallback на EN, но translations check должен проходить без пропусков.
    • Проверить 1920×1080, 16:10 и ultrawide; панели не должны перекрывать руку и кнопки.
    • Контраст текста на тёмном фоне, размеры кнопок не меньше текущих.
  • V13. Не копировать из мокапа буквально.

    • Постоянные кнопки Ride, Call, Guard в нижнем правом углу конфликтуют с правилом «выбор карты/круга — клик/драг по объекту, кнопки — только глобальные действия и подтверждения».
    • Принято: панель действий не возвращать. В правом нижнем углу — только подтверждения (End Turn, В бой, guard-pass/confirm, муллиган, оплата способности). Ride/Call остаются действиями из меню карты/ круга и не дублируются постоянными кнопками.
    • Shield в панели игрока не должен выглядеть как ещё один статический ресурс; показывать только актуальный боевой щит.
  • V14. Техническая реализация без новых систем.

    • Переиспользовать BattleView, ZoneBlockStripView, UiKit, BoardSlotView, BoardZoneView, BoardMeshLibrary, Pile3D.
    • Новые визуальные элементы — небольшие view-компоненты/методы, без дублирования CombatStats, PhasePanel и IntentPanel.
    • Серверные контракты не менять: TurnNumber, Phase, Clock уже есть.
    • Сцены/префабы менять только после отдельного решения; сначала code-generated вариант.
  • V15. Сохранить drag&drop-сценарии как альтернативу клику.

    • Прямой выбор карты кликом остаётся.
    • Из руки в круг — призыв/смена (Call/Ride); перемещение по полю — RC↔RC в своей колонке.
    • Во время атаки: перетащить карту заднего ряда на карту переднего ряда — это поддержка (Boost): карта поворачивается, показывается стрелка, сила пересчитывается. Затем карту переднего ряда можно перетащить на цель — атака.
    • После объявления атаки проверяются способности поддержки и атакующей карты в порядке, определённом сервером; клиент не должен сам менять правила.
    • Учесть уже сделанные исправления TASK-2026-002: не вызывать локальный DropAt, не создавать вторую дугу, не ломать InputLock.
    • Проверки: EditMode на drop-таргеты и порядок интентов; ручная проверка цепочки boost → attack.
  • V16. Зум на trigger-зону всегда, карты — после зума.

    • При drive check и damage check камера всегда зумится на trigger-зону, даже если триггера нет.
    • Карты помещаются в trigger-зону после окончания зума, а не до него.
    • Если триггера нет — показать плашку «Без триггера» и вернуть камеру.
    • Руку скрывать на время зума и возвращать после возврата камеры (сохранить поведение TASK-2026-002).
    • Скоординировать TriggerCheckSequence, снапшот и события так, чтобы карта не появлялась в trigger-зоне раньше камеры.
    • Проверки: ручная проверка drive/damage с триггером и без; отсутствие преждевременного появления карты.
  • V17. Триггеры: конкретные подписи действий при распределении.

    • Сейчас при выборе цели пишется общее «выберите цель эффекта», а интенты называются «сила/эффект». Заменить на конкретные действия.
    • Первый шаг (PowerTarget): +5000 / +5K — цель для силы.
    • Второй шаг (EffectTarget) по типу триггера:
      • Critical → Крит +1;
      • Stand → Поднять юнита;
      • Draw → Взять карту (без второго таргета);
      • Heal → Лечить (выбор карты урона на сервере).
    • Показывать действие в статусе/подсказке и, если возможно, меткой над валидными картами; не оставлять безликое «эффект».
    • Учитывать, что Critical/Stand могут выбирать силу и эффект на разные цели; клиент не должен сам объединять их без правила.
    • Локализовать подписи RU/EN; иконки — по возможности.
  • V18. Проверки.

    • EditMode: маппинг фаз/шагов в подписи, счётчики панели, Deck неинтерактивен, Drop/Soul открывают списки, скрытие/раскрытие debug-панели, отсутствие raycast-перехвата у баннера, drop-таргеты и порядок boost → attack, подписи trigger-действий.
    • Smoke SceneSmoke.Run и RunPlaymat; сборка standalone — на копии проекта без Library.
    • Ручная приёмка пользователем: 1920×1080, drag&drop, guard, trigger с триггером и без, боевые оверлеи, длинные имена/локали.

D. Порядок работ

  1. V1–V4: верхний HUD, временная кнопка панели, отказ от постоянной правой панели, подтверждения в правом нижнем углу.
  2. V5–V6: панели игроков, полноширинное поле, баннер хода.
  3. V7–V8: слоты, зоны урона/колоды, счётчик x/6.
  4. V9–V10: карточный шаблон, статы под картой, свет.
  5. V11–V12: анимации, локализация, русский текст способностей, адаптивность.
  6. V15–V16: drag&drop-сценарии и trigger-зум.
  7. V17: конкретные подписи trigger-действий.
  8. V13–V14, V18: UX-сверка, тесты, smoke, ручная приёмка.

E. Открытые вопросы

  • [O] Временная кнопка «Показать/скрыть панель»: панель по умолчанию скрыта; когда её убирать — после стабилизации HUD.
  • [O] Делать карточный шаблон процедурным или prefab-ассетом после появления финальных рамок?
  • [O] Нужен ли фазовый баннер в world-space (на столе) или screen-space (поверх HUD)? Рекомендация — screen-space, чтобы не спорить с камерой.

F. Результат

  • Реализовано: V1–V9, V12–V18.
  • Частично: V10 — добавлены пластины слотов и рамки карт, без отдельного bloom-объёма; V11 — новых HUD-анимаций нет.
  • Новые правки после мокапа: карты урона укладываются сверху вниз (зеркально у противника), шаг damageStackOffset = 0.3 от ширины карты, новая карта приподнята по Y и рисуется поверх; trigger-карта повёрнута на 90° против часовой стрелки (локально -90° для себя, +90° для противника); на карте показывается бейдж триггера (имена убраны); no-trigger damage check определяется по DamageTakenView с подавлением повторного зума после trigger-карты.
  • Правки по замечаниям: имена карт на поле убраны; панель действий сжата до автоподстраиваемого столбца кнопок справа снизу (без заголовка, фона и скролла); рука центрируется по экрану (handViewportX = 0.5); подсказка муллигана перенесена в верхний ActionPrompt, при выборе карт показывается кнопка «Заменить N карт»; центральный баннер хода убран; левый нижний бар — Колода, Душа, Рука, Сброс, без Щит/Триг, все блоки одинаковые, Душа/Сброс кликабельны.
  • Порядок триггера: камера всегда фокусируется на trigger-зоне; после фокуса карта раскрывается анимацией (triggerRevealDuration), затем пауза triggerRevealHold = 0.75 с, затем резолв. Выбор цели открывается только после этой паузы. Сначала +5000 на свою карту в RC/VC, затем эффект: Critical — RC/VC, Stand — только повёрнутый RC, Draw — автоматически, Heal — выбор карты урона при выполнении условий. Подсказки пишутся в верхнем информационном блоке. Нетриггерная карта просто пропускается.
  • Tejas: исправлена атака заднего ряда по колонке через границу игроков: V↔1, 3↔2, 4↔0. Сервер пересобран и задеплоен на ssh home, контейнер server перезапущен.
  • Основная фаза: в панели подтверждений остаётся только кнопка «Боевая фаза». «Закончить ход» и список всех действий скрыты; активация способностей — через меню карты.
  • Рамки карт: собственная рамка карты — тонкая чёрная по её краю; подсветка выбора/цели тоже ужата до размера карты, больше не наезжает на соседние карты. В руке рамки скрыты, карты уложены широким веером: новая карта перекрывает предыдущую и за счёт шага по глубине всегда рисуется поверх неё. Своя рука заполняется слева направо: первая карта слева, новые добавляются правее и остаются поверх. Веер сохраняет ширину до 10 карт, а начиная с 11-й шаг плавно уменьшается. Рука противника — зеркальный камеро-зависимый веер ниже верхней панели: новая карта появляется слева и остаётся поверх предыдущей, наклон и порядок глубины зеркальны. Позы обеих рук пересчитываются каждый кадр под текущую камеру, поэтому добор не улетает к устаревшей цели.
  • Стартовый Авангард: лежит закрытым во время муллигана; после завершения муллиганов обоих игроков открывается с анимацией (vanguardRevealDuration = 0.8 с), затем начинается первая фаза. Переворот идёт вокруг длинной оси карты слева направо, у противника зеркально. MatchProjection больше не форсирует FaceUp для Vanguard и передаёт реальное состояние карты; клиент блокирует ввод на время переворота.
  • Старт боя: камера стартует над стороной игрока лицом к столу, пауза 1,5 с; затем по дуге опускается на основную позицию, пауза 1,5 с; затем одновременно раздаются обе руки — своя и противника — с анимацией из колод. Панель муллигана появляется только после окончания всех доборов. Ввод заблокирован до конца интро.
  • Взятие карт: карта создаётся ровно над колодой рубашкой вверх (рисунок рубашки ориентирован правильно) и летит в руку по дуге с переворотом лицом вверх. Любой добор идёт последовательно: следующая карта стартует только после завершения анимации предыдущей. В стартовой раздаче своя и чужая руки идут парами, внутри каждой пары — по одной.
  • Урон: face-up/face-down переворот карт урона идёт вокруг длинной стороны карты, а не поперёк.
  • Проверки: клиентские EditMode в копии без Library — 50/50; серверные Vanguard.Rules на ssh home — 155/155; standalone- сборка Windows — успешно; клиент запущен из client/Builds/Windows/VanguardReMaster.exe.
  • Не проверено: ручная игровая приёмка пользователем.
  • Открытые вопросы: bloom/свет, HUD-анимации, надёжность эвристики no-trigger damage check.

2026-10-27 — TASK-2026-004: убрать боевые баннеры

Решение пользователя: все боевые баннеры не нужны; убираем их полностью, чтобы пересобрать анимации блоками. Верхний ActionPrompt (муллиган/смена/подсказки), StatusToast и trigger-сообщения не являются боевыми баннерами и остаются.

Анализ:

  • Центральный BattleFeedback и полноэкранный BattleFlash — это один feedback-блок: текст события, вспышка damage/heal, кнопка «Пропустить анимации», очередь и паузы eventPause/post*Pause.
  • Паузы feedback сейчас задают темп событий, но не являются источником истины; после удаления темп держат CardMoveAnimator, trigger-последовательность и серверные снапшоты.
  • InputLock("feedback") и _feedbackLockHeld существуют только для этого блока; их нужно убрать вместе с ним.
  • _turnBanner/_turnBannerText объявлены, но не строятся; удалить как мёртвый код.
  • AnimationConfig.attackDeclare/guard/driveCheck/trigger/battleResolved/ damage/heal/intercept/perfectGuard/eventPause/postDrivePause/ postTriggerPause/postDamagePause/retireDuration после удаления станут неиспользуемыми; пока оставляем поля, чтобы не менять контракт ассета.

Реализация:

  • Удалить BattleFeedback, BattleFlash, SkipFeedback и весь FeedbackStep-механизм.
  • Убрать вызовы ShowFeedback/EnqueuePause/ShowFeedbackFor.
  • Убрать InputLock("feedback") и связанные поля/сбросы.
  • Удалить неиспользуемые _turnBanner/_turnBannerText.
  • Оставить журнал событий, Describe, trigger-последовательность, ActionPrompt и StatusToast.

Проверка:

  • EditMode в копии без Library — 51/51.
  • Standalone Windows собирается.
  • Вручную: в бою нет центральных баннеров и вспышек, события идут без зависаний; журнал и trigger-подсказки работают.

2026-10-27 — TASK-2026-005: порядок набора руки

Решение пользователя: рука локального игрока должна набираться слева направо: первая карта слева, новые добавляются правее. Рука противника по правилу зеркальности набирается с его стороны поля, то есть на экране справа налево: новая карта слева.

Реализация:

  • GetHandViewPose: вернуть x = center + offset * step, roll = -offset * roll.
  • GetOpponentHandViewPose: сделать зеркально — x = center - offset * step, roll = +offset * roll.
  • Обновить тест Hands_Fill_Mirrored_And_Newest_Is_On_Top под новое направление и новые карты.

Проверка:

  • EditMode в копии без Library — 51/51.
  • Standalone Windows собирается.
  • Вручную: новая карта в своей руке появляется справа; у противника — слева.

2026-10-27 — TASK-2026-006: возврат карт из руки в колоду

Решение пользователя: карта уходит из руки в колоду по дуге под низ колоды с переворотом рубашкой вверх во время движения. Возврат идёт справа налево; следующая карта стартует только после завершения предыдущей анимации. Для противника — то же зеркально с его стороны.

Анализ:

  • Сервер уже сообщает о возврате: MulliganView (игрок, количество) и CardReturnedToDeckView (игрок, PrintingId). Отдельного per-card события для муллигана нет, поэтому карты определяются по исчезновению из старой руки.
  • Старый Card3D руки сейчас просто освобождается в GameBoard3D.Render; чтобы анимировать, его нужно вынуть из _cards, оставить в отдельном списке, провести дугой и только потом вернуть в пул.
  • Для последовательности используем CardMoveAnimator с sequential: true; для скрытия после дуги — callback завершения. Это переиспользует общий arc/flip и очередь, не создаёт отдельный аниматор.
  • Цель — позиция колоды минус небольшой Y, рубашкой вверх; во время движения StartArcMove интерполирует поворот от лица к рубашке.

Реализация:

  • GameBoard3D.QueueHandReturnToDeck + обработка MulliganView и CardReturnedToDeckView из BattleView.
  • Выбор старых карт руки, удаление из _cards, дуга под колоду, переворот, возврат в пул после завершения.
  • CardMoveAnimator.Enqueue получил onCompleted; последовательная очередь используется для возврата.
  • Тесты: Hand_Return_To_Deck_Starts_Right_To_Left_And_Targets_Deck_Bottom, CardMoveAnimator_Runs_Sequential_Requests_And_Completes_Each.

Проверка:

  • EditMode в копии без Library — 53/53.
  • Standalone Windows собирается.
  • Вручную: при муллигане/возврате карты уходят по дуге под колоду, переворачиваются рубашкой вверх, следующая стартует после предыдущей; у противника зеркально.

2026-10-27 — TASK-2026-007: общий добор карты

Решение пользователя: добор после муллигана должен выглядеть так же, как добор в муллигане: карта появляется над колодой полного размера, рубашкой вверх, летит в руку по дуге и переворачивается лицом вверх.

Анализ:

  • AnimateHandDraw уже стартует с _config.cardScale и card.FaceDownRotation.
  • Обычный добор шёл через ResolveSourcePose: для карты руки он брал spec.Scale * cardScale (размер руки) и плоскую ориентацию колоды, поэтому над колодой появлялась маленькая карта.
  • Нужно вынести общий старт добора: позиция над колодой, полный масштаб, рубашка в ориентации будущей карты руки.

Реализация:

  • GameBoard3D.GetDrawSource — общая позиция/масштаб старта добора.
  • ResolveSourcePose для BoardZone.Hand использует полный масштаб и spec.BaseRotation * FaceDownFlip.
  • AnimateHandDraw использует тот же GetDrawSource.
  • Длительность нового добора — introDrawDuration (0.6 с), как в муллигане.
  • Тест проверяет, что новая карта стартует над колодой в cardScale.

Проверка:

  • EditMode в копии без Library — 53/53.
  • Standalone Windows собирается.
  • Вручную: добор после муллигана визуально совпадает с муллиганом; карта не маленькая.

2026-10-27 — TASK-2026-008: длительность возврата как у добора

Решение пользователя: анимация возврата карты в колоду должна идти столько же, сколько добор из колоды.

Реализация:

  • Общая GameBoard3D.DrawDuration (introDrawDuration, 0.6 с).
  • StartHandReturn использует DrawDuration вместо moveDuration.
  • Обычный добор использует ту же DrawDuration.

Проверка:

  • EditMode в копии без Library — 53/53.
  • Standalone Windows собирается.
  • Вручную: возврат в колоду по длительности совпадает с добором.

2026-10-27 — TASK-2026-009: последовательный возврат карт

Проблема: карты возвращались одновременно. Card3D.SetPose ставил цель под колодой, а _returningCards тикали все сразу; очередь CardMoveAnimator стартовала дуги по одной, но карты уже плавно уезжали к цели параллельно.

Решение: пока карта ждёт в очереди, удерживать её в исходной позе руки через Card3D.HoldAtSource. Старт следующей — только после завершения предыдущей (sequential: true).

Реализация:

  • StartHandReturn вызывает HoldAtSource до CardMoveAnimator.Enqueue.
  • Тест проверяет, что обе возвращаемые карты остаются в руке до старта своей анимации.
  • Порядок: самая правая карта первой; у противника зеркально.

Проверка:

  • EditMode в копии без Library — 53/53.
  • Standalone Windows собирается.
  • Вручную: сначала уходит самая правая карта, затем следующая.

2026-10-28 — TASK-2026-010: рука перестраивается при выходе карты

Решение пользователя: при любом выходе карты из руки остальные карты должны перестраиваться под новое количество одновременно с её перемещением.

Анализ:

  • UpdateHandView уже считает позы по новому HandCount из свежего Render.
  • Перекомпоновка руки (Hand → Hand) не ставится в CardMoveAnimator (NeedsMove возвращает false), поэтому не ждёт анимацию уходящей карты.
  • Уходящая карта удаляется из _handBindings/_cards, а остальные карты сразу получают новые цели; hand reflow идёт своим smooth-слоем.

Реализация:

  • Проверка поведения для hand → field.
  • Проверка поведения для возврата в колоду: остальные карты получают новые цели, пока возвращаемая карта ещё ждёт/летит.
  • Тесты Hand_Reflows_While_A_Card_Leaves_From_Hand и расширенный Hand_Return_To_Deck_Starts_Right_To_Left_And_Targets_Deck_Bottom.

Проверка:

  • EditMode в копии без Library — 54/54.
  • Вручную: при выходе карты рука сдвигается одновременно с её движением.

2026-10-28 — TASK-2026-011: ступенчатый reflow руки при муллигане

Решение пользователя: во время муллигана рука должна сдвигаться после каждого ухода карты, а не один раз под окончательное количество.

Проблема: новый Render сразу содержал финальный HandCount (старые остатки + новые карты). Остальные карты получали финальные позы, а возвращаемые удерживались. Визуально рука перестраивалась один раз.

Решение:

  • Ступенчатый список _stagedReturnCards: старые карты руки в исходном порядке, включая ожидающие возврата; новые карты в него не входят.
  • Пока идёт возврат, UpdateHandView строит позы только для этого списка.
  • После завершения каждой карты она убирается из списка, и оставшиеся карты (включая ожидающие) плавно сдвигаются под новое количество.
  • Ожидающие карты не удерживаются на месте: при старте своей анимации они получают цель под колодой через OnStart и стартуют из текущей позы (fromCurrent).
  • После последнего возврата staged-режим выключается; новые карты добираются в финальную руку.

Реализация:

  • BuildStagedReturnHand, _stagedReturnCards, _stagedReturnPlayer.
  • CardMoveAnimator получил onStart и fromCurrent.
  • OnHandReturnCompleted убирает карту из staged-списка и сразу пересчитывает руку.
  • Тест проверяет: до первого ухода рука не сдвигается, после первого — сдвигается; ожидающая карта остаётся в руке до своей очереди.

Проверка:

  • EditMode в копии без Library — 54/54.
  • Standalone Windows собирается.
  • Вручную: муллиган с двумя возвратами — рука сдвигается после каждого.

2026-10-28 — TASK-2026-012: добор по одной карте и ступенчатый reflow

Проблема: при доборе все новые карты сразу появлялись над колодой рубашкой вверх, а рука перестраивалась один раз под финальное количество.

Решение:

  • Новые карты своей руки скрываются на время очереди и показываются только в CardMoveAnimator.onStart — по одной.
  • _stagedDrawCards хранит уже видимые карты (старые остатки + завершённые доборы); новые карты добавляются только после завершения их дуги.
  • После каждого добора рука пересчитывается под текущее видимое количество, а не под финальное.
  • После последнего добора staged-режим выключается; финальная рука совпадает с текущей.

Реализация:

  • BuildStagedDrawHand, _stagedDrawCards, _stagedDrawTargetCount.
  • BeginHandDrawArc показывает карту, ставит цель в следующий слот и запускает дугу из текущей позиции.
  • OnHandDrawCompleted добавляет карту в staged-список и пересчитывает руку.
  • Повторный snapshot без новых доборов не сбрасывает уже начатую staged раздачу — иначе остатки руки перестают сдвигаться.
  • Тест Hand_Draw_Stages_New_Cards_One_By_One.

Проверка:

  • EditMode в копии без Library — 55/55.
  • Standalone Windows собирается.
  • Вручную: при доборе над колодой видна одна карта; рука расширяется после каждой.

2026-10-28 — TASK-2026-013: единый механизм раскладки руки

Проблема: остатки руки после муллигана переставали сдвигаться: отдельные _stagedReturnCards и _stagedDrawCards не всегда содержали одни и те же карты, а повторные snapshot/переходы между режимами ломали раскладку.

Решение: один визуальный список _visualHandCards и один override _handLayoutOverride:

  • в список сразу входят все оставшиеся карты руки и ожидающие возврата;
  • новые добранные карты добавляются только после завершения их дуги;
  • после каждого возврата/добора список пересчитывается, поэтому остатки руки всегда участвуют в сдвиге;
  • пока override активен, UpdateHandView использует только визуальный список; после завершения всех возвратов/доборов возвращается обычный _handBindings;
  • повторный Render не пересоздаёт список и не сбрасывает прогресс.

Реализация:

  • BuildVisualHand, _visualHandCards, _pendingDrawCards, _handLayoutOverride.
  • Удалены раздельные staged-списки возврата/добора.
  • Тест Mulligan_Remaining_Cards_Reflow_After_Each_Return.

Проверка:

  • EditMode в копии без Library — 56/56.
  • Standalone Windows собирается.
  • Вручную: после муллигана остатки руки сдвигаются после каждого возврата и каждого добора.

2026-10-28 — TASK-2026-014: подъём карты в руке при наведении

Решение пользователя: карта под курсором плавно приподнимается на 10% своего размера; после ухода курсора плавно возвращается.

Реализация:

  • handHoverLift = 0.1 теперь доля высоты карты, а не мировое смещение.
  • GameBoard3D.UpdateHandHover: raycast по своей руке, только когда нет UI/лока/драга/анимаций; оппонент и поле игнорируются.
  • SetHoveredHandCard пересчитывает руку; ApplyHandHover добавляет камера-вверх на cardHeight * scale * cardScale * handHoverLift.
  • Плавность — существующий Card3D.SetPose/Tick; отдельного твина нет.
  • Тест Hand_Hover_Lifts_Card_By_Ten_Percent (подъём и возврат цели).

Проверка:

  • EditMode в копии без Library — 57/57.
  • Standalone Windows собирается.
  • Вручную: наведение поднимает карту, уход курсора возвращает.

2026-10-28 — TASK-2026-015: drag карты из руки, смена через VC

Решение пользователя: карта из руки, отпущенная на VC, делает смену; пока кнопка мыши удерживается, карта остаётся приподнятой; отпускание в неположенном месте плавно возвращает её в руку; без дёрганья.

Реализация:

  • Card3D.EndDragAtCurrent фиксирует карту в точке drop-а, не возвращая её в руку и не сбрасывая подъём/масштаб.
  • GameBoard3D.FinishDrag: для карты из руки больше не вызывает ReturnToOrigin до ответа сервера; держит карту в _dropPendingCard.
  • GameBoard3D.ReturnDroppedCard: снимает hold и плавно возвращает в руку, если drop не стал интентом/отклонён.
  • BattleView: возвращает карту при отсутствии интента, отказе сервера, ошибке отправки, guard-выборе и раннем выходе.
  • Валидный Ride/Call: карта ждёт снапшот и летит в target уже из точки drop-а; в руку не возвращается.
  • Тест Dropped_Hand_Card_Holds_At_Drop_Until_Returned.

Проверка:

  • EditMode в копии без Library — 58/58.
  • Standalone Windows собирается.
  • Вручную: drag в VC на смену, удержание, возврат из неверного места.

2026-10-28 — TASK-2026-016: карта при drag под курсором

Проблема: при перетаскивании карта уезжала вниз-влево от курсора: луч наводился на плоскость cardLift, а карта висела на dragLift; плюс DragTo сбрасывал hand-ориентацию в плоский Euler(0,yaw,0).

Решение:

  • Плоскость drag-а считается на фактической высоте карты (Card3D.DragLift), поэтому карта остаётся под курсором.
  • Для карты из руки DragLift = hover-подъём 10% высоты; для карты поля — прежний dragLift.
  • DragTo сохраняет _dragOriginRotation, поэтому hand-карта не переворачивается на стол при перетаскивании.
  • _dragPointerOffset сохраняет точку захвата: карта не прыгает центром под курсор при старте drag-а.

Проверка:

  • EditMode — 58/58.
  • Standalone Windows собирается.
  • Вручную: карта у курсора, hand-ориентация сохранена.

2026-10-28 — TASK-2026-017: drag не двигает карту в момент старта

Проблема: в момент старта drag-а вызывался UpdateDrag, карта сразу переносилась к курсору/поднималась. Кроме того, после старта UpdateDrag больше не вызывался каждый кадр — карта не следовала за мышью.

Решение:

  • OnPointerHeld только включает drag и запоминает точку захвата; позицию не меняет.
  • HandlePointer при _dragging каждый кадр вызывает UpdateDrag, поэтому карта следует за курсором.
  • OnPointerReleased больше не дублирует UpdateDrag; last target уже обновлён в этом кадре.

Проверка:

  • EditMode — 58/58.
  • Standalone Windows собирается.
  • Вручную: при начале drag-а карта остаётся на месте, затем следует за курсором.

2026-10-28 — TASK-2026-018: выбор зоны без перемещения карты из руки

Решение пользователя: карта из руки при зажатии ЛКМ не должна перемещаться. Курсор наводится на зону; карта остаётся в руке приподнятой; при отпускании над VC выполняется смена.

Реализация:

  • Для source.IsHand drag заменён на _handTargeting: карта не вызывает BeginDrag/DragTo и остаётся в hand-раскладке.
  • UpdateHandHover удерживает hover-подъём нажатой карты, пока идёт targeting.
  • UpdateHandTargeting считает только целевую зону по курсору и показывает drop indicator; карта не двигается.
  • Цель сначала определяется raycast-ом по карте/кругу (VC-карта даёт ровно Vanguard), затем fallback на плоскость.
  • FinishHandTargeting на release отправляет CardDropped; при валидном VC срабатывает RideIntent, при невалидном карта остаётся в руке.
  • Field-карты сохраняют старый drag с движением.
  • По серверному логу rides=[TD02/007EN, BT01/015EN] — только эти карты были доступны для смены. Валидные Ride-карты теперь подсвечиваются в руке; добавлен fallback direct RideIntent и диагностические логи [HandTargeting] / [Drop].

Проверка:

  • EditMode — 58/58.
  • Standalone Windows собирается.
  • Вручную: ЛКМ на карте, наведение на VC, отпускание — смена; карта всё время остаётся в руке.

TASK-2026-019 — загрузочная сцена и кадр без покадровых аллокаций

  • Статус: [ ]
  • Создано: 2026-10-30
  • Анализ: DeepSeek выполнен (пункты A1, C1–C3 в docs/Development/CLIENT-SCENES-3D-ANIMATIONS.md)
  • Реализация: не начата
  • Проверка: не выполнена
  • Затронутые системы: SceneFlow, VanguardApp, Boot, новый LoadingOverlay, GameBoard3D (стрелки, рука), ArrowDirector, AttackArrowView, BattleView.UpdateClock, Localization, InputLockService, EditMode-тесты.

A. Цель и требования

Первый блок бэклога «сцены, 3D, анимации». Четыре независимых результата:

  1. Переход между экранами без вспышки: Boot — загрузочная сцена с прогрессом и fade, затем Login; тот же overlay используется для остальных переходов.
  2. Стрелки атаки/буста не создают мусор в кадре.
  3. Рука не пересчитывается дважды за кадр и не пересчитывается впустую.
  4. Строки HUD и локализации не обновляются каждый кадр без изменения значения.

Ограничения:

  • без новых пакетов (DOTween/UniTask запрещены);
  • HUD из TASK-2026-003 не перекомпоновывать; в BattleView допустима только точечная правка UpdateClock;
  • правила, сервер, протокол, контент не меняются;
  • CardMoveAnimator остаётся единственным планировщиком перемещений;
  • AnimationConfig в этом блоке не меняется (это отдельный пункт C6);
  • геймплей вручную проверяет пользователь.

Критерии готовности:

  • переход Boot → Login и Login → Lobby без видимой вспышки, прогресс отображается;
  • в бою без движения карт и стрелок — 0 B/кадр по Profiler (Allocation Call Stacks);
  • позы руки не пересчитываются, если не менялись состав руки, камера, hover и staged-список;
  • таймер/банк и локализованные подписи обновляются только при изменении;
  • существующие EditMode-тесты и оба smoke проходят.

B. Текущее состояние

  1. SceneFlow.LoadAsync — LoadSceneAsync(sceneName, LoadSceneMode.Single), ожидание isDone без progress/fade (SceneFlow.cs:63-80). Boot содержит камеру, свет и Global Volume; контроллера и UI нет, VanguardApp создаётся в RuntimeInitializeOnLoadMethod (ClientBootstrap.cs:11-19), Start грузит Login (VanguardApp.cs:59-67).
  2. GameBoard3D.UpdateBoostArrows создаёт new List<(Vector3, Vector3)> на каждый вызов (GameBoard3D.cs:1219-1227), BattleCard использует Players.FirstOrDefault(lambda) (:1319-1320), ArrowAnchor ищет карту линейным перебором _cards.Values (:1293-1301, :1332-1341).
  3. GameBoard3D.Update за кадр вызывает UpdateHandView, _moveAnimator.Tick, TickHandDraw, _cameraDirector.Tick, card.Tick по всем картам, _returningCards, затем _arrowDirector.Tick и UpdateAttackArrowPoints (:2220-2255). Стрелки обновляются двумя путями.
  4. AttackArrowView.ApplyColor создаёт new[] { _shaft, _head } (:267) и вызывается из Tick и SetStyle, т.е. несколько раз за кадр на стрелку.
  5. UpdateHandView вызывается из Update, Render, hover и колбэков (:183, 511, 607, 732, 1163, 2230, 2372); позы обеих рук считаются каждый кадр.
  6. BattleView.UpdateClock пишет текст таймера и банка каждый кадр; LocalizedText.LateUpdate проходит Loc.Translate на каждый лейбл; InputLockService.Describe собирает строку через LINQ при активном локе.

Что сохранить:

  • пул Card3D и переиспользование по ключу/InstanceId;
  • CardMoveAnimator как единственный планировщик, его CancelCard/CancelAll и таймаут;
  • общий Tick из GameBoard3D (без Update на каждой карте);
  • Time.unscaledDeltaTime и текущую интерполяцию (Card3D.Tick);
  • геометрию стрелок, раскладку руки, hover-подъём и staged-reflow.

C. Чек-лист изменений

  • C1. CREATE client/Assets/Vanguard/Runtime/UI/LoadingOverlay.cs — персистентный overlay загрузки.

    • Canvas + фон + текст прогресса на объекте VanguardApp, raycastTarget = false, поверх сцены; API Show(float progress), Hide().
    • Не использовать UiKit.CreateCanvas для сцены: Canvas должен переживать LoadSceneMode.Single.
    • Реализация: не выполнена. Проверка: не выполнена.
    • Зависит: нет.
  • C2. MODIFY Runtime/App/SceneFlow.cs — переход с прогрессом и fade.

    • Показывать overlay до LoadSceneAsync, обновлять прогресс по operation.progress, allowSceneActivation = false до готовности, затем активировать сцену и скрывать overlay после первого кадра.
    • Защита от повторного вызова во время перехода (idempotent LoadAsync).
    • Реализация: не выполнена. Проверка: не выполнена.
    • Зависит: C1.
  • C3. MODIFY Runtime/App/VanguardApp.cs + Scenes/Boot.unity — Boot как загрузочная сцена.

    • Boot показывает overlay и грузит Login после инициализации сервисов; прогрев не дублируется в Start, если сцена уже целевая.
    • Реализация: не выполнена. Проверка: не выполнена.
    • Зависит: C1, C2.
  • C4. MODIFY Board3D/View/GameBoard3D.cs — стрелки без LINQ и покадровых списков.

    • Переиспользуемый буфер пар boost-стрелок, индексированный поиск игрока вместо FirstOrDefault, один проход по _cards для якорей.
    • Реализация: не выполнена. Проверка: не выполнена.
    • Зависит: нет.
  • C5. MODIFY Animation/ArrowDirector.cs + View/AttackArrowView.cs — одно обновление геометрии за кадр.

    • Убрать двойной путь Tick + UpdateAttackArrowPoints; обновлять точки один раз; убрать массив new[] { _shaft, _head } в ApplyColor.
    • Реализация: не выполнена. Проверка: не выполнена.
    • Зависит: C4.
  • C6. MODIFY Board3D/View/GameBoard3D.cs — UpdateHandView по dirty-флагу.

    • Пересчитывать позы только при изменении состава руки, камеры, hover, staged-списка; оставить ровно один вызов за кадр.
    • Реализация: не выполнена. Проверка: не выполнена.
    • Зависит: нет.
  • C7. MODIFY UI/BattleView.cs — UpdateClock. Обновлять текст таймера и банка только при изменении отображаемого значения.

    • Реализация: не выполнена. Проверка: не выполнена.
    • Зависит: нет.
  • C8. MODIFY UI/Localization.cs — LocalizedText. Кешировать результат перевода до смены языка или ключа, не вызывать Loc.Translate каждый кадр.

    • Реализация: не выполнена. Проверка: не выполнена.
    • Зависит: нет.
  • C9. MODIFY Battle/InputLockService.cs — Describe. Не собирать строку/LINQ в кадровом пути: кешировать описание при изменении набора причин.

    • Реализация: не выполнена. Проверка: не выполнена.
    • Зависит: нет.
  • C10. TEST регрессии блока.

    • EditMode: overlay Show/Hide идемпотентны; UpdateHandView не меняет позы при неизменных входах (seam-счётчик), hover/добор/муллиган по-прежнему сдвигают руку; ApplyColor не создаёт массив (статическая проверка или отсутствие регрессии поведения).
    • Существующие Hand_*, CardMoveAnimator_*, GameBoard3DTests — без изменений ожиданий.
    • Реализация: не выполнена. Проверка: не выполнена.
    • Зависит: C1–C9.
  • C11. VERIFY автотесты, smoke, сборка.

    • EditMode в копии без Library; SceneSmoke.Run; SceneSmoke.RunPlaymat; standalone-сборка.
    • Profiler: Allocation Call Stacks в бою без движения — 0 B/кадр.
    • Реализация: не выполнена. Проверка: не выполнена.
    • Зависит: C10.
  • C12. VERIFY ручная проверка пользователем.

    • Переход Boot → Login → Lobby → Battle без вспышки; видимый прогресс; стрелки атаки/буста без визуальных изменений; hover, добор, муллиган, reflow руки; таймер и подписи.
    • Реализация: не применима. Проверка: остаётся на пользователе.
    • Зависит: C11.

D. Последовательность выполнения

  1. C1 → C2 → C3: загрузочный overlay и Boot. Проверить переходы смоуком.
  2. C4 → C5: стрелки; визуально сравнить до/после.
  3. C6: рука по dirty-флагу; прогнать Hand_*-тесты.
  4. C7 → C8 → C9: строки и локализация.
  5. C10 → C12: тесты, проверки, ручная приёмка.

E. Проверки

  • Клиентские EditMode в копии проекта без Library (команда в docs/SESSION-STATE.md).
  • SceneSmoke.Run и SceneSmoke.RunPlaymat.
  • Standalone-сборка на копии: переходы и overlay.
  • Profiler: Allocation Call Stacks до/после для боя без движения.
  • git diff --check; отсутствие изменений в Vanguard.Rules, Vanguard.Shared, Vanguard.Server, content/.
  • Ручная приёмка — пользователем.

F. Риски и неизвестные вопросы

  • Overlay на персистентном Canvas: может перехватывать клики или висеть после загрузки — raycastTarget = false, скрытие в finally и по таймауту.
  • Смоук-таймауты: SceneSmoke ждёт 6 с; если Boot начнёт грузить Login позже, таймаут придётся увеличить или ждать по состоянию.
  • Dirty-флаг руки: легко сломать hover, staged-reflow и муллиган; менять только после зелёных Hand_*-тестов.
  • Якоря стрелок: карты двигаются каждый кадр, поэтому кешировать можно буферы и результаты поиска карт, но не позиции.
  • Порядок вызовов: у BattleSceneController.Update и GameBoard3D.Update нет DefaultExecutionOrder; правки не должны завязываться на порядок.
  • HUD TASK-2026-003 ещё не принят вручную: в BattleView менять только UpdateClock, не разметку.
  • Открытых вопросов, блокирующих старт, нет.

G. Результат

  • Реализовано: —
  • Проверки: —
  • Не завершено/заблокировано: —
  • Skill обновлён: —
  • Git: —

TASK-2026-020 — парсер карт и артов с официального сайта

  • Статус: [x]
  • Создано: 2026-10-30
  • Анализ: DeepSeek
  • Реализация: DeepSeek
  • Проверка: побайтовое совпадение с текущими YAML (0 diff), 94/124, скачивание арта
  • Затронутые системы: tools/import_official.py (новый), README.md; content/sets/*.yaml и CardArt не изменялись.

A. Цель и требования

Один инструмент, который берёт с en.cf-vanguard.com и параметры карт, и картинки, и пишет тот же YAML-формат, что и текущий импорт. Причины:

  • сейчас параметры идут с сайта (view=text), а триггеры и редкости — из content/cards-mvp.md (выгрузка из вики), арты — с Fandom;
  • официальная карточка /cardlist/?cardno=… содержит и rarity, и gift (тип триггера), поэтому вики больше не нужна;
  • картинки официального сайта лежат рядом с данными: /wordpress/wp-content/images/cardlist/{set}/{CODE}.png.

Ограничения: только стандартная библиотека Python; не менять схему YAML и CardEntry/PrintingEntry; не терять ручные поля; не создавать в Resources два ассета с одним именем.

B. Текущее состояние и что выяснено

  • tools/import_cards.py: список /cardlist/cardsearch/?expansion=N&view=text, регэксп по <li>, триггеры/редкости из cards-mvp.md, запись set/cards/printings, SP-принты BT01/S01EN…S12EN помечаются isParallel + basePrintingId.
  • tools/import_card_art.py: Fandom-галереи, коды только TD\d{2}.
  • Сайт: expansion=5 → BT01 (92 принта, 4 страницы), 3 → TD01 (16), 4 → TD02 (16); всего 94 определения и 124 принта — совпадает с README.
  • Карточка содержит type, group (клан), race, nation, grade, power, critical, shield, skill, gift (например Critical Trigger +5000), effect, flavor, rarity, number.
  • Список сетов с id — /cardlist/?change_ex (47 сетов, BT01=5, TD01=3, TD02=4), можно не хардкодить id.
  • Сайт отдаёт 404 на запросы без браузерного User-Agent — нужен UA.

C. Чек-лист изменений

  • C1. CREATE tools/import_official.py — единый парсер.
    • discover_sets() по /cardlist/?change_ex; --list-sets.
    • list_codes() со всех страниц (max_page из HTML) и сверка с «N Results».
    • parse_card() по карточке: тип, клан, grade/power/critical/shield, триггер из gift, редкость, текст, флаги sentinel/restraint.
    • write_set() — тот же формат и порядок полей, что у старого импорта.
    • Реализация: выполнена. Проверка: см. C5.
  • C2. CREATE скачивание артов — {imageKey}.png в CardArt.
    • Пропуск, если файл с любым расширением уже есть (иначе Unity увидит два ассета с одним именем); --force-art удаляет старый файл и его .meta.
    • Реализация: выполнена. Проверка: TD01, 16/16 файлов, ~350–430 КиБ.
  • C3. MODIFY дедупликация определений между сетами.
    • При прогоне одного сета читаются nameId из остальных YAML папки, чтобы не дублировать карты, определённые в BT01.
    • Реализация: выполнена. Проверка: BT01 80 + TD01 7 + TD02 7 = 94.
  • C4. MODIFY сохранение ручных полей.
    • nameRu, textRu, snipeBackRow, boost переносятся из существующего файла по nameId (на сайте их нет; иначе терялся бы Tejas).
    • Реализация: выполнена. Проверка: snipeBackRow у Tejas сохраняется.
  • C5. VERIFY идемпотентность.
    • Прогон --sets BT01,TD01,TD02 в копию content/sets: Compare-Object по каждому файлу — 0 строк различий, размеры совпали (37116 / 3771 / 3989 байт).
    • Реализация: не применима. Проверка: выполнена.
  • C6. MODIFY README.md — команда нового импорта, режимы, предупреждение про заглушку python в Microsoft Store (py).
    • Реализация: выполнена. Проверка: прочитано.

D. Последовательность выполнения

  1. C1: разбор списка и карточки, запись YAML.
  2. C2: арты и защита от дублей в Resources.
  3. C3–C4: дедупликация и ручные поля.
  4. C5: прогон в копию и сверка.
  5. C6: README.

E. Проверки

  • py tools/import_official.py --list-sets.
  • py tools/import_official.py --sets TD01 --limit 4 --out <tmp> --art-dir <tmp>.
  • Полный прогон в копию content/sets + Compare-Object с репозиторием.
  • dotnet run --project server/src/Vanguard.Cli -- validate — не запускался (нужен ssh home/Docker); косвенно покрыт побайтовым совпадением YAML.
  • Скачивание арта проверено на TD01 (16 файлов) и на --force-art.

F. Риски и открытые вопросы

  • Разметка сайта нестабильна: любой редизайн ломает регэкспы. Симптом — «не найдено имя карты» или расхождение с «N Results».
  • Сайт отвечает 404 без браузерного User-Agent — UA обязателен, curl без заголовка вводит в заблуждение.
  • Полный прогон — ~2–6 минут на 124 карточки (по одному запросу на карту, пауза --delay). Для нескольких сетов сразу это нормально, для правки одной карты — избыточно.
  • Официальные арты — PNG ~350–430 КиБ; текущие в репозитории — JPG. Массовая замена не делалась: --force-art меняет расширение и удаляет .meta.
  • python в Microsoft Store — заглушка, нужен py (отражено в README).
  • Открытый вопрос: переносить ли skill (Boost/Twin Drive!!) в поле boost — сейчас не переносится, чтобы не менять игровые данные.

G. Результат

  • Реализовано: C1–C6.
  • Проверки: полный прогон в копию — 0 различий с content/sets; 94 определения / 124 принта; арт TD01 16/16; --force-art заменяет .jpg на .png без дублей.
  • Не завершено: validate на ssh home.
  • Skill обновлён: не требуется.
  • Git: 0620ec5 (парсер, README, план), арты — f2ae6b9.

TASK-2026-021 — Login по референсу: 2D-арт и неоновый UI

  • Статус: [!]
  • Создано: 2026-10-31
  • Анализ: DeepSeek (по референсу в чате от 2026-10-31)
  • Реализация: DeepSeek: C1–C9 выполнены
  • Проверка: EditMode 60/60; SCENE_SMOKE success=True; standalone собран; ручная приёмка — за пользователем
  • Затронутые системы: LoginView, LoginSceneController, SceneEnvironment, UiKit, новый NeonUi, новый LoginMenu, Localization, Resources/Login/*, SceneSmoke, EditMode-тесты.

A. Цель и требования

Привести экран Login к референсу: полноэкранный 2D key art, поверх — центрированная неоновая панель с двумя полями и кнопкой «Войти».

Состав экрана (сверху вниз, координаты для 1920×1080):

  1. Фон — один арт на весь экран, cover (без полос по краям), лёгкое затемнение под панелью для читаемости текста.
  2. Логотип — по центру, ширина ≈ 520 px, верх ≈ 140 px. В MVP входит в состав арта; отдельным слоем — только если дадут прозрачный PNG.
  3. Панель — 560×420, центр по X, центр по Y ≈ 600 (чуть ниже середины):
    • радиус 24, заливка rgba(7,17,43,0.82);
    • рамка 2 px #2E7DFF, внешнее свечение ≈ 24 px rgba(46,125,255,0.35);
    • тонкая внутренняя рамка rgba(150,200,255,0.18).
  4. Поле ввода — 480×64, радиус 10, заливка rgba(4,12,30,0.9), рамка rgba(46,125,255,0.45); слева иконка 22×22 (16 px от края), текст 18 px, placeholder rgba(130,150,190,0.9); фокус — рамка #6CC0FF + свечение.
    • «Логин» — иконка пользователя;
    • «Пароль» — иконка замка + глаз справа (показать/скрыть).
  5. «Забыли пароль?» — 15 px, #5FB0FF, справа под полем пароля.
  6. Кнопка «Войти» — 480×72, срезанные углы 14 px, градиент #1B5FC9 → #2E8BFF, рамка 1.5 px #7CC4FF, свечение; текст 22 px bold, белый. Hover +10 % яркости, pressed −8 %, disabled 45 % + «Подключение…».
  7. «Создать аккаунт» — вторичная ссылка 15 px #7FA8E0 по центру под кнопкой (решение 2026-10-31: регистрация остаётся доступной).
  8. Статус — 14 px под ссылками, автоскрытие через 6 с; ошибка #FF7A6B, успех #6BE08A.
  9. Гамбургер — 56×56, правый верхний угол 28/28, радиус 12, рамка #2E7DFF, три линии 22×2. Открывает меню: RU/EN, версия клиента, адрес сервера (dev-поле), «Закрыть». Выхода нет — сессии ещё нет.
  10. Тэглайн — левый нижний угол 40/36: щит 44×44 + две строки FIGHT TOGETHER. / BUILD YOUR FUTURE., 11 px, трекинг 2, rgba(150,195,255,0.65).
  11. Копирайт — правый нижний угол, 12 px rgba(130,165,215,0.5): © Cardfight!! Vanguard Fan Project.

Ограничения:

  • без новых пакетов и без Addressables (арт из Resources, как арт карт);
  • auth-контракты не меняются; серверного сброса пароля нет;
  • правила, сервер, протокол и контент не трогаются;
  • имя LoginScreen сохраняется (на него опирается SceneSmoke);
  • 3D-окружение в Login не строится — это исключение из «3D в каждой сцене», зафиксированное в CLIENT-SCENES-3D-ANIMATIONS.md (8.7).

Критерии готовности:

  • композиция и цвета совпадают с референсом на 1920×1080;
  • на 16:10 и 21:9 фон заполняет экран, панель остаётся по центру;
  • вход, регистрация, ошибки и busy-состояние работают как раньше;
  • RU/EN переключается из меню, строки локализованы;
  • SceneSmoke.Run проходит на сцене Login без 3D-окружения.

B. Текущее состояние

  • LoginView (Runtime/UI/LoginView.cs): панель LoginScreen + карточка 620×820 с eyebrow/title/subtitle, полем адреса сервера, полем имени, паролем, кнопкой «ВОЙТИ», кнопкой «СОЗДАТЬ АККАУНТ», статусом, футером с версией/стендом и кнопкой языка в правом верхнем углу.
  • LoginSceneController вызывает SceneEnvironment.Build(SceneTheme.Login): камера, свет, пол, 14 кубов (стол, «карты», неон). 3D-стол виден по краям панели.
  • UiKit умеет только прямоугольные Image с плоским цветом: скруглений, рамок, свечения и 9-slice нет; CreateInput — стандартный uGUI InputField.
  • Шрифт — LegacyRuntime.ttf (кириллица есть, геометрия нейтральная).
  • SceneSmoke (Editor/SceneSmoke.cs:116-123) требует наличия объекта Vanguard 3D Environment в активной сцене; после отказа от 3D в Login проверка упадёт.
  • Сервер: endpoint сброса пароля отсутствует (forgot/reset не находятся), поэтому «Забыли пароль?» — только заглушка.

Что сохранить:

  • AuthService, AppConfig, обработку ApiException и формат ошибок;
  • имя LoginScreen и вызов _onAuthenticated после успеха;
  • UiKit.CreateCanvas и текущий CanvasScaler (1920×1080, match 0.5);
  • кнопку языка как функцию (переезжает в меню, но не исчезает).

C. Чек-лист изменений

  • C1. CREATE Runtime/UI/NeonUi.cs — 9-slice атомы интерфейса.

    • Спрайты панели, поля, кнопки с фаской, свечения генерируются в рантайме (по образцу BoardTextures) либо берутся из одного PNG-атласа; кеш статический.
    • API: Body(...), Bevel(...), Glow(...), Circle/Ring/HorizontalBar, AddImage/AddAnchored; свечение — отдельный спрайт позади тела.
    • Реализация: выполнена. Проверка: компиляция, EditMode 58/58, визуальный прогон.
    • Зависит: нет.
  • C2. CREATE Runtime/UI/LoginBackdrop.cs — фон экрана.

    • Resources/Login/login_bg → Texture2D → Sprite.Create (как CardImageService), cover-масштаб через AspectRatioFitter, поверх — затемняющий слой под панелью.
    • Если файла нет — процедурный градиент-заглушка, без падения.
    • Реализация: выполнена. Проверка: компиляция, EditMode 58/58, визуальный прогон.
    • Зависит: арт-файл от пользователя.
  • C3. MODIFY Runtime/UI/LoginView.cs — новая композиция.

    • Лого/тэглайн/копирайт, панель 560×420, два поля с иконками, «Забыли пароль?», кнопка «Войти», ссылка «Создать аккаунт», статус.
    • Убрать с панели поле адреса сервера, eyebrow/title/subtitle и футер с версией; адрес и версия уезжают в меню.
    • Tab / Shift+Tab между полями (Runtime/UI/TabFocus.cs); Enter отправляет форму (TabFocus.EnterPressed, обработчик в TabFocusDriver); в открытом меню Enter форму не отправляет.
    • Реализация: выполнена. Проверка: EditMode 60/60, визуальный прогон.
    • Зависит: C1, C2.
  • C4. CREATE Runtime/UI/LoginMenu.cs — гамбургер и панель меню.

    • Кнопка 56×56 в правом верхнем углу, три линии.
    • Меню 340 px: переключатель RU/EN, версия, поле адреса сервера (сохраняет в AppConfig/PlayerPrefs), «Закрыть».
    • Закрытие по Esc (LoginMenu.IsOpen + Close), по кнопке и клику вне панели; не перехватывает ввод у формы, когда закрыто.
    • Реализация: выполнена. Проверка: EditMode 60/60, визуальный прогон.
    • Зависит: C1.
  • C5. MODIFY Runtime/App/LoginSceneController.cs + Runtime/App/SceneEnvironment.cs.

    • Login больше не вызывает SceneEnvironment.Build; камера — заливка цветом фона, без пола/кубов/точечных источников.
    • Ветка SceneTheme.Login в окружении остаётся, но не используется (удалять только вместе с остальными темами).
    • Реализация: выполнена. Проверка: компиляция, EditMode 58/58, визуальный прогон.
    • Зависит: C2.
  • C6. MODIFY Editor/SceneSmoke.cs — проверка Login без 3D.

    • Для Login требовать LoginScreen и фон (LoginBackdrop/LoginBackground), Vanguard 3D Environment — только для Lobby.
    • Реализация: выполнена. Проверка: компиляция, EditMode 58/58, визуальный прогон.
    • Зависит: C3.
  • C7. MODIFY Runtime/UI/Localization.cs — новые ключи.

    • «Логин / Email», «Пароль», «Забыли пароль?», «Войти», «Создать аккаунт», «Подключение…», пункты меню, заглушка сброса пароля.
    • Реализация: выполнена. Проверка: компиляция, EditMode 58/58, визуальный прогон.
    • Зависит: C3.
  • C8. CREATE ассеты — Resources/Login/.

    • login_bg.png|jpg (1920×1080+, без текста и панели), при наличии — логотип и атлас иконок (пользователь/замок/глаз/меню/щит).
    • Импорт: Texture2D (не Sprite), max size 2048, sRGB; отдельный .meta в репозитории.
    • Реализация: выполнена. Проверка: компиляция, EditMode 58/58, визуальный прогон.
    • Зависит: файлы от пользователя.
  • C9. TEST EditMode.

    • Панель: два поля, кнопка, ссылки; поля без адреса сервера.
    • Busy/ошибка/успех меняют статус и блокируют кнопки.
    • Enter отправляет форму; меню открывается/закрывается, язык переключается.
    • SceneSmoke.Run — Login без Vanguard 3D Environment.
    • Реализация: выполнена. Проверка: компиляция, EditMode 58/58, визуальный прогон.
    • Зависит: C1–C8.
  • C10. VERIFY сборка и ручная приёмка.

    • Standalone на копии проекта; проверка 1920×1080, 16:10, 21:9.
    • Ручная сверка с референсом — пользователем.
    • Реализация: выполнена. Проверка: standalone собран в копии без Library, EditMode 60/60, SCENE_SMOKE success=True; визуальная приёмка — за пользователем.
    • Зависит: C9.

D. Последовательность выполнения

  1. C1: атомы (панель, поле, кнопка, свечение) — без них остальное не собрать.
  2. C2: фон и затемнение; проверить cover на 16:9/21:9.
  3. C3: новая композиция формы, сохранение текущей логики входа.
  4. C4: меню (язык, версия, адрес сервера).
  5. C5–C6: убрать 3D из Login, поправить смоук.
  6. C7–C8: локализация и ассеты.
  7. C9–C10: тесты, сборка, сверка с референсом.

E. Проверки

  • EditMode в копии проекта без Library (команда в docs/SESSION-STATE.md).
  • SceneSmoke.Run — SCENE_SMOKE success=True active=Login.
  • Standalone-сборка и запуск на 1920×1080; при возможности 21:9.
  • Проверка входа: успех, неверный пароль, нет сети (сообщения прежние).
  • git diff --check; сцены/префабы не менялись без отдельного пункта.

F. Риски и неизвестные вопросы

  • Арт-файла ещё нет в проекте — в чате только превью 1680×945. До появления файла C2 работает на градиенте; финальный вид не принять.
  • Логотип — часть арта; отдельный слой потребует прозрачного PNG и аккуратного позиционирования, иначе логотип «поедет» на другом AR.
  • Свечение в uGUI — bloom пост-обработки не действует на Screen Space Overlay: glow только запечённый в спрайт (несколько слоёв, без шейдеров).
  • Шрифт: LegacyRuntime даёт нейтральную геометрию; для совпадения с референсом нужен TTF с кириллицей (Inter/Exo 2) — ждём решение.
  • Отказ от 3D в Login ломает SceneSmoke и может задеть EditMode-тесты, которые искали окружение; правится в C6/C9.
  • Адрес сервера без UI — смена стенда только через меню (C4) или PlayerPrefs; в README нужно оставить инструкцию.
  • Сброс пароля — заглушка; если появится API, экран расширяется отдельной задачей.
  • Открытых вопросов, блокирующих C1–C7, нет; C2/C8 ждут арт.

G. Результат

  • Реализовано: C1–C9.
  • Проверки: компиляция без ошибок; EditMode 60/60 (в копии без Library, +Login_Hotkeys_Dispatch_Submit_And_Cancel); SceneSmoke.Run — success=True active=Login env=False backdrop=True login=True; standalone собран в копии; экран снят на 1920×1080 в прошлом проходе; меню открывается и закрывается.
  • Не завершено:
    • C10: ручная сверка с референсом, поведение Enter/Esc и меню на standalone — за пользователем.
  • Замечания: панель и поля полупрозрачные (0.94 / 0.97) — сквозь них видно арт; если нужно плотнее, поднять альфу. Иконки собраны из примитивов (Compose), шрифт — LegacyRuntime.
  • Skill обновлён: не требуется.
  • Git: не коммитилось.

TASK-2026-022 — Lobby по референсу: дашборд, заглушки и сцена Community

  • Статус: [!]
  • Создано: 2026-10-31
  • Анализ: DeepSeek выполнен (референс Lobby от 2026-10-31, решения ниже)
  • Реализация: DeepSeek: C1–C11 выполнены
  • Проверка: EditMode 62/62; SCENE_SMOKE, PLAYMAT_SMOKE, standalone; ручная приёмка — за пользователем
  • Затронутые системы: LobbySceneController, новый LobbyView (вместо MetaView), новый LobbyBackdrop, новый BattleEntryOverlay, меню, новая сцена Community + CommunitySceneController/CommunityView, SceneFlow, SceneBuilder, SceneSmoke, Localization, EditMode-тесты. Сервер, правила и протокол не меняются.

A. Цель и требования

Привести Lobby к композиции референса, но оставить только реально поддержанные функции; отсутствующие в сервере блоки — честные заглушки «скоро» без фейковых цифр.

Состав экрана (1920×1080):

  1. Верхняя панель (≈96 px): аватар (инициалы, ассетов нет), ник, RankedMmr, бейдж сезона-заглушки; справа — «Рейтинг», «Сообщество», «Меню».
  2. Левая колонка (≈560 px): три крупные плашки с иконкой, заголовком и подписью — BATTLE, DECK BUILDER, SHOP.
  3. Блок миссий под плашками: заголовок «Ежедневные / еженедельные задания» и 2–4 строки-заглушки с замком и «скоро». Прогресс и таймер обновления не рисуются: серверной модели нет.
  4. Балансы внизу слева: Gold, Dust, Tokens; клик открывает магазин.
  5. Центр: большая панель Chat — заглушка «скоро» с полем ввода в состоянии disabled.
  6. Справа: панель Friends Online — заглушка: счётчик без данных не показывать, 3–5 строк-заглушек, «скоро».
  7. Фон: процедурный тёмный неоновый градиент с сеткой и виньеткой в стиле Login. 3D-окружение в Lobby не строится.

Поведение:

  • BATTLE открывает модальный оверлей: список колод, режим Рейтинговый / С ботом, кнопка «В бой». Без колод или без валидной колоды кнопка disabled и есть переход «Собрать колоду» в DeckBuilder. Рейтинговый режим уходит в существующую очередь classic-ranked (Flow.OpenLobbyWithQueueAsync), «С ботом» — StartPracticeAsync и LoadBattleAsync. Серверные контракты не меняются.
  • Rating — оверлей-заглушка «Рейтинг — скоро». Лидерборд (top-100 по RankedMmr) — отдельная задача с серверным endpoint.
  • Community — отдельная сцена-каркас с вкладками «Гайды / Колоды / Тикеты», внутри каждой — заглушка. Реальные гайды, публичные колоды и отправка тикетов — отдельные задачи с сервером.
  • Меню — гамбургер: язык RU/EN, версия, адрес сервера, «Выйти», «Закрыть»; Esc и клик вне панели закрывают.
  • Онбординг в Lobby не показывается: ни статуса, ни кнопки. Выбор стартовой триал-деки остаётся отдельным экраном до Lobby; когда появится обучение, он будет заменён им.

Ограничения:

  • без новых пакетов и без Addressables;
  • сервер, правила, протокол и контент не меняются;
  • QueueView, StoreView, HistoryView, DeckBuilder и Battle сохраняют текущее поведение;
  • Login не должен измениться визуально при выносе общего меню;
  • геймплей и визуальную приёмку проверяет пользователь.

Критерии готовности:

  • композиция и стиль совпадают с референсом на 1920×1080, на 16:10 и 21:9 фон заполняет экран, панели не разъезжаются;
  • все несерверные блоки визуально помечены «скоро» и не кликабельны;
  • BATTLE доводит до очереди или практики без захода в редактор колоды;
  • Community открывается как отдельная сцена, вкладки переключаются;
  • в Lobby нет упоминаний онбординга;
  • существующие EditMode-тесты и смоуки обновлены и проходят.

B. Текущее состояние

  • LobbySceneController строит 3D-окружение (SceneEnvironment.Build), создаёт MetaView, OnboardingView, QueueView, StoreView, HistoryView; при !OnboardingCompleted показывает онбординг вместо лобби; «В бой» ведёт в DeckBuilder.
  • MetaView (Runtime/UI/MetaView.cs): топбар VANGUARD + ник + язык + «Выйти», 5 статистик (Gold/Dust/Tokens/Ranked/Casual), 4 кнопки (Коллекция/Магазин/История/В бой), запись, статус, кнопка выбора триал-деки. Полностью заменяется.
  • QueueView — только classic-ranked; практика запускается из DeckBuilderView («С ботом»).
  • Сервер: UserEntity хранит UserName, RankedMmr, Matches, Wins, но endpoint лидерборда, миссий, чата, друзей, сезонов и тикетов отсутствует.
  • Аватаров в проекте нет; ассетов фона Lobby нет.
  • SceneSmoke требует Vanguard 3D Environment для Lobby; тесты ClientSmokeTests создают MetaView напрямую и проверяют MetaScreen, OnboardingScreen.
  • Общие визуальные атомы — NeonUi из TASK-2026-021.

Что сохранить:

  • AuthService, MetaService, PlayerState, обработку 401 → Login;
  • существующие экраны магазина, истории, очереди, реплеев;
  • SceneFlow как единственный переход между сценами;
  • UiKit/NeonUi, CanvasScaler 1920×1080;
  • текущий визуальный язык Login.

C. Чек-лист изменений

  • C1. CREATE Runtime/UI/LobbyBackdrop.cs — процедурный фон.

    • Вертикальный градиент, сетка, центральное свечение и виньетка; спрайт кешируется статически, создаётся один раз.
    • Cover-масштабирование на 16:9/16:10/21:9 без полос; при появлении арта заменяется одним изображением без правки раскладки.
    • Объект называется LobbyBackdrop для смоука.
    • Реализация: выполнена. Проверка: EditMode 62/62, SCENE_SMOKE.
    • Зависит: нет.
  • C2. CREATE Runtime/UI/LobbyView.cs — новый дашборд вместо MetaView.

    • Топбар: аватар-инициалы, ник, MMR, сезон-заглушка, «Рейтинг», «Сообщество», «Меню».
    • Левая колонка: BATTLE / DECK BUILDER / SHOP с иконками и подписями; блок миссий-заглушка; балансы Gold/Dust/Tokens с переходом в магазин.
    • Центр: Chat-заглушка; справа: Friends Online-заглушка.
    • Корневой объект LobbyScreen; тексты через Loc.
    • Реализация: выполнена. Проверка: Lobby_Shows_Actions_Stubs_And_Balances_Without_Onboarding.
    • Зависит: C1, C9.
  • C3. MODIFY Runtime/App/LobbySceneController.cs — сборка Lobby без 3D и без MetaView.

    • Убран SceneEnvironment.Build(SceneTheme.Lobby); создаются LobbyBackdrop, LobbyView, BattleEntryOverlay, рейтинг-заглушка и HotkeysDriver (Esc).
    • Убраны статус и кнопка онбординга из лобби; при !OnboardingCompleted по-прежнему открывается отдельный экран выбора триал-деки.
    • Подключены BATTLE-оверлей, рейтинг-заглушка, Community → SceneFlow.
    • Реализация: выполнена. Проверка: EditMode 62/62, SCENE_SMOKE.
    • Зависит: C2, C4, C5, C6.
  • C4. CREATE Runtime/UI/BattleEntryOverlay.cs — выбор колоды и режима.

    • Загрузка колод MetaService.GetDecksAsync, выбор карточкой, режимы «Рейтинговый» / «С ботом», кнопка «В бой», «Собрать колоду».
    • Валидация через ValidateDeckAsync перед запуском; ошибки — в панели.
    • Ranked закрывает оверлей и открывает существующую очередь (LobbySceneController.OnRanked → QueueView.Show); практика — StartPracticeAsync + Flow.LoadBattleAsync.
    • Закрытие: крестик и Esc (фон блокируется).
    • Реализация: выполнена. Проверка: компиляция, EditMode 62/62; сетевой сценарий — ручная проверка.
    • Зависит: C2.
  • C5. CREATE сцена Community.

    • Scenes/Community.unity, CommunitySceneController, Runtime/UI/CommunityView с вкладками «Гайды / Колоды / Тикеты» и заглушками; кнопка «Назад» в Lobby.
    • SceneFlow: константа Community и LoadCommunityAsync; SceneBuilder: создание сцены и добавление в Build Settings после Lobby.
    • Серверных вызовов нет.
    • Реализация: выполнена. Проверка: Community_Tabs_Switch_Content, сцена собрана (6 сцен в Build Settings).
    • Зависит: C2.
  • C6. MODIFY меню — общий гамбургер для Login и Lobby.

    • LoginMenu вынесен в общий Runtime/UI/HamburgerMenu.cs (язык, версия, адрес сервера, закрыть; для Lobby дополнительно «История матчей» и «Выйти»).
    • Login использует его без визуальных изменений; в Lobby «Выйти» переехал из топбара в меню.
    • Esc закрывает меню через HotkeysDriver; клик вне панели работает.
    • Реализация: выполнена. Проверка: EditMode 62/62, Login не изменён.
    • Зависит: C2.
  • C7. DELETE/MODIFY Runtime/UI/MetaView.cs — удалить после переноса функций в LobbyView.

    • Обновление профиля и обработка 401 → Logout перенесены в LobbyView; MetaView удалён вместе с .meta.
    • Реализация: выполнена. Проверка: EditMode 62/62, ссылок на MetaView не осталось.
    • Зависит: C2, C3.
  • C8. MODIFY Editor/SceneSmoke.cs + Editor/SceneBuilder.cs.

    • Lobby проверяется по LobbyBackdrop и LobbyScreen; 3D-окружение для неё больше не требуется.
    • SceneBuilder создаёт Community и добавляет её в Build Settings после Lobby.
    • Реализация: выполнена. Проверка: SCENE_SMOKE success=True active=Login backdrop=True; сцена Community собрана.
    • Зависит: C2, C5.
  • C9. MODIFY Runtime/UI/Localization.cs — новые ключи.

    • Рейтинг, Сообщество, подписи плашек, задания, чат, друзья, «скоро», режимы, ошибки проверки колоды, вкладки Community.
    • Реализация: выполнена. Проверка: дубликатов ключей нет, EditMode 62/62.
    • Зависит: C2, C4, C5, C6.
  • C10. TEST EditMode.

    • LobbyView: три плашки, балансы, «скоро», отсутствие онбординга.
    • CommunityView: переключение вкладок.
    • Существующий Client_Views_Build переведён с MetaView на LobbyView; проверка LobbyScreen.
    • Реализация: выполнена. Проверка: EditMode 62/62 в копии без Library.
    • Зависит: C1–C9.
  • C11. VERIFY EditMode, smoke, standalone.

    • Копия проекта без Library; SceneSmoke.Run, RunPlaymat; standalone-сборка и запуск.
    • Реализация: выполнена. Проверка: EditMode 62/62, SCENE_SMOKE success=True, PLAYMAT_SMOKE success=True, WINDOWS_BUILD result=Succeeded errors=0; визуальная сверка с референсом — за пользователем.
    • Зависит: C10.

D. Последовательность выполнения

  1. C1 → C2: фон и раскладка дашборда; проверить 16:9/16:10/21:9.
  2. C6: общее меню; убедиться, что Login не изменился.
  3. C3 → C4: контроллер Lobby, убрать 3D/онбординг, BATTLE-оверлей.
  4. C5: сцена Community и переход.
  5. C7 → C9: удаление MetaView, смоук, локализация.
  6. C10 → C11: тесты, smoke, standalone, ручная приёмка.

E. Проверки

  • Клиентские EditMode в копии без Library.
  • SceneSmoke.Run (Login/Lobby), проверка Community.
  • SceneSmoke.RunPlaymat — поле не должно пострадать.
  • Standalone-сборка и запуск на 1920×1080; при возможности 16:10, 21:9.
  • git diff --check; отсутствие изменений в server/, content/, Vanguard.Rules/Shared.
  • Ручная проверка: BATTLE → очередь/бот, магазин, история, коллекция, Community, меню, Esc, отсутствие онбординга в Lobby.

F. Решения и риски

Зафиксировано пользователем (2026-10-31):

  1. Блоки без сервера (миссии, чат, друзья, сезон, рейтинг) — заглушки «скоро».
  2. Центральный 3D-блок убрать, область отдать большой панели чата.
  3. Rating — в будущем топ игроков по рейтингу; сейчас заглушка.
  4. Community — отдельная сцена: гайды, колоды, тикеты; первая версия — каркас с заглушками.
  5. BATTLE — оверлей выбора колоды и режима.
  6. Онбординг в Lobby не показывать; выбор триал-деки остаётся отдельным экраном до появления обучения.
  7. Фон Lobby — процедурный, арт позже.

Риски:

  • Пустая область чата: большая заглушка может выглядеть как дыра; нужен аккуратный placeholder (иконка, пояснение, «скоро»), иначе экран проигрывает референсу.
  • Процедурный фон без арта будет плоским; заменить на key art, когда появится, без правки раскладки.
  • Аватаров нет — инициалы; при появлении загрузки аватаров заменить.
  • Удаление MetaView затрагивает ClientSmokeTests и SceneSmoke; менять их в том же блоке.
  • Новая сцена требует SceneBuilder и Build Settings; иначе LoadSceneAsync вернёт ошибку.
  • Онбординг новых аккаунтов: выбор триал-деки нельзя потерять, иначе новый игрок останется без колоды.
  • Общее меню не должно менять поведение Login (Tab/Enter/Esc).
  • BATTLE-оверлей не должен блокировать бой из-за пустой коллекции: причина disabled должна быть видимой.

Открытых вопросов, блокирующих старт, нет.

G. Результат

  • Реализовано: C1–C11. Lobby — 2D-дашборд: процедурный фон, топбар (инициалы, ник, MMR, сезон-заглушка), три плашки, миссии-заглушка, балансы, чат- и друзья-заглушки, меню с историей и выходом. BATTLE — оверлей выбора колоды/режима с серверной валидацией. Rating — заглушка. Community — отдельная сцена с вкладками. MetaView удалён.
  • Проверки: EditMode 62/62 в копии без Library; SCENE_SMOKE success=True active=Login env=False backdrop=True; PLAYMAT_SMOKE success=True board=True cards=25 hand=5/5; WINDOWS_BUILD result=Succeeded errors=0; standalone запущен.
  • Не завершено: визуальная сверка с референсом, BATTLE → очередь/бот, магазин/история/коллекция, Community и меню — ручная проверка пользователя.
  • Замечания: сцены Login/Lobby/DeckBuilder/Battle после SceneBuilder.BuildAll содержали только churn fileID — откачены; в репозиторий добавлена только новая Community.unity. Арт фона Lobby отсутствует, фон процедурный.
  • Skill обновлён: не требуется; правила и архитектура не менялись.
  • Git: не коммитилось.

TASK-2026-023 — DeckBuilder: один экран, мультифильтры и список колоды

  • Статус: [!]
  • Создано: 2026-10-31
  • Анализ: DeepSeek выполнен
  • Реализация: DeepSeek: C1–C11 выполнены
  • Проверка: EditMode 67/67; SCENE_SMOKE, PLAYMAT_SMOKE; ручная приёмка — за пользователем
  • Затронутые системы: Runtime/UI/DeckBuilderView.cs, Runtime/UI/CardImageService.cs, Localization.cs, EditMode-тесты. Сервер, правила, протокол и YAML не меняются.

A. Цель и требования

Переделать декбилдер по решениям пользователя от 2026-10-31:

  1. Один экран вместо двух: слева коллекция, справа панель, которая переключается между «Мои колоды» и «Колода» при входе в редактор.
  2. Мультивыбор фильтров по клану, грейду и триггеру в модальном оверлее; выбранные значения подсвечены, есть «Применить» и «Сбросить».
  3. Корректный список колод справа: имя, 50/50, T 16/16, формат, распределение по грейдам, бейдж валидности, подсветка открытой колоды.
  4. В редакторе справа вместо списка колод — карты этой колоды: превью только иллюстрации карты (без рамки и текста, без искажений), имя, количество; сортировка грейд → имя; группировка по nameId.
  5. Live-валидация 50/16/4; «Сохранить» и «В бой» активны только для валидной сохранённой колоды.
  6. Поведение сохранения, создания, удаления, крафта и разбора не меняется.
  7. Геймплей и визуальную приёмку проверяет пользователь.

Критерии готовности:

  • переключение «список колод ⇄ колода» не пересоздаёт сцену и не теряет выбранные фильтры;
  • мультивыбор клана/грейда/триггера реально фильтрует коллекцию и отображается в сводке;
  • в списке колоды одна строка на карту (nameId), превью — иллюстрация без растяжения, порядок грейд → имя;
  • список колод показывает валидность и счётчики, открытая колода подсвечена;
  • существующие EditMode-тесты и смоуки проходят.

B. Текущее состояние

  • DeckBuilderView (1435 строк) строит два полноэкранных режима (DeckList и DeckEditor) с отдельными топбарами и двумя гридами коллекции; переключение — ShowEditor/BackToList.
  • Правая колонка фиксирована на 300 px: в списке — ряды 76 px (имя, N/50 · classic), в редакторе — ряды 64 px (превью 36×50, G, имя, ×N · триггер).
  • Колода хранится как список printingId; в редакторе группируется по printingId и сортируется по нему же. Одна карта в разных принтах даёт две строки, хотя лимит 4 копии считается по nameId.
  • Фильтры — модальный оверлей: одиночные значения (клан, грейд, триггер), выбранное состояние не подсвечивается, сводка показывает по одному значению.
  • CardImageService.Apply ставит preserveAspect = false: арты 350×511 растягиваются в область 174×112 (сетка) и 36×50 (список).
  • Валидация выполняется только при сохранении; кнопки активны всегда.
  • Тестов на фильтрацию/сортировку нет; ClientSmokeTests только создаёт DeckBuilderView.

Что сохранить:

  • загрузку коллекции/каталога/колод и контракты MetaService;
  • лимиты 50/16/4 и правила добавления/удаления;
  • оверлей карты (ПКМ), крафт/разбор;
  • сцена DeckBuilder и переходы SceneFlow;
  • 3D-окружение сцены не трогать в этой задаче.

C. Чек-лист изменений

  • C1. MODIFY Runtime/UI/CardImageService.cs — превью иллюстрации.

    • ApplyIllustration(Image, imageKey, fallback): спрайт из верхней части карты — Rect(0, h * 0.41, w, h * 0.59) (350×511), с отдельным кешем.
    • preserveAspect = true; при отсутствии арта — процедурный fallback.
    • Полный арт (Apply) не изменён.
    • Реализация: выполнена. Проверка: EditMode 67/67, ручная — позже.
    • Зависит: нет.
  • C2. MODIFY Runtime/UI/DeckBuilderView.cs — один экран.

    • Убраны _listView/_editorView; один корень DeckBuilderScreen: топбар (назад в Lobby, заголовок, сводка фильтров, фильтры, сброс) и тело (коллекция + правая панель 380 px).
    • Правая панель: DeckListPanel и DeckPanel; переключение без смены сцены; статус — под коллекцией.
    • Реализация: выполнена. Проверка: EditMode 67/67, панели в Client_Views_Build.
    • Зависит: C1, C3, C4.
  • C3. MODIFY список колод.

    • Строка: имя, N/50, T N/16, Classic, G0…G3, бейдж «Готова / Не готова»; валидность считается локально.
    • Открытая колода подсвечена; «Создать» — в шапке панели.
    • Реализация: выполнена. Проверка: EditMode 67/67, ручная — позже.
    • Зависит: C2.
  • C4. MODIFY список карт колоды.

    • Группировка по nameId; счётчик — сумма принтов; превью — иллюстрация последнего принта группы.
    • Строка 84 px: превью 64×54, имя, G, ×N, триггер и клан; сортировка грейд → имя.
    • Клик — убрать копию; ПКМ — карточка.
    • Реализация: выполнена. Проверка: GroupDeck_* тесты, EditMode 67/67.
    • Зависит: C1, C2.
  • C5. MODIFY фильтры — мультивыбор.

    • DeckFilter с наборами клана/грейда/триггера; пустой набор = «все».
    • Оверлей: чипы с подсветкой, «Сбросить» (черновик), «Отмена», «Применить»; сводка Клан: … · Ранг: … · Триггер: ….
    • Применение пересобирает коллекцию; счётчики обновляются точечно.
    • Реализация: выполнена. Проверка: тесты фильтра, EditMode 67/67.
    • Зависит: C2, C6.
  • C6. CREATE Runtime/UI/DeckListFilter.cs.

    • DeckFilter, DeckCardGroup, DeckValidation, DeckListModel: Matches, GroupDeck, Validate, IsTrigger.
    • Реализация: выполнена. Проверка: 4 теста DeckBuilderTests.
    • Зависит: C4, C5.
  • C7. MODIFY ячейки коллекции.

    • Иллюстрация ApplyIllustration, область арта 174×144 без растяжения.
    • Счётчик по nameId; при лимите 4 или 16 триггеров цифры красные; добавление по-прежнему блокируется с сообщением.
    • Реализация: выполнена. Проверка: EditMode 67/67, ручная — позже.
    • Зависит: C1, C5.
  • C8. MODIFY live-валидация.

    • UpdateStats: счётчики, распределение, «не сохранено», цвет; «Сохранить» — при валидной колоде, «В бой»/«С ботом» — только для сохранённой валидной колоды; «Удалить» — для сохранённой.
    • Ошибки — в статусе, без модальных окон.
    • Реализация: выполнена. Проверка: EditMode 67/67, ручная — позже.
    • Зависит: C3, C4.
  • C9. MODIFY Runtime/UI/Localization.cs — новые ключи.

    • «Создать», «К списку колод», «Готова/Не готова», варианты триггеров, «Сбросить», «Применить», «Удалить колоду», «не сохранено», ошибки.
    • Реализация: выполнена. Проверка: дубликатов ключей нет.
    • Зависит: C2–C5.
  • C10. TEST EditMode.

    • DeckBuilderTests: пустой фильтр, мультивыбор, группировка/сортировка, неизвестный принт, валидация 50/16/4.
    • Client_Views_Build: проверены DeckListPanel, DeckPanel, FilterOverlay.
    • Реализация: выполнена. Проверка: EditMode 67/67 в копии без Library.
    • Зависит: C1–C9.
  • C11. VERIFY EditMode, smoke, standalone.

    • Копия без Library; EditMode; SCENE_SMOKE; PLAYMAT_SMOKE.
    • Реализация: выполнена. Проверка: EditMode 67/67, SCENE_SMOKE success=True, PLAYMAT_SMOKE success=True; ручная проверка — за пользователем.
    • Зависит: C10.

D. Последовательность выполнения

  1. C1 + C6: обрезка иллюстрации и чистые функции фильтра/сортировки/групп.
  2. C2: один экран и переключение правой панели.
  3. C3–C4: список колод и список карт колоды.
  4. C5 + C7: мультифильтры и ячейки коллекции.
  5. C8–C9: валидация и локализация.
  6. C10–C11: тесты, smoke, standalone, ручная приёмка.

E. Проверки

  • EditMode в копии без Library.
  • SceneSmoke.Run, RunPlaymat, standalone-сборка.
  • git diff --check; нет изменений в server/, content/, Vanguard.Rules.
  • Ручная проверка — пользователь.

F. Решения и риски

Зафиксировано пользователем (2026-10-31):

  1. Один экран с двумя панелями.
  2. Превью — только иллюстрация карты (без рамки и текста).
  3. Фильтры — модальный оверлей с мультивыбором.
  4. Триггерный фильтр — тоже мультивыбор.
  5. Сортировка карт колоды — грейд → имя; группировка по названию.

Риски:

  • Границы иллюстрации: доля 0.59 снята с BT01/TD01 (350×511); если встретится другой макет карты, полоса флейвора может попасть в кадр. Константы вынести в одно место и проверить на нескольких сетах.
  • Группировка по nameId: при смешанных принтах в строке показывается иллюстрация первого; удаление уменьшает суммарный счётчик.
  • Один экран: легко потерять состояние (открытая колода, черновик фильтров). Хранить явно и сбрасывать только по действию пользователя.
  • Пересборка грида: при смене фильтра она неизбежна; не пересобирать на каждый клик добавления — обновлять счётчики точечно.
  • Валидность: локальная проверка должна совпадать с серверной (DeckValidator), иначе «Сохранить» включится, а сервер откажет.
  • 3D-фон DeckBuilder остаётся; если позже переведём на 2D, это отдельная задача.

Открытых вопросов, блокирующих старт, нет.

G. Результат

  • Реализовано: C1–C11. DeckBuilder — один экран: коллекция с мультифильтрами (клан/грейд/триггер), справа панель «Мои колоды» ⇄ «Колода». Список колод показывает 50/50, триггеры, грейды и валидность; список колоды группируется по nameId, сортируется грейд → имя, превью — иллюстрация карты без искажений. Live-валидация управляет «Сохранить»/«В бой»/«С ботом».
  • Проверки: EditMode 67/67 в копии без Library (+5 тестов DeckBuilderTests); SCENE_SMOKE success=True; PLAYMAT_SMOKE success=True.
  • Не завершено: ручная проверка фильтров, переключения панелей, добавления/удаления, сохранения, «В бой»/«С ботом», ПКМ-карточки.
  • Замечания: граница иллюстрации 0.59 подобрана по BT01/TD01 (350×511); при другом макете карты константу нужно проверить. Правка имени колоды помечает колоду как несохранённую.
  • Продолжение: визуальная компоновка переработана по референсу в TASK-2026-024 (топбар Lobby, крупные карты, фильтры в оверлее). Логика фильтров, группировки и валидации переиспользуется.
  • Skill обновлён: не требуется.
  • Git: ecb7314.

TASK-2026-024 — DeckBuilder по референсу: топбар Lobby, крупные карты, фильтры

  • Статус: [ ]
  • Создано: 2026-11-01
  • Анализ: DeepSeek выполнен (референс DeckBuilder от 2026-11-01)
  • Реализация: не начата
  • Проверка: не выполнена
  • Затронутые системы: LobbyView, новый PlayerTopBar, HamburgerMenu, DeckBuilderView, DeckBuilderSceneController, CardImageService, DeckListFilter, Localization, EditMode-тесты. Сервер, правила и контент не меняются.

A. Цель и требования

Привести DeckBuilder к композиции референса, сохранив логику TASK-2026-023 (фильтры, группировка, live-валидация):

  1. Топбар как в Lobby: аватар, ник, MMR, сезон, Rating и Community неактивны, Menu — язык, версия, адрес сервера, история, «В лобби», «Выйти».
  2. Без вкладок Collection/Deck Builder — это элемент референса, не функция.
  3. Коллекция — крупные карты со скроллом (6 в ряд): номер грейда, иллюстрация, плашка имени поверх, строка количества, камень редкости. Пагинации нет.
  4. Режим просмотра: карты коллекции только смотрят, крафтят и разбирают; ЛКМ по карте ничего не делает, ПКМ открывает карточку. В этом режиме под картой — «есть ×N».
  5. Режим колоды: при выборе колоды справа появляется её состав, добавление/удаление включается; ЛКМ по карте коллекции добавляет копию, ПКМ — карточка. Под картой — «в колоде ×N · есть ×M».
  6. Список колод: кнопка + Новый набор, строки с эмблемой клана, именем, N/50 и T N/16, кебаб-меню (открыть/удалить), подсветка выбранной.
  7. Фильтры — в модальном оверлее: мультивыбор клана, грейда, триггера и редкости (C/R/RR/RRR/SP), плюс поиск по названию; «Сбросить», «Отмена», «Применить», сводка в шапке.
  8. Визуальный язык — неоновые панели/свечение/фаски из NeonUi, как в Login и Lobby.

Ограничения:

  • сервер, правила, протокол и контент не меняются;
  • 3D-окружение сцены DeckBuilder не трогаем;
  • сохранение/создание/удаление/крафт/разбор сохраняют поведение;
  • Lobby не должен визуально измениться после выноса топбара;
  • ручная приёмка — пользователь.

Критерии готовности:

  • DeckBuilder открывается с топбаром Lobby и меню, «В лобби» работает;
  • в режиме просмотра клик по карте не добавляет её в колоду;
  • после выбора колоды справа состав, ЛКМ добавляет, клик по строке состава убирает;
  • крупные карты не растянуты, имя и количество читаются, скролл плавный;
  • мультифильтры + редкость + поиск реально фильтруют и отражаются в сводке;
  • EditMode и смоуки проходят.

B. Текущее состояние

  • DeckBuilderView (ecb7314) — один экран со своим топбаром (назад, заголовок, сводка, фильтры, сброс), коллекция 174×238 с ApplyIllustration, правая панель 380 px со списком колод и составом.
  • Клик по карте коллекции всегда добавляет копию (в обоих режимах).
  • Фильтры — модальный оверлей без редкости и поиска.
  • Топбар Lobby реализован внутри LobbyView (TopBar, AddTopAction), меню — HamburgerMenu.
  • LobbyView показывает аватар-инициалы, ник, MMR, сезон, Rating, Community и гамбургер.
  • DeckBuilderSceneController резолвит только MetaService/AuthService.

Что сохранить:

  • DeckListModel/DeckFilter/DeckValidation и тесты;
  • CardImageService.ApplyIllustration;
  • правую панель со списком колод/составом и live-валидацию;
  • крафт/разбор и ПКМ-карточку;
  • Lobby: расположение и поведение элементов после выноса топбара.

C. Чек-лист изменений

  • C1. CREATE Runtime/UI/PlayerTopBar.cs; MODIFY LobbyView.cs.

    • Вынести из LobbyView топбар: аватар, ник, MMR, сезон, две кнопки, гамбургер; подписка на PlayerState; меню HamburgerMenu.
    • Конструктор принимает onRating, onCommunity, onHistory, onLobby, onLogout; null для действия = кнопка disabled.
    • LobbyView использует компонент без визуальных изменений.
    • HamburgerMenu: пункт «В лобби» при onLobby != null.
    • Реализация: не выполнена. Проверка: не выполнена.
    • Зависит: нет.
  • C2. MODIFY Runtime/App/DeckBuilderSceneController.cs.

    • Резолвить PlayerState/AppConfig; создать PlayerTopBar с onRating: null, onCommunity: null, «В лобби» → Lobby.
    • Реализация: не выполнена. Проверка: не выполнена.
    • Зависит: C1.
  • C3. MODIFY DeckBuilderView — каркас.

    • Убрать собственный топбар; тело начинается под PlayerTopBar.
    • Сохранить панели DeckListPanel/DeckPanel, статус под коллекцией.
    • Реализация: не выполнена. Проверка: не выполнена.
    • Зависит: C1, C2.
  • C4. MODIFY сетка коллекции.

    • GridLayoutGroup 6 колонок, ячейка ≈216×286, вертикальный скролл без пагинации.
    • Ячейка: иллюстрация ApplyIllustration (216×182), номер грейда в углу, плашка имени поверх низа иллюстрации, строка количества, камень редкости.
    • ЛКМ в просмотре ничего не делает; ПКМ — карточка; в режиме колоды ЛКМ добавляет, ПКМ — карточка.
    • Количество: «есть ×N» / «в колоде ×N · есть ×M».
    • Реализация: не выполнена. Проверка: не выполнена.
    • Зависит: C3, C5.
  • C5. MODIFY режимы просмотра и колоды.

    • _editing == null — просмотр: панель колод, добавление выключено; нажатие на колоду открывает состав и включает добавление.
    • «К списку колод» возвращает просмотр (несохранённые изменения отбрасываются; предупреждение — отдельный пункт при необходимости).
    • Реализация: не выполнена. Проверка: не выполнена.
    • Зависит: C3.
  • C6. MODIFY список колод по референсу.

    • Кнопка + Новый набор (полная ширина).
    • Строка: эмблема клана (процедурная, цвет клана), имя, N/50 · T N/16, бейдж валидности точкой/цветом; кебаб-меню «Открыть / Удалить».
    • Подсветка открытой колоды.
    • Реализация: не выполнена. Проверка: не выполнена.
    • Зависит: C3.
  • C7. MODIFY фильтры.

    • DeckFilter: добавить Rarities (C/R/RR/RRR/SP) и Search.
    • Оверлей: поиск, чипы редкости, клан/грейд/триггер; «Сбросить», «Отмена», «Применить»; сводка включает редкость и поиск.
    • DeckListModel/DeckFilter остаются чистыми и тестируемыми.
    • Реализация: не выполнена. Проверка: не выполнена.
    • Зависит: C3.
  • C8. MODIFY Localization.cs — «В лобби», «Новый набор», «Поиск по названию», «Редкость», «есть», «в колоде», «Открыть», «Ещё», названия редкостей.

    • Реализация: не выполнена. Проверка: не выполнена.
    • Зависит: C4–C7.
  • C9. TEST EditMode.

    • DeckFilter: редкость и поиск (регистр, локализованное имя).
    • PlayerTopBar: создаётся, ник/MMR обновляются, disabled-кнопки при null-действиях.
    • UI: DeckBuilderScreen, панели, отсутствие добавления в просмотре (seam), переключение в режим колоды.
    • Реализация: не выполнена. Проверка: не выполнена.
    • Зависит: C1–C8.
  • C10. VERIFY EditMode, smoke, standalone.

    • Копия без Library; EditMode; SCENE_SMOKE; PLAYMAT_SMOKE; standalone.
    • Ручная проверка: топбар и меню, крупные карты, скролл, фильтры и поиск, режимы, добавление/удаление, сохранение, «В бой»/«С ботом».
    • Реализация: не выполнена. Проверка: не выполнена.
    • Зависит: C9.

D. Последовательность выполнения

  1. C1: PlayerTopBar и меню; проверить, что Lobby не изменился.
  2. C2–C3: DeckBuilder с топбаром Lobby.
  3. C4–C5: крупная сетка и режимы просмотра/колоды.
  4. C6: список колод по референсу.
  5. C7–C8: фильтры, поиск, локализация.
  6. C9–C10: тесты, смоуки, standalone, ручная приёмка.

E. Проверки

  • EditMode в копии без Library.
  • SceneSmoke.Run, RunPlaymat, standalone.
  • Проверка Lobby после выноса топбара (визуально и смоуком).
  • git diff --check; нет изменений в server/, content/, Vanguard.Rules.
  • Ручная приёмка — пользователь.

F. Решения и риски

Зафиксировано пользователем (2026-11-01):

  1. Топбар как в Lobby; Rating/Community неактивны; «В лобби» — в меню.
  2. Вкладки Collection/Deck Builder не реализуем.
  3. Коллекция — крупные карты, вертикальный скролл, без пагинации.
  4. Просмотр: ЛКМ не добавляет, ПКМ открывает карточку.
  5. В режиме колоды справа состав, ЛКМ добавляет, клик по строке убирает.
  6. Количество: «есть ×N» / «в колоде ×N · есть ×M».
  7. Фильтры — всё в оверлее, включая редкость C/R/RR/RRR/SP и поиск.

Риски:

  • Вынос топбара затрагивает принятый Lobby: менять аккуратно, проверять смоук и раскладку.
  • ПКМ-only в просмотре может быть неочевидным: нужна подсказка «ПКМ — подробности» в статусе/шапке коллекции.
  • Несохранённые изменения при возврате к списку теряются; при необходимости добавить подтверждение отдельным пунктом.
  • Крупные ячейки уменьшают число видимых карт: скролл должен быть плавным, без пересоздания пула на каждый кадр.
  • Кебаб-меню требует отдельного маленького оверлея; не дублировать существующие меню.
  • Редкости SP в каталоге есть; если у принтов встретится другая строка, чип не совпадёт — показывать как есть.

Открытых вопросов, блокирующих старт, нет.

G. Результат

  • Реализовано: —
  • Проверки: —
  • Не завершено/заблокировано: —
  • Skill обновлён: —
  • Git: —