# 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: анализ и план готовы; реализация ожидает | не выполнена | Новые задачи добавляются в конец файла отдельными разделами. Не используй один раздел для нескольких независимых задач. --- ## Шаблон задачи (не является активной задачей) ### TASK-YYYY-NNN — краткое название - **Статус:** `[ ]` - **Создано:** YYYY-MM-DD - **Анализ:** DeepSeek - **Реализация:** DeepSeek - **Затронутые системы:** ... #### A. Цель и требования - Что просит пользователь. - Ожидаемое поведение. - Обязательные правила Skill. - Ограничения, которые нельзя нарушать. - Критерии готовности. #### B. Текущее состояние - Как работает сейчас. - Какие системы и файлы проверены. - Существующие механики, которые нужно сохранить. - Подтверждённые факты и открытые вопросы. #### C. Чек-лист изменений Каждый пункт: - `[ ]` `MODIFY` / `CREATE` / `DELETE` / `TEST` / `VERIFY` - путь к файлу (если известен); - класс/метод (если существует); - конкретная логика; - причина изменения; - ожидаемый результат; - зависимости от других пунктов; - **Реализация:** не выполнена; - **Проверка:** не выполнена. Пример формата: ```text - [ ] 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. Чек-лист изменений - [x] **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. - [x] **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. - [x] **C3. `MODIFY` `GameBoard3D` — карта источника из предыдущего `MatchView`.** - Файл: `client/Assets/Vanguard/Runtime/Board3D/View/GameBoard3D.cs`. - Сохраняются `_previousView` и `_sourceByInstance` до замены `_view`. - Построен `Dictionary` для `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. - [x] **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. - [x] **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. - [x] **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. - [x] **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 (можно параллельно). - [x] **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. - [x] **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 во время движения: нет двойного старта/сброса в источник. - Ручная проверка — пользователем; не заменять её автотестом. Команды (подтверждены проектом): ```powershell # клиентская копия без Library & 'C:\Program Files\Unity\Hub\Editor\6000.6.6f1\Editor\Unity.exe' ` -batchmode -nographics -projectPath ` -runTests -testPlatform EditMode -testResults \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. Чек-лист изменений - [x] **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. - [x] **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. - [x] **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. - [x] **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. - [x] **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. - Зависит от: нет. - [x] **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 (общий серверный блок можно делать параллельно). - [x] **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. - [x] **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` после удаления станут неиспользуемыми; пока оставляем поля, чтобы не менять контракт ассета. **Реализация:** - [x] Удалить `BattleFeedback`, `BattleFlash`, `SkipFeedback` и весь `FeedbackStep`-механизм. - [x] Убрать вызовы `ShowFeedback`/`EnqueuePause`/`ShowFeedbackFor`. - [x] Убрать `InputLock("feedback")` и связанные поля/сбросы. - [x] Удалить неиспользуемые `_turnBanner`/`_turnBannerText`. - [x] Оставить журнал событий, `Describe`, trigger-последовательность, `ActionPrompt` и `StatusToast`. **Проверка:** - [x] EditMode в копии без `Library` — 51/51. - [x] Standalone Windows собирается. - [ ] Вручную: в бою нет центральных баннеров и вспышек, события идут без зависаний; журнал и trigger-подсказки работают. --- ## 2026-10-27 — TASK-2026-005: порядок набора руки **Решение пользователя:** рука локального игрока должна набираться **слева направо**: первая карта слева, новые добавляются правее. Рука противника по правилу зеркальности набирается с его стороны поля, то есть на экране справа налево: новая карта слева. **Реализация:** - [x] `GetHandViewPose`: вернуть `x = center + offset * step`, `roll = -offset * roll`. - [x] `GetOpponentHandViewPose`: сделать зеркально — `x = center - offset * step`, `roll = +offset * roll`. - [x] Обновить тест `Hands_Fill_Mirrored_And_Newest_Is_On_Top` под новое направление и новые карты. **Проверка:** - [x] EditMode в копии без `Library` — 51/51. - [x] 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` интерполирует поворот от лица к рубашке. **Реализация:** - [x] `GameBoard3D.QueueHandReturnToDeck` + обработка `MulliganView` и `CardReturnedToDeckView` из `BattleView`. - [x] Выбор старых карт руки, удаление из `_cards`, дуга под колоду, переворот, возврат в пул после завершения. - [x] `CardMoveAnimator.Enqueue` получил `onCompleted`; последовательная очередь используется для возврата. - [x] Тесты: `Hand_Return_To_Deck_Starts_Right_To_Left_And_Targets_Deck_Bottom`, `CardMoveAnimator_Runs_Sequential_Requests_And_Completes_Each`. **Проверка:** - [x] EditMode в копии без `Library` — 53/53. - [x] Standalone Windows собирается. - [ ] Вручную: при муллигане/возврате карты уходят по дуге под колоду, переворачиваются рубашкой вверх, следующая стартует после предыдущей; у противника зеркально. --- ## 2026-10-27 — TASK-2026-007: общий добор карты **Решение пользователя:** добор после муллигана должен выглядеть так же, как добор в муллигане: карта появляется над колодой **полного размера**, рубашкой вверх, летит в руку по дуге и переворачивается лицом вверх. **Анализ:** - `AnimateHandDraw` уже стартует с `_config.cardScale` и `card.FaceDownRotation`. - Обычный добор шёл через `ResolveSourcePose`: для карты руки он брал `spec.Scale * cardScale` (размер руки) и плоскую ориентацию колоды, поэтому над колодой появлялась маленькая карта. - Нужно вынести общий старт добора: позиция над колодой, полный масштаб, рубашка в ориентации будущей карты руки. **Реализация:** - [x] `GameBoard3D.GetDrawSource` — общая позиция/масштаб старта добора. - [x] `ResolveSourcePose` для `BoardZone.Hand` использует полный масштаб и `spec.BaseRotation * FaceDownFlip`. - [x] `AnimateHandDraw` использует тот же `GetDrawSource`. - [x] Длительность нового добора — `introDrawDuration` (0.6 с), как в муллигане. - [x] Тест проверяет, что новая карта стартует над колодой в `cardScale`. **Проверка:** - [x] EditMode в копии без `Library` — 53/53. - [x] Standalone Windows собирается. - [ ] Вручную: добор после муллигана визуально совпадает с муллиганом; карта не маленькая. --- ## 2026-10-27 — TASK-2026-008: длительность возврата как у добора **Решение пользователя:** анимация возврата карты в колоду должна идти столько же, сколько добор из колоды. **Реализация:** - [x] Общая `GameBoard3D.DrawDuration` (`introDrawDuration`, 0.6 с). - [x] `StartHandReturn` использует `DrawDuration` вместо `moveDuration`. - [x] Обычный добор использует ту же `DrawDuration`. **Проверка:** - [x] EditMode в копии без `Library` — 53/53. - [x] Standalone Windows собирается. - [ ] Вручную: возврат в колоду по длительности совпадает с добором. --- ## 2026-10-27 — TASK-2026-009: последовательный возврат карт **Проблема:** карты возвращались одновременно. `Card3D.SetPose` ставил цель под колодой, а `_returningCards` тикали все сразу; очередь `CardMoveAnimator` стартовала дуги по одной, но карты уже плавно уезжали к цели параллельно. **Решение:** пока карта ждёт в очереди, удерживать её в исходной позе руки через `Card3D.HoldAtSource`. Старт следующей — только после завершения предыдущей (`sequential: true`). **Реализация:** - [x] `StartHandReturn` вызывает `HoldAtSource` до `CardMoveAnimator.Enqueue`. - [x] Тест проверяет, что обе возвращаемые карты остаются в руке до старта своей анимации. - [x] Порядок: самая правая карта первой; у противника зеркально. **Проверка:** - [x] EditMode в копии без `Library` — 53/53. - [x] Standalone Windows собирается. - [ ] Вручную: сначала уходит самая правая карта, затем следующая. --- ## 2026-10-28 — TASK-2026-010: рука перестраивается при выходе карты **Решение пользователя:** при любом выходе карты из руки остальные карты должны перестраиваться под новое количество одновременно с её перемещением. **Анализ:** - `UpdateHandView` уже считает позы по новому `HandCount` из свежего `Render`. - Перекомпоновка руки (`Hand → Hand`) не ставится в `CardMoveAnimator` (`NeedsMove` возвращает false), поэтому не ждёт анимацию уходящей карты. - Уходящая карта удаляется из `_handBindings`/`_cards`, а остальные карты сразу получают новые цели; hand reflow идёт своим smooth-слоем. **Реализация:** - [x] Проверка поведения для hand → field. - [x] Проверка поведения для возврата в колоду: остальные карты получают новые цели, пока возвращаемая карта ещё ждёт/летит. - [x] Тесты `Hand_Reflows_While_A_Card_Leaves_From_Hand` и расширенный `Hand_Return_To_Deck_Starts_Right_To_Left_And_Targets_Deck_Bottom`. **Проверка:** - [x] EditMode в копии без `Library` — 54/54. - [ ] Вручную: при выходе карты рука сдвигается одновременно с её движением. --- ## 2026-10-28 — TASK-2026-011: ступенчатый reflow руки при муллигане **Решение пользователя:** во время муллигана рука должна сдвигаться **после каждого** ухода карты, а не один раз под окончательное количество. **Проблема:** новый `Render` сразу содержал финальный `HandCount` (старые остатки + новые карты). Остальные карты получали финальные позы, а возвращаемые удерживались. Визуально рука перестраивалась один раз. **Решение:** - Ступенчатый список `_stagedReturnCards`: старые карты руки в исходном порядке, включая ожидающие возврата; новые карты в него не входят. - Пока идёт возврат, `UpdateHandView` строит позы только для этого списка. - После завершения каждой карты она убирается из списка, и оставшиеся карты (включая ожидающие) плавно сдвигаются под новое количество. - Ожидающие карты не удерживаются на месте: при старте своей анимации они получают цель под колодой через `OnStart` и стартуют из текущей позы (`fromCurrent`). - После последнего возврата staged-режим выключается; новые карты добираются в финальную руку. **Реализация:** - [x] `BuildStagedReturnHand`, `_stagedReturnCards`, `_stagedReturnPlayer`. - [x] `CardMoveAnimator` получил `onStart` и `fromCurrent`. - [x] `OnHandReturnCompleted` убирает карту из staged-списка и сразу пересчитывает руку. - [x] Тест проверяет: до первого ухода рука не сдвигается, после первого — сдвигается; ожидающая карта остаётся в руке до своей очереди. **Проверка:** - [x] EditMode в копии без `Library` — 54/54. - [x] Standalone Windows собирается. - [ ] Вручную: муллиган с двумя возвратами — рука сдвигается после каждого. --- ## 2026-10-28 — TASK-2026-012: добор по одной карте и ступенчатый reflow **Проблема:** при доборе все новые карты сразу появлялись над колодой рубашкой вверх, а рука перестраивалась один раз под финальное количество. **Решение:** - Новые карты своей руки скрываются на время очереди и показываются только в `CardMoveAnimator.onStart` — по одной. - `_stagedDrawCards` хранит уже видимые карты (старые остатки + завершённые доборы); новые карты добавляются только после завершения их дуги. - После каждого добора рука пересчитывается под текущее видимое количество, а не под финальное. - После последнего добора staged-режим выключается; финальная рука совпадает с текущей. **Реализация:** - [x] `BuildStagedDrawHand`, `_stagedDrawCards`, `_stagedDrawTargetCount`. - [x] `BeginHandDrawArc` показывает карту, ставит цель в следующий слот и запускает дугу из текущей позиции. - [x] `OnHandDrawCompleted` добавляет карту в staged-список и пересчитывает руку. - [x] Повторный snapshot без новых доборов не сбрасывает уже начатую staged раздачу — иначе остатки руки перестают сдвигаться. - [x] Тест `Hand_Draw_Stages_New_Cards_One_By_One`. **Проверка:** - [x] EditMode в копии без `Library` — 55/55. - [x] Standalone Windows собирается. - [ ] Вручную: при доборе над колодой видна одна карта; рука расширяется после каждой. --- ## 2026-10-28 — TASK-2026-013: единый механизм раскладки руки **Проблема:** остатки руки после муллигана переставали сдвигаться: отдельные `_stagedReturnCards` и `_stagedDrawCards` не всегда содержали одни и те же карты, а повторные snapshot/переходы между режимами ломали раскладку. **Решение:** один визуальный список `_visualHandCards` и один override `_handLayoutOverride`: - в список сразу входят все оставшиеся карты руки и ожидающие возврата; - новые добранные карты добавляются только после завершения их дуги; - после каждого возврата/добора список пересчитывается, поэтому остатки руки всегда участвуют в сдвиге; - пока override активен, `UpdateHandView` использует только визуальный список; после завершения всех возвратов/доборов возвращается обычный `_handBindings`; - повторный Render не пересоздаёт список и не сбрасывает прогресс. **Реализация:** - [x] `BuildVisualHand`, `_visualHandCards`, `_pendingDrawCards`, `_handLayoutOverride`. - [x] Удалены раздельные staged-списки возврата/добора. - [x] Тест `Mulligan_Remaining_Cards_Reflow_After_Each_Return`. **Проверка:** - [x] EditMode в копии без `Library` — 56/56. - [x] Standalone Windows собирается. - [ ] Вручную: после муллигана остатки руки сдвигаются после каждого возврата и каждого добора. --- ## 2026-10-28 — TASK-2026-014: подъём карты в руке при наведении **Решение пользователя:** карта под курсором плавно приподнимается на 10% своего размера; после ухода курсора плавно возвращается. **Реализация:** - [x] `handHoverLift = 0.1` теперь доля высоты карты, а не мировое смещение. - [x] `GameBoard3D.UpdateHandHover`: raycast по своей руке, только когда нет UI/лока/драга/анимаций; оппонент и поле игнорируются. - [x] `SetHoveredHandCard` пересчитывает руку; `ApplyHandHover` добавляет камера-вверх на `cardHeight * scale * cardScale * handHoverLift`. - [x] Плавность — существующий `Card3D.SetPose`/`Tick`; отдельного твина нет. - [x] Тест `Hand_Hover_Lifts_Card_By_Ten_Percent` (подъём и возврат цели). **Проверка:** - [x] EditMode в копии без `Library` — 57/57. - [x] Standalone Windows собирается. - [ ] Вручную: наведение поднимает карту, уход курсора возвращает. --- ## 2026-10-28 — TASK-2026-015: drag карты из руки, смена через VC **Решение пользователя:** карта из руки, отпущенная на VC, делает смену; пока кнопка мыши удерживается, карта остаётся приподнятой; отпускание в неположенном месте плавно возвращает её в руку; без дёрганья. **Реализация:** - [x] `Card3D.EndDragAtCurrent` фиксирует карту в точке drop-а, не возвращая её в руку и не сбрасывая подъём/масштаб. - [x] `GameBoard3D.FinishDrag`: для карты из руки больше не вызывает `ReturnToOrigin` до ответа сервера; держит карту в `_dropPendingCard`. - [x] `GameBoard3D.ReturnDroppedCard`: снимает hold и плавно возвращает в руку, если drop не стал интентом/отклонён. - [x] `BattleView`: возвращает карту при отсутствии интента, отказе сервера, ошибке отправки, guard-выборе и раннем выходе. - [x] Валидный Ride/Call: карта ждёт снапшот и летит в target уже из точки drop-а; в руку не возвращается. - [x] Тест `Dropped_Hand_Card_Holds_At_Drop_Until_Returned`. **Проверка:** - [x] EditMode в копии без `Library` — 58/58. - [x] 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-а. **Проверка:** - [x] EditMode — 58/58. - [x] Standalone Windows собирается. - [ ] Вручную: карта у курсора, hand-ориентация сохранена. --- ## 2026-10-28 — TASK-2026-017: drag не двигает карту в момент старта **Проблема:** в момент старта drag-а вызывался `UpdateDrag`, карта сразу переносилась к курсору/поднималась. Кроме того, после старта `UpdateDrag` больше не вызывался каждый кадр — карта не следовала за мышью. **Решение:** - `OnPointerHeld` только включает drag и запоминает точку захвата; позицию не меняет. - `HandlePointer` при `_dragging` каждый кадр вызывает `UpdateDrag`, поэтому карта следует за курсором. - `OnPointerReleased` больше не дублирует `UpdateDrag`; last target уже обновлён в этом кадре. **Проверка:** - [x] EditMode — 58/58. - [x] Standalone Windows собирается. - [ ] Вручную: при начале drag-а карта остаётся на месте, затем следует за курсором. --- ## 2026-10-28 — TASK-2026-018: выбор зоны без перемещения карты из руки **Решение пользователя:** карта из руки при зажатии ЛКМ не должна перемещаться. Курсор наводится на зону; карта остаётся в руке приподнятой; при отпускании над VC выполняется смена. **Реализация:** - [x] Для `source.IsHand` drag заменён на `_handTargeting`: карта не вызывает `BeginDrag`/`DragTo` и остаётся в hand-раскладке. - [x] `UpdateHandHover` удерживает hover-подъём нажатой карты, пока идёт targeting. - [x] `UpdateHandTargeting` считает только целевую зону по курсору и показывает drop indicator; карта не двигается. - [x] Цель сначала определяется raycast-ом по карте/кругу (VC-карта даёт ровно Vanguard), затем fallback на плоскость. - [x] `FinishHandTargeting` на release отправляет `CardDropped`; при валидном VC срабатывает RideIntent, при невалидном карта остаётся в руке. - [x] Field-карты сохраняют старый drag с движением. - [x] По серверному логу `rides=[TD02/007EN, BT01/015EN]` — только эти карты были доступны для смены. Валидные Ride-карты теперь подсвечиваются в руке; добавлен fallback direct RideIntent и диагностические логи `[HandTargeting]` / `[Drop]`. **Проверка:** - [x] EditMode — 58/58. - [x] 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`, регэксп по `
  • `, триггеры/редкости из `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. Чек-лист изменений - [x] **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. - [x] **C2. `CREATE` скачивание артов** — `{imageKey}.png` в `CardArt`. - Пропуск, если файл с любым расширением уже есть (иначе Unity увидит два ассета с одним именем); `--force-art` удаляет старый файл и его `.meta`. - Реализация: выполнена. Проверка: TD01, 16/16 файлов, ~350–430 КиБ. - [x] **C3. `MODIFY` дедупликация определений между сетами.** - При прогоне одного сета читаются `nameId` из остальных YAML папки, чтобы не дублировать карты, определённые в BT01. - Реализация: выполнена. Проверка: BT01 80 + TD01 7 + TD02 7 = 94. - [x] **C4. `MODIFY` сохранение ручных полей.** - `nameRu`, `textRu`, `snipeBackRow`, `boost` переносятся из существующего файла по `nameId` (на сайте их нет; иначе терялся бы Tejas). - Реализация: выполнена. Проверка: `snipeBackRow` у Tejas сохраняется. - [x] **C5. `VERIFY` идемпотентность.** - Прогон `--sets BT01,TD01,TD02` в копию `content/sets`: `Compare-Object` по каждому файлу — **0 строк различий**, размеры совпали (37116 / 3771 / 3989 байт). - Реализация: не применима. Проверка: выполнена. - [x] **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 --art-dir `. - Полный прогон в копию `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. Чек-лист изменений - [x] **C1. `CREATE` `Runtime/UI/NeonUi.cs`** — 9-slice атомы интерфейса. - Спрайты панели, поля, кнопки с фаской, свечения генерируются в рантайме (по образцу `BoardTextures`) либо берутся из одного PNG-атласа; кеш статический. - API: `Body(...)`, `Bevel(...)`, `Glow(...)`, `Circle/Ring/HorizontalBar`, `AddImage/AddAnchored`; свечение — отдельный спрайт позади тела. - Реализация: выполнена. Проверка: компиляция, EditMode 58/58, визуальный прогон. - Зависит: нет. - [x] **C2. `CREATE` `Runtime/UI/LoginBackdrop.cs`** — фон экрана. - `Resources/Login/login_bg` → `Texture2D` → `Sprite.Create` (как `CardImageService`), cover-масштаб через `AspectRatioFitter`, поверх — затемняющий слой под панелью. - Если файла нет — процедурный градиент-заглушка, без падения. - Реализация: выполнена. Проверка: компиляция, EditMode 58/58, визуальный прогон. - Зависит: арт-файл от пользователя. - [x] **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. - [x] **C4. `CREATE` `Runtime/UI/LoginMenu.cs`** — гамбургер и панель меню. - Кнопка 56×56 в правом верхнем углу, три линии. - Меню 340 px: переключатель `RU/EN`, версия, поле адреса сервера (сохраняет в `AppConfig`/`PlayerPrefs`), «Закрыть». - Закрытие по Esc (`LoginMenu.IsOpen` + `Close`), по кнопке и клику вне панели; не перехватывает ввод у формы, когда закрыто. - Реализация: выполнена. Проверка: EditMode 60/60, визуальный прогон. - Зависит: C1. - [x] **C5. `MODIFY` `Runtime/App/LoginSceneController.cs` + `Runtime/App/SceneEnvironment.cs`.** - `Login` больше не вызывает `SceneEnvironment.Build`; камера — заливка цветом фона, без пола/кубов/точечных источников. - Ветка `SceneTheme.Login` в окружении остаётся, но не используется (удалять только вместе с остальными темами). - Реализация: выполнена. Проверка: компиляция, EditMode 58/58, визуальный прогон. - Зависит: C2. - [x] **C6. `MODIFY` `Editor/SceneSmoke.cs`** — проверка Login без 3D. - Для `Login` требовать `LoginScreen` и фон (`LoginBackdrop`/`LoginBackground`), `Vanguard 3D Environment` — только для `Lobby`. - Реализация: выполнена. Проверка: компиляция, EditMode 58/58, визуальный прогон. - Зависит: C3. - [x] **C7. `MODIFY` `Runtime/UI/Localization.cs`** — новые ключи. - «Логин / Email», «Пароль», «Забыли пароль?», «Войти», «Создать аккаунт», «Подключение…», пункты меню, заглушка сброса пароля. - Реализация: выполнена. Проверка: компиляция, EditMode 58/58, визуальный прогон. - Зависит: C3. - [x] **C8. `CREATE` ассеты** — `Resources/Login/`. - `login_bg.png|jpg` (1920×1080+, без текста и панели), при наличии — логотип и атлас иконок (пользователь/замок/глаз/меню/щит). - Импорт: `Texture2D` (не Sprite), max size 2048, `sRGB`; отдельный `.meta` в репозитории. - Реализация: выполнена. Проверка: компиляция, EditMode 58/58, визуальный прогон. - Зависит: файлы от пользователя. - [x] **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. Чек-лист изменений - [x] **C1. `CREATE` `Runtime/UI/LobbyBackdrop.cs`** — процедурный фон. - Вертикальный градиент, сетка, центральное свечение и виньетка; спрайт кешируется статически, создаётся один раз. - Cover-масштабирование на 16:9/16:10/21:9 без полос; при появлении арта заменяется одним изображением без правки раскладки. - Объект называется `LobbyBackdrop` для смоука. - Реализация: выполнена. Проверка: EditMode 62/62, `SCENE_SMOKE`. - Зависит: нет. - [x] **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. - [x] **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. - [x] **C4. `CREATE` `Runtime/UI/BattleEntryOverlay.cs`** — выбор колоды и режима. - Загрузка колод `MetaService.GetDecksAsync`, выбор карточкой, режимы «Рейтинговый» / «С ботом», кнопка «В бой», «Собрать колоду». - Валидация через `ValidateDeckAsync` перед запуском; ошибки — в панели. - Ranked закрывает оверлей и открывает существующую очередь (`LobbySceneController.OnRanked` → `QueueView.Show`); практика — `StartPracticeAsync` + `Flow.LoadBattleAsync`. - Закрытие: крестик и Esc (фон блокируется). - Реализация: выполнена. Проверка: компиляция, EditMode 62/62; сетевой сценарий — ручная проверка. - Зависит: C2. - [x] **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. - [x] **C6. `MODIFY` меню** — общий гамбургер для Login и Lobby. - `LoginMenu` вынесен в общий `Runtime/UI/HamburgerMenu.cs` (язык, версия, адрес сервера, закрыть; для Lobby дополнительно «История матчей» и «Выйти»). - `Login` использует его без визуальных изменений; в Lobby «Выйти» переехал из топбара в меню. - Esc закрывает меню через `HotkeysDriver`; клик вне панели работает. - Реализация: выполнена. Проверка: EditMode 62/62, Login не изменён. - Зависит: C2. - [x] **C7. `DELETE`/`MODIFY` `Runtime/UI/MetaView.cs`** — удалить после переноса функций в `LobbyView`. - Обновление профиля и обработка 401 → Logout перенесены в `LobbyView`; `MetaView` удалён вместе с `.meta`. - Реализация: выполнена. Проверка: EditMode 62/62, ссылок на `MetaView` не осталось. - Зависит: C2, C3. - [x] **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. - [x] **C9. `MODIFY` `Runtime/UI/Localization.cs`** — новые ключи. - Рейтинг, Сообщество, подписи плашек, задания, чат, друзья, «скоро», режимы, ошибки проверки колоды, вкладки Community. - Реализация: выполнена. Проверка: дубликатов ключей нет, EditMode 62/62. - Зависит: C2, C4, C5, C6. - [x] **C10. `TEST` EditMode.** - `LobbyView`: три плашки, балансы, «скоро», отсутствие онбординга. - `CommunityView`: переключение вкладок. - Существующий `Client_Views_Build` переведён с `MetaView` на `LobbyView`; проверка `LobbyScreen`. - Реализация: выполнена. Проверка: EditMode **62/62** в копии без `Library`. - Зависит: C1–C9. - [x] **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 выполнен - **Реализация:** не начата - **Проверка:** не выполнена - **Затронутые системы:** `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 (иллюстрация до полосы флейвора), с кешем по `imageKey`. - `preserveAspect = true`; выравнивание — центральное; при отсутствии арта — процедурный fallback без обрезки. - Полный арт (`Apply`) не меняется: он нужен карточке и 3D-картам. - Реализация: не выполнена. Проверка: не выполнена. - Зависит: нет. - [ ] **C2. `MODIFY` `Runtime/UI/DeckBuilderView.cs`** — один экран. - Убрать `_listView`/`_editorView` и `BackToList`; оставить один корень `DeckBuilderScreen`: топбар (назад в Lobby, заголовок, фильтры, сброс, статус) и тело (коллекция + правая панель). - Правая панель: `DeckListPanel` и `DeckPanel` (карты колоды), видимость переключается; при открытии колоды — `DeckPanel`, кнопка «К списку колод» возвращает `DeckListPanel`. - Ширина правой панели 380 px; содержимое — без пересоздания сцены. - Реализация: не выполнена. Проверка: не выполнена. - Зависит: C1, C3, C4. - [ ] **C3. `MODIFY` список колод.** - Строка: имя, `N/50`, `T N/16`, формат (`Classic`, локализовано), распределение `G0…G3`, бейдж «Готова / Не готова». - Валидность считать локально (50 карт, 16 триггеров, ≤4 по `nameId`). - Подсветка открытой колоды; кнопка «Создать колоду» — над списком. - Реализация: не выполнена. Проверка: не выполнена. - Зависит: C2. - [ ] **C4. `MODIFY` список карт колоды.** - Группировать по `nameId`, счётчик — сумма принтов; превью — первая карта группы. - Строка 84 px: иллюстрация 64×54 (пропорция 1.18), имя (2 строки), бейдж `G`, `×N`, триггер. - Сортировка: `Grade` → `Loc.CardName` (`StringComparer.Ordinal`). - Клик по строке — убрать одну копию; ПКМ — карточка. Счётчики и валидация обновляются сразу. - Реализация: не выполнена. Проверка: не выполнена. - Зависит: C1, C2. - [ ] **C5. `MODIFY` фильтры — мультивыбор.** - Хранить `HashSet Clans`, `HashSet Grades`, `HashSet Triggers`; пустой набор = «все». - Оверлей: группы чипов с подсветкой выбранного, «Все»/«Сбросить», «Применить» (применяет черновик) и «Отмена» (откатывает). - Сводка в топбаре: `Клан: RP, Kagero · Ранг: 0, 1 · Триггер: Heal`. - `MatchesFilter` проверяет вхождение во все активные наборы. - После применения — пересборка коллекции; счётчики в ячейках обновляются без полного пересоздания (если возможно). - Реализация: не выполнена. Проверка: не выполнена. - Зависит: C2, C6. - [ ] **C6. `MODIFY` фильтрация и сортировка — чистый класс.** - Вынести в `Runtime/UI/DeckListFilter.cs` (или статический helper): `Matches(meta, filter)`, `SortDeck(cards, catalog)`, `GroupDeck(...)`. - Фильтр: наборы клана/грейда/триггера; сортировка колоды: грейд → имя; группировка по `nameId`. - Реализация: не выполнена. Проверка: не выполнена. - Зависит: C4, C5. - [ ] **C7. `MODIFY` ячейки коллекции.** - Иллюстрация через `ApplyIllustration`, область арта с правильной пропорцией (например, 174×147), без растяжения. - Показывать `×в колоде` по `nameId`; при достижении лимита 4 или 16 триггеров — приглушать и не добавлять. - Реализация: не выполнена. Проверка: не выполнена. - Зависит: C1, C5. - [ ] **C8. `MODIFY` live-валидация.** - `UpdateStats` дополнить статусом валидности и цветом счётчиков; «Сохранить», «В бой», «С ботом» — `interactable` только при валидной сохранённой колоде. - Ошибки лимитов — в статусе редактора, без модальных окон. - Реализация: не выполнена. Проверка: не выполнена. - Зависит: C3, C4. - [ ] **C9. `MODIFY` `Runtime/UI/Localization.cs`** — новые ключи. - «Мои колоды», «К списку колод», «Готова», «Не готова», «Клан: », «Ранг: », «Триггер: », «Применить», «Отмена», «Все кланы», «Все ранги», «Все триггеры», «Иллюстрация» и подписи счётчиков. - Реализация: не выполнена. Проверка: не выполнена. - Зависит: C2–C5. - [ ] **C10. `TEST` EditMode.** - `DeckListFilter`: мультивыбор клана/грейда/триггера, пустой набор = все; сортировка грейд → имя; группировка по `nameId` суммирует принты. - `CardImageService`: обрезка иллюстрации возвращает неполный rect (проверка по спрайту/текстуре без реального файла — на fallback). - UI: один экран, переключение панелей, сводка фильтров, бейдж валидности. - Существующий `Client_Views_Build` — без изменений ожиданий. - Реализация: не выполнена. Проверка: не выполнена. - Зависит: C1–C9. - [ ] **C11. `VERIFY` EditMode, smoke, standalone.** - Копия без `Library`; EditMode; `SCENE_SMOKE`; standalone. - Ручная проверка: фильтры, список колод, редактор, добавление/удаление, сохранение, «В бой»/«С ботом», ПКМ-карточка. - Реализация: не выполнена. Проверка: не выполнена. - Зависит: 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. Результат - **Реализовано:** — - **Проверки:** — - **Не завершено/заблокировано:** — - **Skill обновлён:** — - **Git:** —