diff --git a/.gitignore b/.gitignore index dafdd4e..b5df926 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,10 @@ client/Assets/StreamingAssets/aa/* client/*.log client/TestResults*.xml +# Локальный 4x-арт (251 МБ, кодом не используется) — не коммитим +client/Assets/Vanguard/Resources/CardArt_4x/ +client/Assets/Vanguard/Resources/CardArt_4x.meta + # --- Unity прочее --- *.unitypackage crashlytics-build.properties diff --git a/.opencode/skills/vanguard-remaster/SKILL.md b/.opencode/skills/vanguard-remaster/SKILL.md index ea713d7..7e870ce 100644 --- a/.opencode/skills/vanguard-remaster/SKILL.md +++ b/.opencode/skills/vanguard-remaster/SKILL.md @@ -49,7 +49,8 @@ description: Use for any work in the Vanguard ReMaster repository: Cardfight!! V - **После блока:** клиентские EditMode, серверные тесты (на home), `validate`, `translations check`, коммит и push. - **Сначала план, потом код:** новые изменения проходят анализ DeepSeek с - записью в `docs/Development/IMPLEMENTATION_PLAN.md`, затем реализацию Kimi. + записью в `docs/Development/IMPLEMENTATION_PLAN.md`, затем реализацию — + её тоже выполняет DeepSeek, отдельным проходом по готовому плану. Передача — через файлы и Git, не через память модели. Детали — `docs/Development/AGENT-WORKFLOW.md`. diff --git a/.opencode/skills/vanguard-remaster/workflow-and-decisions.md b/.opencode/skills/vanguard-remaster/workflow-and-decisions.md index 3f4b24c..39f76bd 100644 --- a/.opencode/skills/vanguard-remaster/workflow-and-decisions.md +++ b/.opencode/skills/vanguard-remaster/workflow-and-decisions.md @@ -64,6 +64,12 @@ - **[D]** 3D-сцена, отдельные сцены Login/Lobby/DeckBuilder/Battle; HUD — uGUI; reference 1920×1080. +- **[D]** Анимации описываются для локального игрока. Для противника + применяются те же правила, но зеркально с его стороны поля: направление, + сторона появления карты, наклон веера и порядок перекрытия зеркальны; + камера также отражается относительно центра стола. Отдельные + «противнические» анимации не делать, если это не подтверждённое + исключение. - **[D]** Камера в покое фиксирована по центру поля с небольшим наклоном; временно двигается только `CameraDirector` в trigger-последовательности. - **[D]** Выбор карты/круга/зоны/цели — клик/драг по объекту; кнопки только @@ -112,7 +118,8 @@ ## 4. Процесс разработки -Формальный двухэтапный процесс DeepSeek → Kimi описан в +Формальный двухэтапный процесс (анализ → план → реализация; обе стадии +выполняет DeepSeek, но раздельно) описан в `docs/Development/AGENT-WORKFLOW.md`; активный план задачи — в `docs/Development/IMPLEMENTATION_PLAN.md`. Ниже — краткие правила, без дублирования шаблона плана. diff --git a/AGENTS.md b/AGENTS.md index ac119e3..27b3283 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -40,8 +40,9 @@ ## Вопросы -- Для существенной неопределённости используй специальный инструмент - `question` (если он недоступен — задай вопрос обычным сообщением). +- **Все вопросы задавай только через специальный инструмент `question`.** + Это относится к любым уточнениям, выборам и открытым вопросам. Обычным + сообщением вопрос задавай только если инструмент недоступен. - Предлагай 2–4 коротких варианта с рекомендацией и возможность своего ответа. - Дождись ответа, прежде чем принимать зависящее от него решение. - Не спрашивай о том, что уже однозначно установлено кодом, документацией или @@ -50,22 +51,23 @@ ## Двухэтапная разработка - Любое пожелание, требующее изменения игры/кода, сначала проходит **анализ и - план DeepSeek**, затем **реализацию Kimi**. Не начинай код без плана, кроме - явно тривиальной правки, которую пользователь разрешил сделать сразу. + план**, затем **реализацию**. Оба этапа выполняет DeepSeek, но раздельно: + не начинай код в том же проходе, где анализируешь, кроме явно тривиальной + правки, которую пользователь разрешил сделать сразу. - Активный план и журнал задач — `docs/Development/IMPLEMENTATION_PLAN.md`; подробный процесс — `docs/Development/AGENT-WORKFLOW.md`. -- DeepSeek на этапе анализа не меняет игровой код, сцены, префабы, SO и - конфиги; можно читать, задавать вопросы и обновлять план/документацию. -- Kimi перед реализацией читает `AGENTS.md`, Skill и раздел задачи, проверяет - Git и актуальность файлов/методов. Устаревший план не является разрешением на +- На этапе анализа не меняются игровой код, сцены, префабы, SO и конфиги; + можно читать, задавать вопросы и обновлять план/документацию. +- Перед реализацией прочитай `AGENTS.md`, Skill и раздел задачи, проверь Git и + актуальность файлов/методов. Устаревший план не является разрешением на изменения. - Передача контекста — только через файлы репозитория и Git, не через память модели. Если агенты в разных ветках/копиях, это фиксируется в плане. - Статусы пунктов: `[ ]`, `[x]`, `[!]`, `[-]`; отдельно указывается «Реализация» и «Проверка». `[x]` без проверки запрещён. -- Kimi не расширяет объём, не делает необязательный рефакторинг и не удаляет - чужие изменения. При новом существенном решении пользователя — вопрос и - остановка зависимой части. +- На этапе реализации не расширяй объём, не делай необязательный рефакторинг и + не удаляй чужие изменения. При новом существенном решении пользователя — + вопрос и остановка зависимой части. ## Изменения diff --git a/README.md b/README.md index da79303..6927bbc 100644 --- a/README.md +++ b/README.md @@ -158,7 +158,8 @@ dotnet run --project server/src/Vanguard.Cli -- translations check - `IMPLEMENTATION_CHECKLIST.md` — исторический план по блокам и гейтам. - `docs/Development/IMPLEMENTATION_PLAN.md` — активный журнал задач и шаблон плана. -- `docs/Development/AGENT-WORKFLOW.md` — двухэтапный процесс DeepSeek → Kimi. +- `docs/Development/AGENT-WORKFLOW.md` — двухэтапный процесс: анализ → реализация + (обе стадии на DeepSeek). - `docs/HANDOFF.md` — состояние на конец сессии и порядок следующих шагов. - `docs/protocol.md` — протокол матча: кадры, интенты, события, таймеры, реконнект. - `docs/game-rules.md` — конспект правил и сверка с текущей реализацией. diff --git a/client/Packages/manifest.json b/client/Packages/manifest.json index e8cd2ca..bc71a94 100644 --- a/client/Packages/manifest.json +++ b/client/Packages/manifest.json @@ -1,5 +1,6 @@ { "dependencies": { + "com.coplaydev.unity-mcp": "https://github.com/CoplayDev/unity-mcp.git?path=/MCPForUnity#v10.3.0", "com.unity.addressables": "4.1.1", "com.unity.collab-proxy": "2.13.6", "com.unity.ide.rider": "3.0.40", diff --git a/docs/Development/AGENT-WORKFLOW.md b/docs/Development/AGENT-WORKFLOW.md index d18ef90..bbfed4d 100644 --- a/docs/Development/AGENT-WORKFLOW.md +++ b/docs/Development/AGENT-WORKFLOW.md @@ -1,9 +1,11 @@ -# Процесс DeepSeek → Kimi +# Процесс: анализ → реализация (DeepSeek) Двухэтапный процесс для задач, которые меняют игру, клиент, сервер, контент или -инфраструктуру. Он нужен, потому что анализ и реализация могут выполняться -разными моделями и в разных сессиях OpenCode. Общий контекст — только файлы -репозитория и Git, не память модели. +инфраструктуру. Оба этапа выполняет DeepSeek, но в разных проходах и, как +правило, в разных сессиях: сначала фиксируется план, затем по нему пишется код. +Разделение нужно, чтобы анализ не смешивался с правками, а объём не +расширялся по ходу. Общий контекст — только файлы репозитория и Git, не память +модели. ## 1. Роли @@ -18,7 +20,7 @@ - **Не изменяет игровой код, сцены, префабы, ScriptableObject и конфиги.** - Может обновлять только документацию и план. -### Kimi — реализация +### DeepSeek — реализация - Читает `AGENTS.md`, Skill и раздел задачи по её ID. - Проверяет актуальное состояние Git и кода; сверяет план с реальными @@ -33,10 +35,13 @@ - Оба этапа используют **тот же репозиторий** и те же `AGENTS.md`/Skill. Отдельные копии правил не создаются. -- Оператор выбирает модель в OpenCode. В текущей установке доступны Kimi-модели - (подтверждено каталогом моделей): `opencode/kimi-k3`, - `opencode/kimi-k2.7-code`, `opencode-go/kimi-k3.1-pro`, - `opencode-go/kimi-k2.7-code`. Конкретную модель выбирает пользователь. +- Оператор выбирает модель в OpenCode. В текущей установке доступны + DeepSeek-модели (подтверждено каталогом моделей): + `opencode-go/deepseek-v4.1-flash`, `opencode-go/deepseek-v4-pro`, + `deepseek/deepseek-flash`, `deepseek/deepseek-v4-pro`, + `opencode/deepseek-v4.1-flash-free`. Конкретную модель выбирает + пользователь; этапы анализа и реализации могут идти на разных моделях + DeepSeek. - Передача работы — через: 1. план задачи в `docs/Development/IMPLEMENTATION_PLAN.md`; 2. текущий Git-коммит/ветку/рабочую копию; @@ -46,7 +51,7 @@ - Если агенты работают в разных ветках/копиях, это должно быть явно указано в плане; не создавай ветки и не делай merge без разрешения. -## 3. Этап 1 — DeepSeek +## 3. Этап 1 — анализ 1. **Пожелание:** сформулируй ожидаемый результат, ограничения и критерии готовности. Не пересказывай задачу в плане длиннее, чем нужно. @@ -63,7 +68,7 @@ зависимости и ожидаемый результат должны быть явными. 8. **Стоп:** после анализа не переходи к коду. -## 4. Этап 2 — Kimi +## 4. Этап 2 — реализация 1. **Подготовка:** прочитай `AGENTS.md`, Skill, нужный раздел плана. 2. **Git:** `git status`, `git log -1`, при необходимости `git diff`; не diff --git a/docs/Development/CLIENT-SCENES-3D-ANIMATIONS.md b/docs/Development/CLIENT-SCENES-3D-ANIMATIONS.md new file mode 100644 index 0000000..8cd4946 --- /dev/null +++ b/docs/Development/CLIENT-SCENES-3D-ANIMATIONS.md @@ -0,0 +1,409 @@ +# Клиент: сцены, 3D и анимации — план доработок + +**Дата:** 2026-10-30 +**Анализ:** DeepSeek выполнен; код, сцены, префабы, SO и конфиги не менялись. +**Решения:** зафиксированы 2026-10-30 (раздел 8). +**Статус:** реализация не начата. +**Связанные документы:** `docs/client-visual-direction.md`, +`docs/3d-ux-backlog.md`, `docs/Development/ANIMATION-INVENTORY.md`, +`docs/Development/IMPLEMENTATION_PLAN.md` (TASK-2026-001…018). + +Документ — приоритизированный бэклог по трём направлениям: разбиение на сцены, +3D-наполнение каждой сцены, оптимизация анимаций. Он не заменяет +`IMPLEMENTATION_PLAN.md`: при старте работ пункты переносятся туда отдельными +TASK с полями «Реализация» и «Проверка» по `AGENT-WORKFLOW.md`. + +Первый блок (A1 + C1–C3) оформлен как `TASK-2026-019` в +`docs/Development/IMPLEMENTATION_PLAN.md`; код ещё не менялся. + +## 0. Границы + +- без новых пакетов: DOTween/UniTask запрещены (решение 2026-10-24); +- правила, сервер, протокол и YAML-контент не меняются; +- `CardMoveAnimator` остаётся единственным планировщиком перемещений карт; +- сцены и префабы меняются только в рамках конкретного пункта, не попутно; +- геймплей вручную проверяет пользователь. + +## 1. Текущее состояние (факты) + +### 1.1 Сцены + +| Сцена | В `.unity` | Создаётся кодом | 3D-содержимое | +|---|---|---|---| +| `Boot` | камера, свет, Global Volume | контроллера и UI нет; `VanguardApp` из `RuntimeInitializeOnLoadMethod` | нет | +| `Login` | контроллер, камера, свет | Canvas + `LoginView`, `SceneEnvironment` | пол, 14 кубов (колонны, неон, задник, стол, 4 карты, 2 панели), 2 point light | +| `Lobby` | контроллер, камера, свет | Canvas + `MetaView`/очередь, `SceneEnvironment` | пол, 2 колонны, 2 стола, стена, световая полоса | +| `DeckBuilder` | контроллер, камера, свет | Canvas + `DeckBuilderView`, `SceneEnvironment` | пол, 3 куба | +| `Battle` | контроллер, камера, свет | Canvas + `BattleView`, `SceneEnvironment`, `GameBoard3D` | 4 фоновых куба + процедурное поле | +| `GameBoard3D` | камера/свет из конфига, `PlaymatSandbox` | песочница поля без сервера | поле целиком | + +Переходы: `SceneFlow.LoadAsync` → `LoadSceneAsync(Single)`, ожидание `isDone`, +без fade, прогресса и `allowSceneActivation` (`SceneFlow.cs:63-80`). +`GameBoard3D.unity` отсутствует в Build Settings; константа `PlaymatSandbox` +метода загрузки не имеет. + +### 1.2 3D и ассеты + +- Окружение одно на все сцены: `SceneEnvironment.Build(SceneTheme)` + + `switch` по теме (`SceneEnvironment.cs:22`, декорации `:110-159`). +- Камера и свет заданы дважды: `SceneBuilder` пишет их в `.unity`, а + `SceneEnvironment` перезаписывает в рантайме другими значениями + (FOV 60 в сцене против 46/52 в рантайме; интенсивность света 0.9 против 1.15). +- Каждый примитив фона получает собственный `Material` в рантайме + (`SceneEnvironment.cs:177-202`); ассетов `.mat` в проекте нет. +- Ассеты: 246 `.jpg` арта карт в `Resources/CardArt`, 0 `.fbx`, 0 `.mat`, + 0 `.shader`, 0 `.anim`, 0 `.controller`, 4 prefab-оверлея. +- Addressables `4.1.1` установлен, но `Addressables.*` в коде не используется. +- `AudioListener` есть только в `Boot` (в остальных сценах — 0). +- Global Volume есть только в `Boot`; в сценах-экранах нет + `UniversalAdditionalCameraData` — нужна визуальная проверка пост-обработки. +- `LODGroup`, baked-свет и occlusion отсутствуют; материалы и меши стола + создаются процедурно (`BoardMaterials`, `BoardMeshLibrary`). + +### 1.3 Анимации + +Уже сделано и переделывать не нужно: пул `Card3D`, `sharedMaterial` + +`MaterialPropertyBlock`, кеши мешей и текстур, единый `Tick` из +`GameBoard3D.Update`, FPS-независимая интерполяция (экспонента и smoothstep +на `Time.unscaledDeltaTime`), отсутствие `Update` на каждой карте. + +Проблемы, подтверждённые кодом: + +- `UpdateBoostArrows` создаёт `new List<(Vector3, Vector3)>` каждый кадр, а + `BattleCard` — `Players.FirstOrDefault(lambda)` 2–4 раза за кадр; якоря + стрелок ищутся линейным перебором `_cards.Values`. +- Стрелки обновляются дважды за кадр: `_arrowDirector.Tick` и + `UpdateAttackArrowPoints` (`GameBoard3D.Update`), плюс + `AttackArrowView.ApplyColor` создаёт массив `new[] { _shaft, _head }`. +- `UpdateHandView` пересчитывает позы обеих рук каждый кадр и вызывается + повторно из `Render`, hover и колбэков (`GameBoard3D.cs:183, 511, 607, + 732, 1163, 2230, 2372`) — в одном кадре возможен двойной `SetPose`. +- Строки в кадре: таймер и банк (`BattleView.UpdateClock`), `Describe()` у + `InputLockService` при активном локе, `LocalizedText` для каждого лейбла. +- Hover/drag используют `Physics.RaycastAll` с сортировкой через лямбду и + `GetComponentInParent` на каждый хит. +- `AnimationConfig` содержит ~15 полей, которые код не читает + (`ANIMATION-INVENTORY.md`, раздел I): риск неверной настройки. + +## 2. Сводка приоритетов + +| ID | Что | Приоритет | Зачем | +|---|---|---|---| +| A1 | Загрузочная сцена и fade | P0 | убрать вспышку при смене сцены | +| A2 | Песочница в Build Settings | P0 | `RunPlaymat` и песочница в билде | +| A3 | Один владелец камеры/света | P0 | убрать рассинхрон `.unity` и рантайма | +| A4 | `SceneProfile` на сцену | P1 | настройка сцены без правки кода | +| A5 | AudioListener на персистентном объекте | P1 | звук во всех сценах | +| A6 | Пост-обработка в сценах-экранах | P1 | одинаковый look | +| A7 | Additive-слой общих сервисов | P2 | не пересоздавать Canvas | +| A8 | SceneSmoke для DeckBuilder/Battle | P2 | покрытие смоуком | +| B1 | Общие материалы вместо `new Material` | P0 | батчинг, редактируемость | +| B2 | Login: полноценная 3D-сцена | P0 | первый экран | +| B3 | Lobby и DeckBuilder: 3D-сцены | P1 | сейчас 6 и 3 куба | +| B4 | Battle: окружение вокруг поля | P1 | задник/свет без правки поля | +| B5 | Prefab-экраны UI | P1 | уйти от code-generated | +| B6 | Addressables для арта | P1 | разгрузить `Resources` | +| B7 | Light/reflection probes, baked-свет | P2 | качество света | +| B8 | Живое окружение | P2 | неон, частицы, скайбокс | +| C1 | Аллокации в стрелках | P0 | 0 B/кадр в бою | +| C2 | Один тик руки | P0 | убрать двойной `SetPose` | +| C3 | Строки не каждый кадр | P0 | GC и цена UI | +| C4 | Событийный `InputLock` | P1 | паузы по факту, не по таймеру | +| C5 | Raycast без аллокаций | P1 | hover/drag | +| C6 | Синхронизировать `AnimationConfig` | P1 | убрать мёртвые поля | +| C7 | Профилирование и бюджет кадра | P2 | 60 fps, метрики | +| C8 | Анимационный LOD | P2 | не тикать невидимое | + +## 3. A — Разбиение на сцены + +- [ ] **A1. P0 — Загрузочная сцена и плавный переход.** + Файлы: `Runtime/App/SceneFlow.cs`, новый `Runtime/UI/LoadingOverlay.cs`, + `Runtime/App/VanguardApp.cs`, `Scenes/Boot.unity`. + Сделать: `Boot` становится загрузочной сценой — прогревает `VanguardApp` и + сервисы, показывает прогресс `operation.progress` и fade 0.15–0.25 с, затем + грузит `Login`; для переходов между экранами используется тот же overlay. + Зачем: переход идёт без fade и прогресса — сцена меняется одним кадром, + экран загрузки не показывается. + Критерий: Login → Lobby → Battle без вспышки; прогресс виден на первом + входе в Lobby. + Зависит: нет. + +- [ ] **A2. P0 — Песочница поля в сборке.** + Файлы: `ProjectSettings/EditorBuildSettings.asset` (через + `Editor/SceneBuilder.cs`), `Runtime/App/SceneFlow.cs`. + Сделать: добавить `Assets/Vanguard/Scenes/GameBoard3D.unity` после `Battle`, + метод `LoadPlaymatAsync`, доступ только в dev-сборке. + Зачем: константа `PlaymatSandbox` не имеет загрузчика, сцены нет в билде. + Критерий: песочница открывается в standalone, `SceneSmoke.RunPlaymat` + проходит. + Зависит: A1. + +- [ ] **A3. P0 — Один владелец камеры и света.** + Файлы: `Editor/SceneBuilder.cs`, `Runtime/App/SceneEnvironment.cs`, + `*.unity`. + Сделать: выбрать одну модель — сцены хранят камеру/свет, а + `SceneEnvironment` их не перезаписывает, либо сцены пустые и всё создаёт + окружение. Зафиксировать в `client-visual-direction.md`. + Зачем: сейчас FOV 60 в сцене против 46/52 в рантайме, свет 0.9 против 1.15; + правки в сцене не действуют. + Критерий: значения в инспекторе совпадают с рантаймом; + `SceneBuilder.BuildAll` не создаёт расхождений. + Зависит: нет (A4 опирается на это решение). + +- [ ] **A4. P1 — `SceneProfile` (ScriptableObject) на сцену.** + Файлы: новый `Runtime/App/SceneProfile.cs` + 4 ассета, + `Runtime/App/SceneEnvironment.cs`, контроллеры сцен. + Сделать: вынести камеру, свет, палитру, туман, декор и пост в профиль; + `Build(SceneProfile)` вместо `switch(SceneTheme)`. + Зачем: настройка сцены сейчас требует правки кода; поля + `ScenePalette.Zone/EnemyZone` не читаются. + Критерий: внешний вид сцены меняется правкой ассета, без компиляции. + Зависит: A3. + +- [ ] **A5. P1 — AudioListener и аудио-корень.** + Файлы: `Runtime/App/VanguardApp.cs`, `*.unity`. + Сделать: один `AudioListener` на персистентном объекте, убрать дубль из + `Boot`-камеры. + Зачем: единственный listener в `Boot` уничтожается при Single-загрузке — + при добавлении звука его не будет слышно. + Критерий: ровно один `AudioListener` в любой сцене, звук слышен. + Зависит: нет. + +- [ ] **A6. P1 — Пост-обработка в сценах-экранах.** + Файлы: `*.unity`, `Assets/Settings/SampleSceneProfile.asset`. + Сделать: `UniversalAdditionalCameraData` и Volume (или общий профиль через + Global Settings) на всех экранах; сверить bloom/vignette/tonemapping. + Зачем: Global Volume есть только в `Boot`, в остальных сценах + пост-обработка, вероятно, не применяется. + Критерий: одинаковый look `Boot`/`Login`/`Lobby`/`DeckBuilder`/`Battle`. + Зависит: A3. + +- [ ] **A7. P2 — Additive-слой общих объектов.** + Сделать: Canvas оверлеев, EventSystem и аудио грузить один раз и не + пересоздавать при смене экрана. + Зачем: каждый экран заново строит Canvas через `UiKit.CreateCanvas`. + Критерий: Lobby ↔ DeckBuilder без пересоздания оверлеев и мигания. + Зависит: A1. + +- [ ] **A8. P2 — Расширить `SceneSmoke`.** + Файлы: `Editor/SceneSmoke.cs`. + Сделать: проверять `DeckBuilder` и `Battle` (контроллер, окружение, камера, + `AudioListener`), сверять FOV/интенсивность света с профилем. + Зачем: сейчас проверяются только `Login`/`Lobby`. + Критерий: смоук падает при потере окружения или listener в любой сцене. + Зависит: A3–A6. + +## 4. B — 3D в каждой сцене + +- [ ] **B1. P0 — Общие материалы вместо `new Material`.** + Файлы: `Runtime/App/SceneEnvironment.cs`, новый `Assets/Vanguard/Art/…`. + Сделать: 3–5 общих URP-материалов ассетами; фон и декор используют их. + Зачем: сейчас каждый примитив получает инстанс материала в рантайме + (`:177-202`), это ломает батчинг и не редактируется в инспекторе. + Критерий: Frame Debugger без лишних `SetPass`, SRP Batcher активен. + Зависит: A4. + +- [ ] **B2. P0 — Login: полноценная 3D-сцена.** + Сделать: authored-стол, 4–6 карт, неоновые направляющие, задник; свет и + камера из профиля; геометрия — префабы, а не код. + Зачем: первый экран сейчас собран из 14 кубов в рантайме. + Критерий: визуально читаемый стол; в рантайме не создаётся статическая + геометрия. + Зависит: B1, A4, референс экрана от пользователя. + +- [ ] **B3. P1 — Lobby и DeckBuilder: 3D-сцены.** + Сделать: Lobby — зал с постаментом/витриной и световыми акцентами; + DeckBuilder — стол с матом, стопками и подсветкой зон. + Зачем: сейчас 6 и 3 куба, экраны визуально пустые. + Критерий: у каждой сцены своя композиция, а не общий `switch` с кубами. + Зависит: B1, A4, референс экрана от пользователя. + +- [ ] **B4. P1 — Battle: окружение вокруг поля.** + Сделать: задник, постамент, световые акценты вокруг `GameBoard3D`, не + вмешиваясь в геометрию поля и `PlaymatLayout`. + Зачем: сейчас 4 фоновых куба; поле выглядит висящим в пустоте. + Критерий: поле читается как стол в помещении; `GameBoard3DTests` без + изменений. + Зависит: B1, A4, референс боевого экрана от пользователя. + +- [ ] **B5. P1 — Prefab-экраны UI.** + Сделать: перенести code-generated панели в префабы (по одному экрану за + шаг), `UiKit` оставить фабрикой для мелких элементов. + Зачем: `client-visual-direction.md` фиксирует это как следующий шаг; + сейчас UI собирается кодом и не редактируется дизайнером. + Критерий: экран собирается из префаба, поведение и тесты не меняются. + Зависит: приёмка TASK-2026-003 (HUD), иначе конфликт правок. + +- [ ] **B6. P1 — Addressables для арта.** + Сделать: инициализировать Addressables-группы, перенести арт карт из + `Resources/CardArt` в адресуемые группы, грузить по `cardId` + (`CardImageService`), оставить процедурный fallback по клану. + Зачем: 246 `.jpg` лежат в `Resources` и грузятся целиком; пакет установлен, + но не используется. + Критерий: арт грузится адресуемо и выгружается, `Resources` не растёт; + контракт `BoardArt`/`CardArt` не меняется. + Зависит: нет; модели карт и стола остаются процедурными (решение 8.1). + +- [ ] **B7. P2 — Light/reflection probes и baked-свет.** + Сделать: запечь статику сцен, добавить probes для карт и стола. + Зачем: `m_LightingDataAsset` пустой, освещение только realtime. + Критерий: статичный свет не считается каждый кадр, карты получают + корректные отражения. + Зависит: B2–B4 (финальная геометрия). + +- [ ] **B8. P2 — Живое окружение.** + Сделать: анимация неона, лёгкие частицы, скайбокс/градиент, дыхание камеры + в Login/Lobby. + Зачем: окружение статично; при этом правила запрещают DOTween — только + существующий ручной тик. + Критерий: эффекты не блокируют ввод и не добавляют аллокаций в кадре. + Зависит: B2–B4, C1–C3. + +## 5. C — Анимации: плавность и стоимость кадра + +- [ ] **C1. P0 — Убрать покадровые аллокации в стрелках.** + Файлы: `Board3D/View/GameBoard3D.cs` (`UpdateBoostArrows`, + `UpdateAttackArrowPoints`, `BattleCard`), `Animation/ArrowDirector.cs`, + `View/AttackArrowView.cs`. + Сделать: кешировать список пар, заменить LINQ на индексированные циклы, + кешировать якоря на `Render`, обновлять геометрию один раз за кадр, убрать + `new[] { _shaft, _head }` в `ApplyColor`. + Зачем: `new List` каждый кадр, 2–4 LINQ-делегата, 4 `UpdateGeometry` на + стрелку за кадр. + Критерий: 0 B/кадр в бою без движения (Profiler), визуал не меняется. + Зависит: нет. + +- [ ] **C2. P0 — Один тик и один `SetPose` руки.** + Файлы: `Board3D/View/GameBoard3D.cs` (`UpdateHandView`), `Board3D/View/Card3D.cs`. + Сделать: dirty-флаг на изменение состава руки, камеры, hover и staged-списка; + убрать повторный вызов из `Render`/колбэков, если позы уже посчитаны. + Зачем: позы обеих рук пересчитываются каждый кадр, возможен двойной + `SetPose` в одном кадре. + Критерий: при неподвижной камере и руке расчёт не выполняется; hover, + добор, муллиган и reflow без регрессий. + Зависит: C1 (общий бюджет кадра). + +- [ ] **C3. P0 — Строки не каждый кадр.** + Файлы: `UI/BattleView.cs` (`UpdateClock`), `UI/Localization.cs` + (`LocalizedText`), `Battle/InputLockService.cs` (`Describe`). + Сделать: обновлять текст только при изменении значения; кешировать перевод + до смены языка; убрать `Describe`/LINQ из кадрового пути. + Зачем: таймер и банк пишутся каждый кадр, `LocalizedText` проходит + `Loc.Translate` на каждый лейбл, при локе — `string.Join`/LINQ. + Критерий: в покое нет аллокаций от UI; переключение RU/EN по-прежнему + мгновенное. + Зависит: нет. + +- [ ] **C4. P1 — Событийный `InputLock` вместо таймера.** + Файлы: `Board3D/Animation/CardMoveAnimator.cs`, + `Board3D/View/GameBoard3D.cs`, `Battle/InputLockService.cs`. + Сделать: снимать `board`-лок по завершении очереди и stagger, таймер + оставить только страховкой. + Зачем: лок держится `cardMoveDuration * 3 + tail` и не совпадает с фактом + окончания анимаций — лишние паузы или преждевременный ввод. + Критерий: ввод открывается сразу после последней карты; зависаний нет + (`CancelAll`, reconnect, отмена). + Зависит: C2. + +- [ ] **C5. P1 — Raycast без аллокаций.** + Файлы: `Board3D/View/GameBoard3D.cs` (`UpdateHandHover`, drag, targeting). + Сделать: `RaycastNonAlloc` с переиспользуемым буфером, выбор ближайшего без + `Array.Sort` с лямбдой, кеш `GetComponentInParent`. + Зачем: `RaycastAll` + сортировка + `GetComponentInParent` на каждый хит + каждый кадр при активном указателе. + Критерий: наведение и drag без аллокаций; выбор карты не изменился. + Зависит: C1. + +- [ ] **C6. P1 — Синхронизировать `AnimationConfig` с кодом.** + Файлы: `Battle/AnimationConfig.cs`, `Resources/AnimationConfig.asset`, + `docs/Development/ANIMATION-INVENTORY.md`. + Сделать: удалить или явно пометить поля, которые не читаются или читаются + не по назначению: `cardFlipDuration`, `callMoveDuration`, + `cameraRezoomDuration`, `arrowHideDuration`, `retireDuration`, + `attackDeclare` и паузы feedback — не читаются; `cardMoveDuration` — + только таймаут board-lock; `arrowFadeDuration` и `boostThickness` дублируют + `ArrowConfig`. Asset привести к фактическим значениям. + Зачем: ~15 мёртвых полей — источник неверной настройки. + Критерий: каждое поле читается кодом или удалено; тест на загрузку ассета. + Зависит: нет. + +- [ ] **C7. P2 — Профилирование и бюджет кадра.** + Сделать: маркеры Profiler в `CardMoveAnimator`, `GameBoard3D.Update`, + `UpdateHandView`; зафиксировать бюджет (60 fps, доска ≤ 1 мс на референсном + ПК); выровнять `shadowDistance` (40 в QualitySettings против 50 в URP asset); + MSAA оставить выключенным (решение 2026-10-30) и проверить края карт с TAA. + Зачем: сейчас метрик нет, настройки противоречат друг другу. + Критерий: отчёт «до/после» с числами, зафиксированный бюджет в документе. + Зависит: C1–C5. + +- [ ] **C8. P2 — Анимационный LOD.** + Сделать: не тикать карты вне экрана и стопки, обновлять их по dirty; + уменьшить частоту обновления рук противника и дальних стопок. + Зачем: `GameBoard3D.Update` тикает все карты в `_cards` каждый кадр. + Критерий: то же визуальное поведение, меньше работы в кадре при 40+ картах. + Зависит: C2, C7. + +## 6. Порядок работ + +1. **Старт (P0):** A1 → C1 → C2 → C3 → A2. Загрузочная `Boot` и чистый кадр + дают сразу видимый эффект и не конфликтуют с приёмкой HUD. +2. **3D-база (P0):** A3 → A4 → B1 → B6. +3. **Экраны (P1):** B2 → A5 → A6 → B3 → B4; B2–B4 стартуют после референсов. +4. **Анимации (P1):** C4 → C5 → C6. +5. **UI (P1):** B5 — после приёмки TASK-2026-003. +6. **Полировка (P2):** A7 → A8 → B7 → B8 → C7 → C8. + +Правило: этапы 1–2 не меняют компоновку HUD из TASK-2026-003, пока идёт его +ручная приёмка; в `BattleView` допускаются только точечные правки вне +разметки (например, `UpdateClock` в C3). + +## 7. Проверки + +- Клиентские EditMode в копии проекта без `Library` (команда в + `docs/SESSION-STATE.md`). +- `SceneSmoke.Run` и `SceneSmoke.RunPlaymat` после каждого блока сцен. +- `SceneBuilder.BuildAll` — сцены пересобираются без ошибок, `.meta` + контроллеров на месте. +- Standalone-сборка на копии: переходы, пост-обработка, звук, песочница. +- Profiler: Allocation Call Stacks (0 B/кадр в бою без движения), Frame + Debugger для батчинга. +- Регрессии: drag&drop, ride/call/move, добор, муллиган, trigger, урон, + hover руки. +- Ручная приёмка визуала и геймплея — пользователем. + +## 8. Решения и риски + +**Зафиксировано пользователем 2026-10-30:** + +1. **3D-арт.** Работаем на примитивах с нормальными URP-материалами; закупка + и генерация моделей в объём не входят. Модели карты/стола можно добавить + позже без смены контрактов `BoardArt`/`CardArt`. +2. **`Boot`.** Остаётся первой сценой и становится загрузочной: прогрев + сервисов, прогресс, fade, затем `Login` (входит в A1). +3. **Addressables.** Арт переводим сейчас, отдельным шагом (B6), не дожидаясь + prefab-экранов. +4. **MSAA.** Остаётся выключенным; края карт сглаживаются TAA. Пересматривать + только по результатам C7. +5. **Референсы.** Пользователь готовит мокапы для всех экранов: `Login`, + `Lobby`, `DeckBuilder` (включая экран колод), `Battle`. B2–B4 стартуют + после получения соответствующего референса. +6. **Первый блок.** A1 + C1–C3. + +**Риски:** + +- правки сцен ломают GUID-ссылки — сцены пересобираются `SceneBuilder`, + копировать `.meta` вместе со скриптами; +- перенос UI в префабы (B5) конфликтует с TASK-2026-003, пока HUD не принят; +- `new Material` в рантайме нельзя заменить на ассеты, пока не решён A3/A4 + (кто владеет материалами сцены); +- референсы для `Battle` могут потребовать правок HUD из TASK-2026-003 — + тогда это отдельная задача, а не попутная правка. + +## 9. Вне объёма + +- боевые правила, сервер, протокол, баланс, YAML-контент; +- HUD по мокапу (TASK-2026-003) и боевые анимации из + `docs/3d-ux-backlog.md` (cost overlay, attack resolution queue, + world-space Power/Shield); +- звук и настройки как отдельная система (кроме A5 — listener); +- лаунчер WPF и сборка релиза. diff --git a/docs/Development/IMPLEMENTATION_PLAN.md b/docs/Development/IMPLEMENTATION_PLAN.md index 036c382..289e763 100644 --- a/docs/Development/IMPLEMENTATION_PLAN.md +++ b/docs/Development/IMPLEMENTATION_PLAN.md @@ -9,12 +9,13 @@ 1. **DeepSeek** анализирует пожелание, код и зависимости, задаёт вопросы и создаёт/обновляет раздел задачи. Игровой код на этом этапе не меняется. -2. **Kimi** читает `AGENTS.md`, Skill и раздел задачи, сверяет план с текущим - кодом, реализует, проверяет и обновляет статусы. +2. **DeepSeek (реализация)** читает `AGENTS.md`, Skill и раздел задачи, сверяет + план с текущим кодом, реализует, проверяет и обновляет статусы. 3. Передача — только через файлы репозитория и Git. Память модели и чат не считаются общим контекстом. -4. Если план устарел или требования изменились, Kimi останавливает зависимую - часть, фиксирует причину и при необходимости возвращает задачу на анализ. +4. Если план устарел или требования изменились, реализация останавливает + зависимую часть, фиксирует причину и при необходимости возвращает задачу на + анализ. 5. Нельзя удалять чужие задачи, невыполненные пункты и историю статусов. Подробный процесс — `docs/Development/AGENT-WORKFLOW.md`. @@ -37,7 +38,8 @@ | ID | Статус | Название | Фаза | Проверка | |---|---|---|---|---| -| TASK-2026-001 | `[ ]` | Плавность карточных перемещений и корректные источники | DeepSeek: анализ готов; Kimi: ожидает | не выполнена | +| TASK-2026-001 | `[!]` | Плавность карточных перемещений и корректные источники | DeepSeek: анализ; реализация выполнена, ждёт ручной проверки C10 | EditMode 46/46; ручная — пользователь | +| TASK-2026-019 | `[ ]` | Загрузочная сцена и кадр без покадровых аллокаций | DeepSeek: план готов; реализация ожидает | не выполнена | Новые задачи добавляются в конец файла отдельными разделами. Не используй один раздел для нескольких независимых задач. @@ -51,7 +53,7 @@ - **Статус:** `[ ]` - **Создано:** YYYY-MM-DD - **Анализ:** DeepSeek -- **Реализация:** Kimi +- **Реализация:** DeepSeek - **Затронутые системы:** ... #### A. Цель и требования @@ -444,3 +446,1274 @@ `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:** — + + + + + + + + + + + + + + diff --git a/docs/HANDOFF.md b/docs/HANDOFF.md index 2e79cfd..3d5b497 100644 --- a/docs/HANDOFF.md +++ b/docs/HANDOFF.md @@ -1,11 +1,181 @@ # Передача сессии -Состояние проекта на **23 октября 2026**. Документ нужен, чтобы новая сессия +Состояние проекта на **28 октября 2026**. Документ нужен, чтобы новая сессия продолжила работу без повторного разбора истории. Краткая оперативная сводка — `docs/SESSION-STATE.md`; этот файл хранит подробную историю. --- +## 2026-10-30 — реализацию выполняет DeepSeek + +- Решение пользователя: этап реализации больше не закреплён за Kimi. Оба + этапа — анализ и реализация — выполняет DeepSeek, но раздельно: сначала + план, потом код по нему. +- Обновлены `AGENTS.md`, `docs/Development/AGENT-WORKFLOW.md`, + `docs/Development/IMPLEMENTATION_PLAN.md` (процесс и шаблон задачи), + `docs/SESSION-STATE.md`, `README.md` и Skill + (`.opencode/skills/vanguard-remaster/SKILL.md`, + `workflow-and-decisions.md`). +- Двухэтапность и все запреты сохранены: анализ не меняет код, реализация не + расширяет объём; `[x]` без проверки запрещён. +- Исторические записи о Kimi (TASK-2026-001, раздел 2026-10-24) не + переписывались. +- Код, сцены, префабы и конфиги не затрагивались. + +--- + +## 2026-10-28 — TASK-2026-018: карта из руки не двигается при выборе зоны + +- Для карты из руки зажатие ЛКМ больше не запускает обычный drag: + включается `_handTargeting`, карта остаётся в hand-раскладке и + hover-подъёме. +- Курсор двигается по зонам, `UpdateHandTargeting` только подсвечивает + валидную цель; цель берётся raycast-ом по карте/кругу, затем плоскостью. +- На release над VC/RC отправляется `CardDropped`; невалидный drop + оставляет карту в руке. +- Карты поля сохраняют обычный drag с движением. +- EditMode — **58/58**, standalone собран, клиент запущен. + +--- + +## 2026-10-28 — TASK-2026-015: drag из руки, смена через VC + +- `Card3D.EndDragAtCurrent` фиксирует карту в точке drop-а без возврата + в руку и сохраняет подъём/масштаб. +- Для карты из руки `FinishDrag` больше не вызывает `ReturnToOrigin` до + ответа сервера: карта ждёт снапшот и летит в VC/RC из точки drop-а. +- `ReturnDroppedCard` снимает hold и плавно возвращает карту, если drop не + стал интентом, сервер отказал или сработал guard-выбор. +- Карты поля сохраняют прежний путь (возврат к source до снапшота). +- Тест `Dropped_Hand_Card_Holds_At_Drop_Until_Returned`. +- Карта из руки при зажатии ЛКМ не двигается: включается `_handTargeting`, + карта остаётся в руке приподнятой, курсор выбирает зону; на release над + VC выполняется смена. Карты поля сохраняют обычный drag. +- EditMode — **58/58**, standalone собран, клиент запущен. + +--- + +## 2026-10-28 — TASK-2026-014: подъём карты в руке при наведении + +- Своя карта под курсором плавно поднимается камера-вверх на 10% своей + высоты (`handHoverLift = 0.1`); после ухода курсора плавно возвращается. +- Hover работает только для своей руки и отключается при UI, input-lock, + драге и анимациях раскладки/возврата/добора. +- Плавность — существующий `Card3D.SetPose`/`Tick`. +- Тест `Hand_Hover_Lifts_Card_By_Ten_Percent`; EditMode — **58/58**, + standalone собран, клиент запущен. + +--- + +## 2026-10-28 — TASK-2026-013: единый механизм раскладки руки + +- Раздельные staged-списки возврата/добора заменены единым + `_visualHandCards` + `_handLayoutOverride`. +- В визуальный список сразу входят оставшиеся карты руки и ожидающие + возврата; новые добранные добавляются после завершения дуги. +- После каждого возврата/добора список пересчитывается, поэтому остатки руки + всегда участвуют в сдвиге; повторный Render не сбрасывает прогресс. +- Новые карты при доборе скрыты и показываются по одной. +- Тест `Mulligan_Remaining_Cards_Reflow_After_Each_Return`. +- EditMode — **58/58**, standalone собран, клиент запущен. + +--- + +## 2026-10-28 — TASK-2026-011: ступенчатый reflow руки при муллигане + +- При возврате нескольких карт рука больше не перестраивается один раз под + финальное количество. +- `_stagedReturnCards` хранит старые карты руки в порядке слева-направо, + включая ожидающие возврата; новые карты в него не входят. +- После завершения каждой карты она убирается из staged-списка, оставшиеся + сдвигаются под новое количество; ожидающие возврата тоже перестраиваются. +- Ожидающая карта получает цель под колодой только в момент старта своей + анимации (`CardMoveAnimator.onStart` + `fromCurrent`). +- Тест проверяет: до первого ухода рука не сдвигается, после первого — + сдвигается; EditMode — **58/58**. + +--- + +## 2026-10-28 — TASK-2026-010: рука перестраивается при выходе карты + +- При любом выходе карты из руки оставшиеся карты получают новые позы в том + же `Render`, не дожидаясь окончания анимации уходящей карты. +- Перекомпоновка `Hand → Hand` не ставится в `CardMoveAnimator` + (`NeedsMove` → false), поэтому hand reflow идёт одновременно с movement. +- Добавлен тест `Hand_Reflows_While_A_Card_Leaves_From_Hand`; тест возврата + в колоду дополнительно проверяет сдвиг остальной руки. +- EditMode — **58/58**. Ручная проверка — за пользователем. + +--- + +## 2026-10-27 — TASK-2026-007: общий добор карты + +- Добор после муллигана приведён к виду добора в муллигане: карта стартует над + колодой **полного размера** (`cardScale`), рубашкой вверх в ориентации + будущей карты руки, летит в руку по дуге и переворачивается лицом вверх. +- `GameBoard3D.GetDrawSource` — общий старт для `AnimateHandDraw` и + `ResolveSourcePose`; новая карта руки больше не стартует в размере руки. +- Длительность обычного добора — `introDrawDuration = 0.6 с`, как в + муллигане. +- Тест проверяет позицию над колодой и `cardScale` у новой карты. +- EditMode — **58/58**, standalone собран, клиент запущен. Ручная проверка — + за пользователем. + +--- + +## 2026-10-27 — TASK-2026-006: возврат карт из руки в колоду + +- Добавлена анимация возврата: старая карта руки уходит по дуге **под низ + колоды**, во время движения переворачивается рубашкой вверх; следующая + карта стартует только после завершения предыдущей. +- Старт — **справа налево**; у противника зеркально с его стороны поля. +- `GameBoard3D.QueueHandReturnToDeck` получает `MulliganView` и + `CardReturnedToDeckView` из `BattleView`; старые карты определяются по + исчезновению из руки, вынимаются из `_cards`, анимируются и возвращаются + в пул после завершения. +- `CardMoveAnimator.Enqueue` получил необязательный `onCompleted`; возврат + использует `sequential: true`. Ожидающие карты удерживаются в руке через + `HoldAtSource`, поэтому не уезжают под колоду параллельно. +- Длительность возврата — общая с добором: `DrawDuration` + (`introDrawDuration = 0.6 с`). +- Тесты: `Hand_Return_To_Deck_Starts_Right_To_Left_And_Targets_Deck_Bottom`, + `CardMoveAnimator_Runs_Sequential_Requests_And_Completes_Each`. +- EditMode — **58/58**, standalone собран, клиент запущен. Ручная проверка — + за пользователем. + +--- + +## 2026-10-27 — TASK-2026-005: направление набора руки + +- Своя рука снова набирается **слева направо**: первая карта слева, новая + добавляется справа и остаётся поверх. +- Рука противника зеркальна с его стороны поля: на экране набирается + **справа налево**, новая карта появляется слева. +- `GetHandViewPose` / `GetOpponentHandViewPose` возвращены к зеркальным + формулам; тест `Hands_Fill_Mirrored_And_Newest_Is_On_Top` обновлён под + новое направление. +- EditMode — **51/51**, standalone собран, клиент запущен. Ручная проверка + порядка набора — за пользователем. + +--- + +## 2026-10-27 — TASK-2026-004: удаление боевых баннеров + +- Удалён центральный feedback-блок `BattleView`: текст событий + (`АТАКА`, `GUARD`, `DRIVE`, `TRIGGER`, `HIT/BLOCK`, `DAMAGE`, `HEAL`, + `RETIRE`, `INTERCEPT`, `PERFECT GUARD`), полноэкранная вспышка, + кнопка «Пропустить анимации», очередь `FeedbackStep` и `InputLock("feedback")`. +- Удалены мёртвые `_turnBanner`/`_turnBannerText`. +- Журнал событий, `Describe`, trigger-последовательность, верхний + `ActionPrompt` и `StatusToast` не тронуты. +- Паузы `eventPause`/`post*Pause` больше не применяются; темп держат + `CardMoveAnimator` и trigger-последовательность. Поля `AnimationConfig` + пока оставлены неиспользуемыми, чтобы не менять контракт ассета. +- EditMode — **51/51**. Ручная проверка боевого темпа — за пользователем. +- Инвентаризация анимаций: `docs/Development/ANIMATION-INVENTORY.md`. + +--- + ## 2026-10-24 — TASK-2026-001: плавность карточных перемещений - Реализована стабильная идентичность анонимных карт (`InstanceId == 0`) в @@ -36,6 +206,116 @@ --- +## 2026-10-25 — TASK-2026-003: боевой HUD по мокапу + +- **HUD:** убрана постоянная правая панель действий и `PhasePanel`; + подтверждения перенесены в нижний правый `ConfirmationBar`. Верхний HUD + показывает `Turn`, текстовую фазовую шкалу `Standby › Main › Battle › End`, + таймер и банк отдельно. Debug-панель (журнал) скрыта по умолчанию, + открывается кнопкой «Показать/скрыть панель». +- **Панели игроков:** новый `PlayerStatsPanelView` — `Hand/Deck/Damage/ + Shield/Trigger` и кнопки `Drop/Soul`. `Deck` неинтерактивен; `Guardian` + в бар не входит. `UpdateZoneStrips` переведён на новый view. +- **Поле:** `BoardSlotView` получил тёмную пластину под рамкой; над зоной + урона добавлен счётчик `x/6` (у противника зеркально к центру). + `Card3D` получил рамку лица; статы вынесены под карту, крит показывается + только при `>1`, вместо `P/C` — символы `▲/◆`. +- **Триггеры:** `BattleView` всегда запускает trigger-зум на `DriveCheckedView` + и damage-триггере; карты trigger-зоны скрыты до `ZoomCompleted`, затем + показываются. При отсутствии триггера в зоне пишется «БЕЗ ТРИГГЕРА». + Подписи распределения: `+5000`, `Крит +1`, `Поднять юнита`, `Взять карту`, + `Лечить`. +- **Дополнительно:** карты урона укладываются сверху вниз (у противника + зеркально) с шагом 30% от ширины карты, новая карта приподнята по Y и + рисуется поверх; trigger-карта повёрнута на 90° против часовой стрелки; + на карте показываются имя и бейдж триггера; добавлен центральный баннер + хода; no-trigger damage check определяется по `DamageTakenView` с + подавлением повторного зума после trigger-карты. +- **Правки по замечаниям:** имена карт на поле убраны; панель действий + сжата до автоподстраиваемого столбца кнопок справа снизу; рука + центрируется по экрану (`handViewportX = 0.5`); подсказка муллигана + перенесена в верхний `ActionPrompt`, при выборе карт появляется кнопка + «Заменить N карт»; центральный баннер хода убран; левый нижний бар — + `Колода, Душа, Рука, Сброс`, без `Щит/Триг`, `Душа/Сброс` кликабельны. +- **Триггеры:** фокус камеры всегда первый; после фокуса карта + раскрывается анимацией, затем пауза 0,75 с, затем резолв. Выбор цели + открывается после паузы. Сначала `+5000` на RC/VC, затем эффект: + `Critical` — RC/VC, `Stand` — только повёрнутый RC, `Draw` — + автоматически, `Heal` — выбор карты урона при условиях. Подсказки — в + верхнем информационном блоке. Нетриггерная карта пропускается. +- **Урон:** переворот face-up/face-down идёт вокруг длинной стороны. +- **Tejas:** атака заднего ряда по колонке `V↔1`, `3↔2`, `4↔0`. + Сервер пересобран и задеплоен на `ssh home`, контейнер `server` + перезапущен. +- **Основная фаза:** в панели подтверждений только кнопка «Боевая фаза»; + «Закончить ход» и список всех действий скрыты, способности — из меню + карты. +- **Рамки карт:** тонкая чёрная рамка по краю карты; подсветка выбора + ужата до размера карты и не перекрывает соседние карты. В руке рамки + скрыты, карты лежат широким веером: своя рука заполняется слева направо, + новая карта добавляется правее и перекрывает предыдущую; ширина веера + сохраняется до 10 карт и плавно уменьшается начиная с 11-й. Рука + противника — зеркальный камеро-зависимый веер ниже верхней панели: + новая карта слева, наклон и глубина зеркальны. Позы обеих рук + пересчитываются каждый кадр, поэтому добор не улетает к устаревшей цели; + порядок закреплён тестом `Hands_Fill_Mirrored_And_Newest_Is_On_Top`. +- **Стартовый Авангард:** до конца муллиганов закрыт (`MatchProjection` + передаёт реальный `FaceUp`, без принудительного открытия); после обоих + муллиганов открывается анимацией 0,8 с вокруг длинной оси слева направо + (у противника зеркально), затем начинается первая фаза. На время + переворота клиент блокирует ввод. +- **Старт боя:** камера над стороной игрока лицом к столу, 1,5 с → + дуга вниз на основную позицию → пауза 1,5 с → одновременный добор своей + и чужой руки → после всех доборов появляется муллиган. Ввод заблокирован + до конца интро. +- **Взятие карт:** карта создаётся ровно над колодой рубашкой вверх и + летит в руку по дуге с переворотом лицом вверх. Любой добор последовательный: + следующая карта стартует только после завершения предыдущей. В стартовой + раздаче своя и чужая руки идут парами, внутри каждой пары — по одной. +- **Проверки:** EditMode 50/50; серверные `Vanguard.Rules` 155/155 на + `ssh home`; standalone Windows собран, клиент запущен. Ручная приёмка — + за пользователем. +- **Частично:** bloom/свет, HUD-анимации; эвристика no-trigger damage check + требует ручной проверки. + +--- + +## 2026-10-25 — TASK-2026-002: боевой UX и правила + +- **D1–D2:** в `Card3D` добавлено состояние hold-at-source; пока запрос ждёт в + очереди `CardMoveAnimator`, карта не плавно двигается к target. + `GameBoard3D.Render` вызывает `HoldAtSource` после `PlaceImmediate` для + перемещаемых карт; отмена/очистка снимает hold. +- **D3:** `GameBoard3D.FinishDrag` больше не вызывает `DropAt` для + server-authoritative перемещений; карта возвращается в source и ждёт + снапшота, после которого идёт одна дуга. +- **D4:** добавлены EditMode-тесты: queued-карта остаётся в source до старта + arc, отмена queued-запроса возвращает нормальное движение, переключение + видимости руки. +- **D5:** в `Vanguard.Rules` добавлена `SameColumnAcrossPlayers` (3↔2, 4↔0, + V↔1) и используется в legal intents/validation для атаки заднего ряда. + Тесты Tejas обновлены: 3→2, 4→0, негативный тест на противоположную колонку. +- **D6:** Perfect Guard переделан в стадийную способность: `Guard` размещает + Sentinel без оплаты; в `TriggerFromCard` для Sentinel в `OnGuard` добавляется + синтетическая опциональная способность с оплатой сбросом карты того же + клана. Принятие способности открывает выбор из руки, отказ оставляет щит 0. + `GuardCommand` больше не генерирует варианты с `DiscardPrintingId`; + клиентская discard-подсказка перед размещением убрана. +- **D7:** в `AnimationConfig` добавлены `eventPause`, `postDrivePause`, + `postTriggerPause`, `postDamagePause` (по умолчанию 1.5 с); увеличены + `moveDuration` (0.8), `importantMoveDuration` (1.3), stagger, camera durations + и `triggerRevealHold` (1.5). `BattleView` ставит паузу после боевых событий. +- **D8:** `TriggerCheckSequence` получил событие `FocusChanged`; `BattleView` + подписывается и скрывает руку на trigger-зуме, восстанавливая при возврате + камеры. `GameBoard3D.SetHandVisible` управляет видимостью своей руки. +- **Проверки:** клиентские EditMode в копии без `Library` — **50/50 passed**; + компиляция чистая. Серверные тесты (`Vanguard.Rules`) не запускались на + рабочем ПК; проверка только на `ssh home`/Docker. +- **Ручная проверка** draw/call из руки и души/Tejas/Perfect Guard/триггер-камеры + остаётся за пользователем. + +--- + ## 2026-10-23 — защита и оплата способностей - Панель «Фазы» переименована в «Действия»; защита перенесена туда. diff --git a/docs/SESSION-STATE.md b/docs/SESSION-STATE.md index 7a7a18a..e5a7583 100644 --- a/docs/SESSION-STATE.md +++ b/docs/SESSION-STATE.md @@ -1,6 +1,6 @@ # Vanguard ReMaster — состояние сессии -**Дата:** 2026-10-24 +**Дата:** 2026-10-27 **HEAD:** см. `git log -1` **Коротко:** сервер готов, клиент — рабочий каркас с 3D-сценами. @@ -12,8 +12,8 @@ пользователем (или его свободный ответ). Не угадывать. - Полный свод правил — `AGENTS.md`. - Новые изменения: анализ DeepSeek → план в - `docs/Development/IMPLEMENTATION_PLAN.md` → реализация Kimi. Код на этапе - анализа не меняется; процесс — `docs/Development/AGENT-WORKFLOW.md`. + `docs/Development/IMPLEMENTATION_PLAN.md` → реализация. Обе стадии выполняет + DeepSeek, раздельно по проходам. Код на этапе анализа не меняется; процесс — `docs/Development/AGENT-WORKFLOW.md`. ## Сервер @@ -359,17 +359,83 @@ client/Assets/Plugins/Vanguard.Shared + MessagePack 1. **TASK-2026-001 — ручная проверка пользователем** (C10): draw, call, ride, move/swap, guard, trigger, drive/damage, retire, search, открытие скрытого урона, рука противника, повторные клики UI во время/после анимации. - Автотесты 46/46; код изменён в `GameBoard3D`, `CardMoveAnimator`, + Автотесты 47/47; код изменён в `GameBoard3D`, `CardMoveAnimator`, `BattleView`, `AnimationConfig.asset` + тесты. -2. 3D UX: P0 (движение, ориентации, камера/триггер) и базовый P1 (стрелки, +2. **TASK-2026-002 — реализован D1–D8, остались серверные проверки и + ручная приёмка.** Реализованы: hold-at-source для queued-карт, + drag-and-drop без локального `DropAt`, Tejas-колонка через границу + игроков, стадийный Perfect Guard (отдельная способность Sentinel после + размещения), боевые паузы 1.5 с, скрытие руки на trigger-зуме. + Клиентские EditMode-тесты 50/50. Серверные тесты (`Vanguard.Rules`) + — только на `ssh home`; ручная проверка — пользователь. +3. **TASK-2026-003 — боевой HUD по мокапу: реализовано, требуется ручная + приёмка.** Готово: правая панель действий убрана, подтверждения в правом + нижнем углу, debug-панель по кнопке, верхний HUD с Turn/фазами/таймером/ + банком, центральный баннер хода, компактные панели игроков, `Drop/Soul` + из бара, `Deck` неинтерактивен, счётчик `x/6` над уроном, пластины + слотов, рамка/имя/бейдж триггера карты, статы под картой (`▲`/`◆`, крит + только >1), конкретные подписи триггеров (`+5000`, `Крит +1`, + `Поднять юнита`, `Взять карту`, `Лечить`), trigger-зум с плашкой + «Без триггера». Карты урона идут сверху вниз с перекрытием 30%, новая + поверх старой; trigger-карта повёрнута на 90° против часовой. Имена карт + на поле убраны; панель действий — компактные кнопки справа снизу; рука + центрируется по экрану; подсказка муллигана — в верхней панели, при + выборе карт появляется «Заменить N карт»; центральный баннер убран; + левый бар — `Колода, Душа, Рука, Сброс`, `Душа/Сброс` кликабельны. + EditMode 50/50, серверные правила 155/155, standalone собран, клиент + запущен. Триггер: фокус камеры → анимация раскрытия карты → пауза + 0,75 с → резолв; затем выбор `+5000` и эффекта (`Critical` — RC/VC, + `Stand` — только повёрнутый RC, `Draw` — авто, `Heal` — карта урона); + подсказки в верхнем блоке. Урон переворачивается вокруг длинной стороны. + Tejas: колонка `V↔1/3↔2/4↔0`, сервер пересобран и задеплоен на + `ssh home`. В Основной фазе — только кнопка «Боевая фаза». Рамка карты — + тонкая чёрная по краю, в руке рамка скрыта; карты лежат широким веером, + своя рука заполняется слева направо, новая карта добавляется правее и + остаётся поверх предыдущей, ширина сохраняется до 10 карт и плавно + уменьшается начиная с 11-й. Рука противника — зеркальный камеро-зависимый + веер ниже верхней панели: новая карта слева, наклон и глубина зеркальны; + позы обеих рук обновляются каждый кадр, добор не летит к устаревшей цели. + Это закреплено тестом `Hands_Fill_Mirrored_And_Newest_Is_On_Top` (58/58). + Возврат карт в колоду: старая карта уходит из руки по дуге под низ колоды + с переворотом рубашкой вверх; следующая стартует только после завершения + предыдущей. Рука идёт ступенчатым reflow: после каждого ухода оставшиеся + карты, включая ожидающие возврата, сдвигаются под новое количество. + Длительность как у добора (`DrawDuration` / `introDrawDuration = 0.6`), + у противника зеркально. Стартовый + Авангард закрыт до конца муллиганов (`MatchProjection` передаёт реальный + `FaceUp`), затем переворачивается вокруг длинной оси слева направо + (у противника зеркально). + Старт боя: камера над стороной игрока лицом к столу, 1,5 с → дуга вниз + на основную позицию → пауза 1,5 с → одновременный добор обеих рук → + только потом муллиган. Добор общий: карта стартует ровно над колодой + полного размера (`cardScale`), рубашкой вверх, летит в руку с переворотом + лицом вверх; длительность как в муллигане — `introDrawDuration = 0.6 с`. + Любой добор последовательный — следующая карта только после предыдущей. + Частично: bloom/свет + и HUD-анимации. 2026-10-27: боевые feedback-баннеры, вспышки и кнопка + «Пропустить анимации» удалены; журнал событий и trigger-подсказки + оставлены. Рука перестраивается под новое количество одновременно с + перемещением уходящей карты (любой выход из руки). При доборе новые + карты показываются по одной, а рука расширяется после каждой. Раскладка + руки идёт через единый `_visualHandCards`: остатки, возвращаемые и уже + добранные карты участвуют в сдвиге после каждого изменения. Hover карты + в руке плавно поднимает её на 10% высоты и возвращает после ухода курсора. + Карта из руки при зажатии ЛКМ не двигается и остаётся в руке + приподнятой; курсор наводится на зону, при отпускании над VC выполняется + смена. Цель определяется raycast-ом по VC-карте/кругу, затем плоскостью. + Невалидный drop оставляет карту в руке. Валидные для смены карты + подсвечиваются в руке; по логу это были TD02/007EN и BT01/015EN. + Карты поля сохраняют обычный drag. EditMode 58/58. + Детали — `docs/Development/IMPLEMENTATION_PLAN.md`. +4. 3D UX: P0 (движение, ориентации, камера/триггер) и базовый P1 (стрелки, multi-target Counter Blast) готовы. Осталось: `AttackResolutionQueue`, multi-target Soul Blast/прочие стоимости, отдельный `CostPaymentOverlay`, world-space Power/Shield (верхний strip принят как HUD), world-space dash shader; полировка позиций на конкретном мониторе. -3. Декбилдер: drag&drop, счётчики лимитов, live-валидация, пресеты. -4. 3D-наполнение: модели карт/стола, prefab-экраны сцен, анимации retire/ghost. -5. Звук и настройки, DOTween. -6. Лаунчер WPF. +4. Декбилдер: drag&drop, счётчики лимитов, live-валидация, пресеты. +5. 3D-наполнение: модели карт/стола, prefab-экраны сцен, анимации retire/ghost. +6. Звук и настройки, DOTween. +7. Лаунчер WPF. Тренировка с ботом и боевой UI с артами/анимациями/локализацией уже есть. diff --git a/opencode.jsonc b/opencode.jsonc new file mode 100644 index 0000000..a26eb35 --- /dev/null +++ b/opencode.jsonc @@ -0,0 +1,25 @@ +{ + "$schema": "https://opencode.ai/config.json", + + // MCP for Unity (CoplayDev): мост поднимается внутри редактора Unity + // (Window → MCP for Unity). Сервер доступен, только пока Unity открыт. + "mcp": { + "servers": { + "unity": { + "type": "remote", + "url": "http://localhost:8080/mcp", + }, + }, + }, + + // Каталоги, которые Unity генерирует сама: не индексировать. + "watcher": { + "ignore": [ + "client/Library/**", + "client/Temp/**", + "client/Logs/**", + "client/obj/**", + "client/Builds/**", + ], + }, +}