2341 lines
160 KiB
Markdown
2341 lines
160 KiB
Markdown
# 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: анализ и план готовы; реализация ожидает | не выполнена |
|
||
|
||
Новые задачи добавляются в конец файла отдельными разделами. Не используй один
|
||
раздел для нескольких независимых задач.
|
||
|
||
---
|
||
|
||
## Шаблон задачи (не является активной задачей)
|
||
|
||
### 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<int, CardSourcePose>` для `Soul`, `Drop`,
|
||
`FaceUpDamage`; источники `Hand`/`Guardian`/`Trigger`/`Field` покрываются
|
||
существующими `Card3D` через `previousByInstance`.
|
||
- `ResolveSourcePose(spec)`: приоритет — `Card3D` (transfer), затем зона из
|
||
`_sourceByInstance`, затем `DeckPosition` с рубашкой вверх.
|
||
- Источники:
|
||
- Deck: `DeckPosition`, рубашка вверх (`baseYaw * X-flip 180°`);
|
||
- Soul: `SoulPosition`, лицом вверх;
|
||
- Drop: `DropPosition`, лицом вверх;
|
||
- FaceUpDamage: `DamagePosition`, лицом вверх и на боку;
|
||
- Hand/Guardian/Trigger/Field — через `previousByInstance`/transfer.
|
||
- Fallback Deck используется только с комментарием и покрывается тестами.
|
||
- `PlaceImmediate` получает rotation с учётом flip.
|
||
- Реализация: выполнена.
|
||
- Проверка: `New_Field_Unit_Starts_From_Hand_Not_Deck` — проходит.
|
||
- Зависит от: C1, C2.
|
||
|
||
- [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 <copy> `
|
||
-runTests -testPlatform EditMode -testResults <copy>\TestResults.xml
|
||
```
|
||
|
||
Серверные тесты не требуются, если не меняются Rules/Server/Shared; при
|
||
изменении контрактов — только `ssh home`/Docker.
|
||
|
||
#### F. Риски и неизвестные вопросы
|
||
|
||
- **Анонимные карты:** `InstanceId=0` не является уникальной личностью;
|
||
нельзя использовать его для глобального map. Нужны стабильные ключи по
|
||
зоне/индексу и отдельная политика для reveal.
|
||
- **Источник вне видимых зон:** Deck не содержит `InstanceId`; для карты из
|
||
колоды источник всегда Deck. Для карты из Soul/Drop/Guardian/Trigger/Damage
|
||
источник можно восстановить из previous view; иначе fallback Deck.
|
||
- **Смена открытого урона:** при перестановке face-up damage возможна
|
||
неоднозначность. Не анимировать перемещение между слотами урона, если это
|
||
не подтверждено; приоритет — не «телепорт из колоды».
|
||
- **`AnimationConfig.asset`:** после добавления полей Unity может сохранить
|
||
старые значения; проверить Inspector, не считать defaults автоматически
|
||
настроенными.
|
||
- **`UpdateHandView`:** если менять target каждый кадр, можно ухудшить
|
||
плавность; C6 требует подтверждения тестом/наблюдением, а не слепой правки.
|
||
- **Не менять** `CardMoveAnimator` timeout/`CancelCard`/`CancelAll` без
|
||
сохранения защиты от зависшего `InputLock`.
|
||
- **Не расширять** задачу на рефакторинг `BattleView` или замену animation
|
||
stack.
|
||
|
||
#### G. Результат
|
||
|
||
- **Реализовано:** C1–C8 (см. чек-лист).
|
||
- **Проверки:** EditMode 46/46 в копии без `Library`; компиляция без ошибок.
|
||
- **Не завершено/заблокировано:** C10 — ручная проверка пользователем.
|
||
- **Skill обновлён:** не требуется; изменения технические, не затрагивают
|
||
подтверждённые правила/архитектуру.
|
||
- **Git:** будут изменены `GameBoard3D.cs`, `CardMoveAnimator.cs`,
|
||
`BattleView.cs`, `AnimationConfig.asset`, `GameBoard3DTests.cs`,
|
||
`ClientSmokeTests.cs`, `IMPLEMENTATION_PLAN.md`, `docs/SESSION-STATE.md`,
|
||
`docs/HANDOFF.md`.
|
||
|
||
---
|
||
|
||
### TASK-2026-002 — Боевой UX и правила после TASK-2026-001
|
||
|
||
- **Статус:** `[ ]`
|
||
- **Создано:** 2026-10-24
|
||
- **Анализ:** DeepSeek выполнен, код не менялся
|
||
- **Реализация:** не начата
|
||
- **Проверка:** не выполнена
|
||
- **Затронутые системы:** `Card3D`, `CardMoveAnimator`, `GameBoard3D`,
|
||
`BattleView`, `AnimationConfig`, `TriggerCheckSequence`, `CameraDirector`,
|
||
`Vanguard.Rules` (Tejas, Perfect Guard), тесты.
|
||
|
||
#### A. Цель и требования
|
||
|
||
Исправить семь замечаний после первого блока анимаций:
|
||
|
||
1. При взятии карты из колоды она не должна сначала появляться в руке, затем
|
||
исчезать и только потом лететь по дуге.
|
||
2. При размещении карты из руки не должно быть двойного визуального
|
||
размещения: сначала в поле, затем исчезновение и дуга.
|
||
3. При призыве из души не должно быть отдельного перемещения перед дугой.
|
||
4. Tejas должен атаковать задний ряд только в колонке, расположенной напротив
|
||
его колонки, а не в зеркально противоположной.
|
||
5. Perfect Guard: сначала поместить карты в защиту, затем проверить
|
||
способности и активировать их; текущая логика оплаты/отмены должна быть
|
||
приведена к этому порядку.
|
||
6. После атаки, drive check, trigger-зоны, урона и других боевых событий
|
||
нужны паузы около 1.5 с; анимации должны быть длиннее.
|
||
7. При фокусе камеры на trigger-зону скрывать руку; после возврата камеры
|
||
возвращать руку.
|
||
|
||
Ограничения:
|
||
|
||
- не менять серверные контракты без необходимости; при изменении протокола
|
||
пересобрать `Vanguard.Shared.dll`;
|
||
- серверные тесты запускать только на `ssh home`/Docker;
|
||
- не менять сцены/префабы без отдельного решения;
|
||
- геймплей вручную проверяет пользователь.
|
||
|
||
#### B. Анализ причин
|
||
|
||
1. **Предварительное smooth-движение queued-карт.** `CardMoveAnimator`
|
||
ставит запросы в очередь со stagger. Пока запрос ждёт в `_queue`,
|
||
`Card3D.Tick` уже выполняет обычное экспоненциальное движение к
|
||
`_targetPosition`. Когда `StartNext` вызывает `StartArcMove`, карта
|
||
возвращается в `FromPosition` и начинает дугу. Визуально это выглядит как
|
||
«сначала переместилась, потом исчезла, потом дуга». Это объясняет п.1 и
|
||
п.3, а также часть п.2 при нескольких перемещениях.
|
||
2. **Локальный `DropAt` до снапшота.** `GameBoard3D.FinishDrag` вызывает
|
||
`card.DropAt(target)` сразу после отпускания мыши. Карта визуально
|
||
встаёт в поле, затем авторитарный снапшот делает `PlaceImmediate(hand/soul)`
|
||
и запускает дугу. Это отдельная причина п.2, не связанная с очередью.
|
||
3. **Tejas: `SameColumn` используется и для Move, и для Attack.** В
|
||
`GameEngine.SameColumn` зашито `0↔3`, `2↔4`, `-1↔1`. Для перемещения
|
||
внутри своего поля это верно. Для атаки по противнику это неверно:
|
||
клиент зеркалит половину противника (`PlaymatLayout.CirclePosition`),
|
||
поэтому атака из слота 3 должна попадать в слот 2, из 4 — в 0, из V — в 1.
|
||
Текущий тест `Tejas_Attacks_Back_Row_In_Its_Column` закрепляет неверную
|
||
пару `3→0`, его нужно исправить вместе с правилом.
|
||
4. **Perfect Guard сейчас — одна команда.** `GuardIntent` содержит и
|
||
карту защиты, и `DiscardPrintingId`; `GameEngine.Guard` сразу добавляет
|
||
карту в Guardian, сбрасывает карту оплаты и ставит `battle.Nullified`.
|
||
Отдельной стадии «карты уже в защите → проверить/активировать
|
||
способности» нет. Тесты `Perfect_Guard_*` описывают упрощённую модель.
|
||
5. **Задержки не образуют боевой такт.** Длительности feedback уже есть в
|
||
`AnimationConfig` (`attackDeclare`, `driveCheck`, `trigger`, `damage` и
|
||
др.), но текст события и движение карт идут разными путями: `OnEvents`
|
||
ставит feedback в очередь, а `RequestSnapshotAsync` отдельно запускает
|
||
`CardMoveAnimator`. Нет явной паузы после боевого события и нет гарантии,
|
||
что движение карты завершилось до следующего события. Движение карт
|
||
(`moveDuration = 0.5`, `importantMoveDuration = 0.9`) короче, чем нужно
|
||
пользователю.
|
||
6. **Рука следует за камерой.** `TriggerCheckSequence` двигает
|
||
`CameraDirector`, но не сообщает `GameBoard3D` о фокусе. `UpdateHandView`
|
||
каждый кадр привязывает руку к камере, поэтому при зуме на trigger-зону
|
||
рука оказывается перед камерой.
|
||
|
||
#### C. Чек-лист изменений
|
||
|
||
- [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`,
|
||
регэксп по `<li>`, триггеры/редкости из `cards-mvp.md`, запись
|
||
`set/cards/printings`, SP-принты `BT01/S01EN…S12EN` помечаются
|
||
`isParallel` + `basePrintingId`.
|
||
- `tools/import_card_art.py`: Fandom-галереи, коды только `TD\d{2}`.
|
||
- Сайт: `expansion=5` → BT01 (92 принта, 4 страницы), `3` → TD01 (16),
|
||
`4` → TD02 (16); всего 94 определения и 124 принта — совпадает с README.
|
||
- Карточка содержит `type`, `group` (клан), `race`, `nation`, `grade`,
|
||
`power`, `critical`, `shield`, `skill`, `gift` (например
|
||
`Critical Trigger +5000`), `effect`, `flavor`, `rarity`, `number`.
|
||
- Список сетов с id — `/cardlist/?change_ex` (47 сетов, `BT01=5`, `TD01=3`,
|
||
`TD02=4`), можно не хардкодить id.
|
||
- Сайт отдаёт 404 на запросы без браузерного `User-Agent` — нужен UA.
|
||
|
||
#### C. Чек-лист изменений
|
||
|
||
- [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 <tmp> --art-dir <tmp>`.
|
||
- Полный прогон в копию `content/sets` + `Compare-Object` с репозиторием.
|
||
- `dotnet run --project server/src/Vanguard.Cli -- validate` — **не запускался**
|
||
(нужен `ssh home`/Docker); косвенно покрыт побайтовым совпадением YAML.
|
||
- Скачивание арта проверено на TD01 (16 файлов) и на `--force-art`.
|
||
|
||
#### F. Риски и открытые вопросы
|
||
|
||
- Разметка сайта нестабильна: любой редизайн ломает регэкспы. Симптом —
|
||
«не найдено имя карты» или расхождение с «N Results».
|
||
- Сайт отвечает 404 без браузерного `User-Agent` — UA обязателен, `curl`
|
||
без заголовка вводит в заблуждение.
|
||
- Полный прогон — ~2–6 минут на 124 карточки (по одному запросу на карту,
|
||
пауза `--delay`). Для нескольких сетов сразу это нормально, для правки
|
||
одной карты — избыточно.
|
||
- Официальные арты — PNG ~350–430 КиБ; текущие в репозитории — JPG. Массовая
|
||
замена не делалась: `--force-art` меняет расширение и удаляет `.meta`.
|
||
- `python` в Microsoft Store — заглушка, нужен `py` (отражено в README).
|
||
- Открытый вопрос: переносить ли `skill` (`Boost`/`Twin Drive!!`) в поле
|
||
`boost` — сейчас не переносится, чтобы не менять игровые данные.
|
||
|
||
#### G. Результат
|
||
|
||
- **Реализовано:** C1–C6.
|
||
- **Проверки:** полный прогон в копию — 0 различий с `content/sets`;
|
||
94 определения / 124 принта; арт TD01 16/16; `--force-art` заменяет
|
||
`.jpg` на `.png` без дублей.
|
||
- **Не завершено:** `validate` на `ssh home`.
|
||
- **Skill обновлён:** не требуется.
|
||
- **Git:** `0620ec5` (парсер, README, план), арты — `f2ae6b9`.
|
||
|
||
---
|
||
|
||
### TASK-2026-021 — Login по референсу: 2D-арт и неоновый UI
|
||
|
||
- **Статус:** `[!]`
|
||
- **Создано:** 2026-10-31
|
||
- **Анализ:** DeepSeek (по референсу в чате от 2026-10-31)
|
||
- **Реализация:** DeepSeek: C1–C9 выполнены
|
||
- **Проверка:** EditMode 60/60; `SCENE_SMOKE success=True`; standalone собран; ручная приёмка — за пользователем
|
||
- **Затронутые системы:** `LoginView`, `LoginSceneController`,
|
||
`SceneEnvironment`, `UiKit`, новый `NeonUi`, новый `LoginMenu`,
|
||
`Localization`, `Resources/Login/*`, `SceneSmoke`, EditMode-тесты.
|
||
|
||
#### A. Цель и требования
|
||
|
||
Привести экран `Login` к референсу: полноэкранный 2D key art, поверх —
|
||
центрированная неоновая панель с двумя полями и кнопкой «Войти».
|
||
|
||
Состав экрана (сверху вниз, координаты для 1920×1080):
|
||
|
||
1. **Фон** — один арт на весь экран, cover (без полос по краям), лёгкое
|
||
затемнение под панелью для читаемости текста.
|
||
2. **Логотип** — по центру, ширина ≈ 520 px, верх ≈ 140 px. В MVP входит в
|
||
состав арта; отдельным слоем — только если дадут прозрачный PNG.
|
||
3. **Панель** — 560×420, центр по X, центр по Y ≈ 600 (чуть ниже середины):
|
||
- радиус 24, заливка `rgba(7,17,43,0.82)`;
|
||
- рамка 2 px `#2E7DFF`, внешнее свечение ≈ 24 px `rgba(46,125,255,0.35)`;
|
||
- тонкая внутренняя рамка `rgba(150,200,255,0.18)`.
|
||
4. **Поле ввода** — 480×64, радиус 10, заливка `rgba(4,12,30,0.9)`, рамка
|
||
`rgba(46,125,255,0.45)`; слева иконка 22×22 (16 px от края), текст 18 px,
|
||
placeholder `rgba(130,150,190,0.9)`; фокус — рамка `#6CC0FF` + свечение.
|
||
- «Логин» — иконка пользователя;
|
||
- «Пароль» — иконка замка + глаз справа (показать/скрыть).
|
||
5. **«Забыли пароль?»** — 15 px, `#5FB0FF`, справа под полем пароля.
|
||
6. **Кнопка «Войти»** — 480×72, срезанные углы 14 px, градиент
|
||
`#1B5FC9 → #2E8BFF`, рамка 1.5 px `#7CC4FF`, свечение; текст 22 px bold,
|
||
белый. Hover +10 % яркости, pressed −8 %, disabled 45 % + «Подключение…».
|
||
7. **«Создать аккаунт»** — вторичная ссылка 15 px `#7FA8E0` по центру под
|
||
кнопкой (решение 2026-10-31: регистрация остаётся доступной).
|
||
8. **Статус** — 14 px под ссылками, автоскрытие через 6 с; ошибка `#FF7A6B`,
|
||
успех `#6BE08A`.
|
||
9. **Гамбургер** — 56×56, правый верхний угол 28/28, радиус 12, рамка
|
||
`#2E7DFF`, три линии 22×2. Открывает меню: `RU/EN`, версия клиента,
|
||
**адрес сервера** (dev-поле), «Закрыть». Выхода нет — сессии ещё нет.
|
||
10. **Тэглайн** — левый нижний угол 40/36: щит 44×44 + две строки
|
||
`FIGHT TOGETHER.` / `BUILD YOUR FUTURE.`, 11 px, трекинг 2,
|
||
`rgba(150,195,255,0.65)`.
|
||
11. **Копирайт** — правый нижний угол, 12 px `rgba(130,165,215,0.5)`:
|
||
`© Cardfight!! Vanguard Fan Project`.
|
||
|
||
Ограничения:
|
||
|
||
- без новых пакетов и без Addressables (арт из `Resources`, как арт карт);
|
||
- auth-контракты не меняются; серверного сброса пароля нет;
|
||
- правила, сервер, протокол и контент не трогаются;
|
||
- имя `LoginScreen` сохраняется (на него опирается `SceneSmoke`);
|
||
- 3D-окружение в `Login` не строится — это исключение из «3D в каждой
|
||
сцене», зафиксированное в `CLIENT-SCENES-3D-ANIMATIONS.md` (8.7).
|
||
|
||
Критерии готовности:
|
||
|
||
- композиция и цвета совпадают с референсом на 1920×1080;
|
||
- на 16:10 и 21:9 фон заполняет экран, панель остаётся по центру;
|
||
- вход, регистрация, ошибки и busy-состояние работают как раньше;
|
||
- RU/EN переключается из меню, строки локализованы;
|
||
- `SceneSmoke.Run` проходит на сцене `Login` без 3D-окружения.
|
||
|
||
#### B. Текущее состояние
|
||
|
||
- `LoginView` (`Runtime/UI/LoginView.cs`): панель `LoginScreen` + карточка
|
||
620×820 с eyebrow/title/subtitle, **полем адреса сервера**, полем имени,
|
||
паролем, кнопкой «ВОЙТИ», кнопкой «СОЗДАТЬ АККАУНТ», статусом, футером с
|
||
версией/стендом и кнопкой языка в правом верхнем углу.
|
||
- `LoginSceneController` вызывает `SceneEnvironment.Build(SceneTheme.Login)`:
|
||
камера, свет, пол, 14 кубов (стол, «карты», неон). 3D-стол виден по краям
|
||
панели.
|
||
- `UiKit` умеет только прямоугольные `Image` с плоским цветом: скруглений,
|
||
рамок, свечения и 9-slice нет; `CreateInput` — стандартный uGUI `InputField`.
|
||
- Шрифт — `LegacyRuntime.ttf` (кириллица есть, геометрия нейтральная).
|
||
- `SceneSmoke` (`Editor/SceneSmoke.cs:116-123`) требует наличия объекта
|
||
`Vanguard 3D Environment` в активной сцене; после отказа от 3D в Login
|
||
проверка упадёт.
|
||
- Сервер: endpoint сброса пароля отсутствует (`forgot`/`reset` не находятся),
|
||
поэтому «Забыли пароль?» — только заглушка.
|
||
|
||
Что сохранить:
|
||
|
||
- `AuthService`, `AppConfig`, обработку `ApiException` и формат ошибок;
|
||
- имя `LoginScreen` и вызов `_onAuthenticated` после успеха;
|
||
- `UiKit.CreateCanvas` и текущий CanvasScaler (1920×1080, match 0.5);
|
||
- кнопку языка как функцию (переезжает в меню, но не исчезает).
|
||
|
||
#### C. Чек-лист изменений
|
||
|
||
- [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, решения ниже)
|
||
- **Реализация:** не начата
|
||
- **Проверка:** не выполнена
|
||
- **Затронутые системы:** `LobbySceneController`, новый `LobbyView` (вместо
|
||
`MetaView`), новый `LobbyBackdrop`, новый `BattleEntryOverlay`, меню,
|
||
новая сцена `Community` + `CommunitySceneController`/`CommunityView`,
|
||
`SceneFlow`, `SceneBuilder`, `SceneSmoke`, `Localization`, EditMode-тесты.
|
||
Сервер, правила и протокол не меняются.
|
||
|
||
#### A. Цель и требования
|
||
|
||
Привести `Lobby` к композиции референса, но оставить только реально
|
||
поддержанные функции; отсутствующие в сервере блоки — честные заглушки
|
||
«скоро» без фейковых цифр.
|
||
|
||
Состав экрана (1920×1080):
|
||
|
||
1. **Верхняя панель (≈96 px):** аватар (инициалы, ассетов нет), ник,
|
||
`RankedMmr`, бейдж сезона-заглушки; справа — «Рейтинг», «Сообщество»,
|
||
«Меню».
|
||
2. **Левая колонка (≈560 px):** три крупные плашки с иконкой, заголовком и
|
||
подписью — `BATTLE`, `DECK BUILDER`, `SHOP`.
|
||
3. **Блок миссий** под плашками: заголовок «Ежедневные / еженедельные
|
||
задания» и 2–4 строки-заглушки с замком и «скоро». Прогресс и таймер
|
||
обновления не рисуются: серверной модели нет.
|
||
4. **Балансы** внизу слева: Gold, Dust, Tokens; клик открывает магазин.
|
||
5. **Центр:** большая панель `Chat` — заглушка «скоро» с полем ввода в
|
||
состоянии disabled.
|
||
6. **Справа:** панель `Friends Online` — заглушка: счётчик без данных не
|
||
показывать, 3–5 строк-заглушек, «скоро».
|
||
7. **Фон:** процедурный тёмный неоновый градиент с сеткой и виньеткой в
|
||
стиле Login. 3D-окружение в `Lobby` не строится.
|
||
|
||
Поведение:
|
||
|
||
- **BATTLE** открывает модальный оверлей: список колод, режим
|
||
`Рейтинговый` / `С ботом`, кнопка «В бой». Без колод или без валидной
|
||
колоды кнопка disabled и есть переход «Собрать колоду» в `DeckBuilder`.
|
||
Рейтинговый режим уходит в существующую очередь `classic-ranked`
|
||
(`Flow.OpenLobbyWithQueueAsync`), «С ботом» — `StartPracticeAsync` и
|
||
`LoadBattleAsync`. Серверные контракты не меняются.
|
||
- **Rating** — оверлей-заглушка «Рейтинг — скоро». Лидерборд (top-100 по
|
||
`RankedMmr`) — отдельная задача с серверным endpoint.
|
||
- **Community** — отдельная сцена-каркас с вкладками «Гайды / Колоды /
|
||
Тикеты», внутри каждой — заглушка. Реальные гайды, публичные колоды и
|
||
отправка тикетов — отдельные задачи с сервером.
|
||
- **Меню** — гамбургер: язык RU/EN, версия, адрес сервера, «Выйти»,
|
||
«Закрыть»; Esc и клик вне панели закрывают.
|
||
- **Онбординг** в Lobby не показывается: ни статуса, ни кнопки. Выбор
|
||
стартовой триал-деки остаётся отдельным экраном до Lobby; когда появится
|
||
обучение, он будет заменён им.
|
||
|
||
Ограничения:
|
||
|
||
- без новых пакетов и без Addressables;
|
||
- сервер, правила, протокол и контент не меняются;
|
||
- `QueueView`, `StoreView`, `HistoryView`, `DeckBuilder` и `Battle`
|
||
сохраняют текущее поведение;
|
||
- `Login` не должен измениться визуально при выносе общего меню;
|
||
- геймплей и визуальную приёмку проверяет пользователь.
|
||
|
||
Критерии готовности:
|
||
|
||
- композиция и стиль совпадают с референсом на 1920×1080, на 16:10 и 21:9
|
||
фон заполняет экран, панели не разъезжаются;
|
||
- все несерверные блоки визуально помечены «скоро» и не кликабельны;
|
||
- BATTLE доводит до очереди или практики без захода в редактор колоды;
|
||
- Community открывается как отдельная сцена, вкладки переключаются;
|
||
- в Lobby нет упоминаний онбординга;
|
||
- существующие EditMode-тесты и смоуки обновлены и проходят.
|
||
|
||
#### B. Текущее состояние
|
||
|
||
- `LobbySceneController` строит 3D-окружение (`SceneEnvironment.Build`),
|
||
создаёт `MetaView`, `OnboardingView`, `QueueView`, `StoreView`,
|
||
`HistoryView`; при `!OnboardingCompleted` показывает онбординг вместо
|
||
лобби; «В бой» ведёт в `DeckBuilder`.
|
||
- `MetaView` (`Runtime/UI/MetaView.cs`): топбар `VANGUARD` + ник + язык +
|
||
«Выйти», 5 статистик (Gold/Dust/Tokens/Ranked/Casual), 4 кнопки
|
||
(Коллекция/Магазин/История/В бой), запись, статус, кнопка выбора
|
||
триал-деки. Полностью заменяется.
|
||
- `QueueView` — только `classic-ranked`; практика запускается из
|
||
`DeckBuilderView` («С ботом»).
|
||
- Сервер: `UserEntity` хранит `UserName`, `RankedMmr`, `Matches`, `Wins`,
|
||
но endpoint лидерборда, миссий, чата, друзей, сезонов и тикетов
|
||
отсутствует.
|
||
- Аватаров в проекте нет; ассетов фона Lobby нет.
|
||
- `SceneSmoke` требует `Vanguard 3D Environment` для `Lobby`; тесты
|
||
`ClientSmokeTests` создают `MetaView` напрямую и проверяют `MetaScreen`,
|
||
`OnboardingScreen`.
|
||
- Общие визуальные атомы — `NeonUi` из `TASK-2026-021`.
|
||
|
||
Что сохранить:
|
||
|
||
- `AuthService`, `MetaService`, `PlayerState`, обработку 401 → Login;
|
||
- существующие экраны магазина, истории, очереди, реплеев;
|
||
- `SceneFlow` как единственный переход между сценами;
|
||
- `UiKit`/`NeonUi`, CanvasScaler 1920×1080;
|
||
- текущий визуальный язык Login.
|
||
|
||
#### C. Чек-лист изменений
|
||
|
||
- [ ] **C1. `CREATE` `Runtime/UI/LobbyBackdrop.cs`** — процедурный фон.
|
||
- Вертикальный градиент, сетка/гексы, виньетка, 2–3 неоновые линии;
|
||
спрайты кешируются статически, создаются один раз.
|
||
- Cover-масштабирование на 16:9/16:10/21:9 без полос; при появлении арта
|
||
заменяется одним изображением без правки раскладки.
|
||
- Объект называется `LobbyBackdrop` для смоука.
|
||
- Реализация: не выполнена. Проверка: не выполнена.
|
||
- Зависит: нет.
|
||
|
||
- [ ] **C2. `CREATE` `Runtime/UI/LobbyView.cs`** — новый дашборд вместо
|
||
`MetaView`.
|
||
- Топбар: аватар-инициалы, ник, MMR, сезон-заглушка, «Рейтинг»,
|
||
«Сообщество», «Меню».
|
||
- Левая колонка: BATTLE / DECK BUILDER / SHOP с иконками и подписями;
|
||
блок миссий-заглушка; балансы Gold/Dust/Tokens с переходом в магазин.
|
||
- Центр: `Chat`-заглушка; справа: `Friends Online`-заглушка.
|
||
- Корневой объект `LobbyScreen`; тексты через `Loc`.
|
||
- Реализация: не выполнена. Проверка: не выполнена.
|
||
- Зависит: C1, C9.
|
||
|
||
- [ ] **C3. `MODIFY` `Runtime/App/LobbySceneController.cs`** — сборка Lobby
|
||
без 3D и без `MetaView`.
|
||
- Убрать `SceneEnvironment.Build(SceneTheme.Lobby)`; создать
|
||
`LobbyBackdrop`, `LobbyView`, оверлеи.
|
||
- Убрать `ShowOnboarding`/`_onboardingButton` из лобби; оставить переход
|
||
на отдельный экран выбора триал-деки при `!OnboardingCompleted`.
|
||
- Подключить BATTLE-оверлей, Rating-заглушку, Community → `SceneFlow`.
|
||
- Реализация: не выполнена. Проверка: не выполнена.
|
||
- Зависит: C2, C4, C5, C6.
|
||
|
||
- [ ] **C4. `CREATE` `Runtime/UI/BattleEntryOverlay.cs`** — выбор колоды и
|
||
режима.
|
||
- Загрузка колод `MetaService.GetDecksAsync`, выбор карточкой, режимы
|
||
«Рейтинговый» / «С ботом», кнопка «В бой», «Собрать колоду».
|
||
- Валидация через `ValidateDeckAsync` перед запуском; ошибки — в панели.
|
||
- Ranked → `Flow.OpenLobbyWithQueueAsync`; практика →
|
||
`StartPracticeAsync` + `Flow.LoadBattleAsync`.
|
||
- Закрытие: крестик, Esc, клик вне панели; фон блокируется.
|
||
- Реализация: не выполнена. Проверка: не выполнена.
|
||
- Зависит: C2.
|
||
|
||
- [ ] **C5. `CREATE` сцена `Community`.**
|
||
- `Scenes/Community.unity`, `CommunitySceneController`,
|
||
`Runtime/UI/CommunityView` с вкладками «Гайды / Колоды / Тикеты» и
|
||
заглушками; кнопка «Назад» в Lobby.
|
||
- `SceneFlow`: константа `Community` и `LoadCommunityAsync`;
|
||
`SceneBuilder`: создание сцены и добавление в Build Settings после
|
||
`Lobby`; `SceneSmoke` — проверка экрана `CommunityScreen`.
|
||
- Серверных вызовов нет.
|
||
- Реализация: не выполнена. Проверка: не выполнена.
|
||
- Зависит: C2.
|
||
|
||
- [ ] **C6. `MODIFY` меню** — общий гамбургер для Login и Lobby.
|
||
- Вынести `LoginMenu` в общий `Runtime/UI/HamburgerMenu.cs` (язык,
|
||
версия, адрес сервера, закрыть; для Lobby дополнительно «Выйти»).
|
||
- `Login` использует его без визуальных изменений; в Lobby «Выйти»
|
||
переезжает из топбара в меню.
|
||
- Esc и клик вне панели закрывают; вне меню ввод формы не перехватывается.
|
||
- Реализация: не выполнена. Проверка: не выполнена.
|
||
- Зависит: C2.
|
||
|
||
- [ ] **C7. `DELETE`/`MODIFY` `Runtime/UI/MetaView.cs`** — удалить после
|
||
переноса функций в `LobbyView`.
|
||
- Убедиться, что в `MetaView` не осталось уникальной логики (обновление
|
||
профиля, 401 → Logout) — она переносится в `LobbyView`.
|
||
- Реализация: не выполнена. Проверка: не выполнена.
|
||
- Зависит: C2, C3.
|
||
|
||
- [ ] **C8. `MODIFY` `Editor/SceneSmoke.cs` + `Editor/SceneBuilder.cs`.**
|
||
- `Lobby` проверяется по `LobbyBackdrop` и `LobbyScreen`; 3D-окружение
|
||
больше не требуется.
|
||
- Для `Community` — отдельная проверка по состоянию; при необходимости
|
||
расширить смоук на несколько сцен.
|
||
- Реализация: не выполнена. Проверка: не выполнена.
|
||
- Зависит: C2, C5.
|
||
|
||
- [ ] **C9. `MODIFY` `Runtime/UI/Localization.cs`** — новые ключи.
|
||
- BATTLE / DECK BUILDER / SHOP и подписи, «Рейтинг», «Сообщество»,
|
||
«Меню», «Задания», «Чат», «Друзья онлайн», «скоро», «Режим»,
|
||
«Рейтинговый», «С ботом», «Собрать колоду», «Выйти», вкладки Community.
|
||
- Реализация: не выполнена. Проверка: не выполнена.
|
||
- Зависит: C2, C4, C5, C6.
|
||
|
||
- [ ] **C10. `TEST` EditMode.**
|
||
- `LobbyView`: топбар, три плашки, балансы, заглушки disabled и с
|
||
пометкой «скоро»; в Lobby нет текста/кнопки онбординга.
|
||
- `BattleEntryOverlay`: список колод, disabled без валидной колоды,
|
||
закрытие по Esc, выбор режима.
|
||
- `CommunityView`: переключение вкладок, «Назад».
|
||
- `SceneSmoke`: `Lobby` без 3D, `Community` открывается.
|
||
- Существующие тесты `ClientSmokeTests` (создание `MetaView`) переписать
|
||
под `LobbyView`.
|
||
- Реализация: не выполнена. Проверка: не выполнена.
|
||
- Зависит: C1–C9.
|
||
|
||
- [ ] **C11. `VERIFY` EditMode, smoke, standalone.**
|
||
- Копия проекта без `Library`; `SceneSmoke.Run`, `RunPlaymat`;
|
||
standalone-сборка.
|
||
- Ручная сверка с референсом — пользователь.
|
||
- Реализация: не выполнена. Проверка: не выполнена.
|
||
- Зависит: 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. Результат
|
||
|
||
- **Реализовано:** —
|
||
- **Проверки:** —
|
||
- **Не завершено/заблокировано:** —
|
||
- **Skill обновлён:** —
|
||
- **Git:** —
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|