125 KiB
Implementation Plan
Активный журнал задач и планов изменений. Постоянные правила проекта хранятся
в Skill vanguard-remaster; история старых блоков — в IMPLEMENTATION_CHECKLIST.md,
DEVELOPMENT_CHECKLIST.md и docs/HANDOFF.md. Этот файл не заменяет их и не
является источником игровых правил.
Процесс
- DeepSeek анализирует пожелание, код и зависимости, задаёт вопросы и создаёт/обновляет раздел задачи. Игровой код на этом этапе не меняется.
- DeepSeek (реализация) читает
AGENTS.md, Skill и раздел задачи, сверяет план с текущим кодом, реализует, проверяет и обновляет статусы. - Передача — только через файлы репозитория и Git. Память модели и чат не считаются общим контекстом.
- Если план устарел или требования изменились, реализация останавливает зависимую часть, фиксирует причину и при необходимости возвращает задачу на анализ.
- Нельзя удалять чужие задачи, невыполненные пункты и историю статусов.
Подробный процесс — docs/Development/AGENT-WORKFLOW.md.
Статусы
[ ]— не выполнено;[x]— выполнено и подтверждено доступными проверками;[!]— заблокировано или ждёт решения;[-]— больше не требуется после согласованного изменения плана.
Для каждого пункта храни отдельно:
- Реализация: выполнена / не выполнена / не требуется.
- Проверка: выполнена (что именно) / не выполнена (почему).
[x] нельзя ставить, если реализация есть, а проверка не запускалась.
Журнал задач
| ID | Статус | Название | Фаза | Проверка |
|---|---|---|---|---|
| TASK-2026-001 | [!] |
Плавность карточных перемещений и корректные источники | DeepSeek: анализ; реализация выполнена, ждёт ручной проверки C10 | EditMode 46/46; ручная — пользователь |
| TASK-2026-019 | [ ] |
Загрузочная сцена и кадр без покадровых аллокаций | DeepSeek: план готов; реализация ожидает | не выполнена |
| TASK-2026-020 | [x] |
Парсер карт и артов с официального сайта | DeepSeek: анализ и реализация | идемпотентность 0 diff; validate не запускался |
Новые задачи добавляются в конец файла отдельными разделами. Не используй один раздел для нескольких независимых задач.
Шаблон задачи (не является активной задачей)
TASK-YYYY-NNN — краткое название
- Статус:
[ ] - Создано: YYYY-MM-DD
- Анализ: DeepSeek
- Реализация: DeepSeek
- Затронутые системы: ...
A. Цель и требования
- Что просит пользователь.
- Ожидаемое поведение.
- Обязательные правила Skill.
- Ограничения, которые нельзя нарушать.
- Критерии готовности.
B. Текущее состояние
- Как работает сейчас.
- Какие системы и файлы проверены.
- Существующие механики, которые нужно сохранить.
- Подтверждённые факты и открытые вопросы.
C. Чек-лист изменений
Каждый пункт:
[ ]MODIFY/CREATE/DELETE/TEST/VERIFY- путь к файлу (если известен);
- класс/метод (если существует);
- конкретная логика;
- причина изменения;
- ожидаемый результат;
- зависимости от других пунктов;
- Реализация: не выполнена;
- Проверка: не выполнена.
Пример формата:
- [ ] MODIFY `client/Assets/.../BattleView.cs` — `BattleView.SomeMethod`
Логика: ...
Причина: ...
Результат: ...
Зависит от: ...
Реализация: не выполнена
Проверка: не выполнена
D. Последовательность выполнения
Нумерованный порядок с обязательными зависимостями. Разделяй работу по системам на небольшие шаги.
E. Проверки
- Какие тесты/команды доступны и подтверждены проектом.
- Какие механики проверить на регрессию.
- Критерии готовности.
- Что остаётся на ручную проверку пользователя.
F. Риски и неизвестные вопросы
- Противоречия и побочные эффекты.
- Вопросы, требующие ответа пользователя.
- Что нельзя реализовывать до ответа.
G. Результат
- Реализовано: ...
- Проверки: ...
- Не завершено/заблокировано: ...
- Skill обновлён: да/нет, почему.
- Git: commit hash, изменённые файлы.
TASK-2026-001 — Плавность карточных перемещений и корректные источники
- Статус:
[!] - Создано: 2026-10-24
- Анализ: DeepSeek выполнен
- Реализация: Kimi выполнена
- Проверка: автотесты 46/46; ручная C10 — пользователь
- Затронутые системы:
GameBoard3D,Card3D,CardMoveAnimator,BattleView,AnimationConfig, EditMode-тесты.
A. Цель и требования
Пользователь сообщил: анимации движения карт «отрабатывают рвано», запускаются ненужные анимации, карты двигаются не из нужных позиций. Нужно исправить именно презентационный слой, не меняя правила игры.
Ожидаемое поведение:
- анимация запускается только при реальном переходе карты между зонами/слотами или при изменении позы, а не при каждом UI-рендере;
- источник анимации — фактическая старая зона/поза карты (рука, поле, Trigger, Damage, Drop, Soul, Guardian, Deck), а не всегда колода;
- рубашка/лицо источника учитываются: карта из колоды начинает движение рубашкой вверх, а не лицом;
- повторный снапшот или локальный
Renderне создаёт второй запрос на ту же карту и не откатывает её в середину пути; - скрытые карты (рука противника, закрытый урон) не пересоздаются и не летят
из колоды на каждом
Render; - существующие правила, серверные контракты,
InputLockService, пул карт и публичные визуальные эффекты не ломаются.
Ограничения:
- не менять
GameEngine, правила, сервер, протокол и YAML-контент; - не добавлять DOTween/UniTask/новые пакеты;
- не менять сцены/префабы без отдельного решения;
- не переписывать
CardMoveAnimatorна другой animation stack; - геймплей вручную проверяет пользователь; автотесты показывают только техническую корректность.
B. Текущее состояние и найденные причины
Проверены:
client/Assets/Vanguard/Runtime/Board3D/View/GameBoard3D.cs;client/Assets/Vanguard/Runtime/Board3D/Animation/CardMoveAnimator.cs;client/Assets/Vanguard/Runtime/Board3D/View/Card3D.cs;client/Assets/Vanguard/Runtime/Board3D/Data/BoardTypes.cs;client/Assets/Vanguard/Runtime/Board3D/Logic/PlaymatLayout.cs;client/Assets/Vanguard/Runtime/UI/BattleView.cs;client/Assets/Vanguard/Runtime/Battle/AnimationConfig.cs;client/Assets/Vanguard/Resources/AnimationConfig.asset;client/Assets/Vanguard/Tests/Editor/GameBoard3DTests.cs.
Факты:
- Скрытые карты с
InstanceId = 0пересоздаются. ВGameBoard3D.AddOpponentHandSpecs(OH:player:i) иGameBoard3D.AddDamageSpecs(D:player:i) для скрытых картBoardCardRef.InstanceIdравен0. ВGameBoard3D.Renderраспределение старых карт идёт только черезpreviousByInstanceи условиеspec.Ref.InstanceId != 0. Поэтому при каждомRenderтакие карты арендуются заново, старые освобождаются, а новые получаютmoved = trueи анимацию изSourcePose. BattleView.Render()вызывается не только на снапшот. Локальные действия (выбор guard, меню карты, подтверждение, статус) тоже вызываютBattleView.Render→GameBoard3D.Render. В сочетании с п.1 это даёт «ненужные анимации» на каждый клик.SourcePoseвсегда использует колоду. МетодGameBoard3D.SourcePoseвозвращает_layout.DeckPosition(localSeat)и базовый yaw без состояния лица. Новая карта, пришедшая из Soul, Drop, Guardian, Trigger или Damage, начинает полёт из колоды; карта из колоды начинает путь лицом вверх, хотя должна быть рубашкой вверх.- У Damage ключ по индексу, а не по личности.
D:player:iостаётся тем же при смене открытой карты или при открытии закрытой. Текущий branchsameInstanceосвобождает старую карту и создаёт новую сSourcePose(колода), хотя это может быть переворот/смена карты на месте. - Нет coalescing в
CardMoveAnimator.Enqueueне отменяет старый queued/active request той жеCard3D. Если карта успевает сменить зону до старта предыдущего запроса, второй запрос может стартовать из устаревшей позиции; активная запись при этом может остаться в_active. Card3D.SetPoseне является точкой начала движения. Для существующей картыRenderвызываетSetPose(меняет target), затемEnqueue; для новой картыPlaceImmediate+SetPose. При повторномRendertarget может быть обновлён до/после старта, а старый arc не всегда согласован с новым request.- Конфиг анимаций не синхронизирован с кодом. В
AnimationConfig.assetнет новых полейmoveDuration,importantMoveDuration,moveDelayBetweenCards,arcHeight, camera/arrow полей, хотя они объявлены вAnimationConfig.cs. Нужно проверить фактические значения после импорта Unity; нельзя считать asset настроенным, пока поля не сериализованы. UpdateHandViewкаждый кадр делаетSetPoseдля руки. Это может перетирать target во время движения карты в руку и мешать стабильности arc; нужно проверить и, если подтверждено, не менять target у карты, которая уже летит, либо синхронизировать target с move request.- Пул карт и
InputLockуже имеют защиту (CancelCard, таймаут,CancelAll); её нельзя удалять. Исправления должны сохранить отсутствие вечногоcard-move-лока.
Что сохранить:
GameBoard3Dкак единственный рендер-дифф и владелец пула;CardMoveAnimatorкак единый планировщик;Card3Dкак визуальную карту и arc-интерполяцию;InputLockServiceи существующие long-running последовательности;- серверный snapshot как источник состояния.
C. Чек-лист изменений
-
C1.
MODIFYGameBoard3D.Render— стабильная идентичность анонимных карт.- Файл:
client/Assets/Vanguard/Runtime/Board3D/View/GameBoard3D.cs. - Добавлена ветка reuse-by-key до поиска по
InstanceId. - Переиспользуется
Card3Dпоspec.Key, если старая/новая карта анонимна или совпадают реальныеInstanceId; иначе используетсяpreviousByInstanceилиRentCard. - При смене
0→realв том же слоте карта сохраняется и обновляетсяBind/SetArt/SetPoseбез анимации. - Карты с исчезнувшими ключами освобождаются через
ReleaseCard. - Реализация: выполнена.
- Проверка:
Repeated_Render_Reuses_Hidden_Cards_And_Does_Not_Enqueue_Moves,Revealing_Face_Down_Damage_Reuses_Card_And_Stays_In_Damage_Zone— проходят. - Зависит от: C2, C4.
- Файл:
-
C2.
MODIFYGameBoard3D.AddOpponentHandSpecs/AddDamageSpecs— стабильные ключи и явная политика анимации.- Файл:
client/Assets/Vanguard/Runtime/Board3D/View/GameBoard3D.cs. - Ключи
OH:player:iиD:player:iоставлены;InstanceId=0не считается личностью. - Скрытые карты при стабильном ключе не анимируются; новая скрытая карта в конце анимируется из Deck.
- Для урона: смена
0↔realилиreal→realв том же слоте обрабатывается reuse-by-key, а не полётом из Deck. - Реализация: выполнена.
- Проверка: см. C1 +
Board_Renders_Field_Hand_Guardian_Damage_And_Soul. - Зависит от: C1.
- Файл:
-
C3.
MODIFYGameBoard3D— карта источника из предыдущегоMatchView.- Файл:
client/Assets/Vanguard/Runtime/Board3D/View/GameBoard3D.cs. - Сохраняются
_previousViewи_sourceByInstanceдо замены_view. - Построен
Dictionary<int, CardSourcePose>дляSoul,Drop,FaceUpDamage; источникиHand/Guardian/Trigger/Fieldпокрываются существующимиCard3DчерезpreviousByInstance. ResolveSourcePose(spec): приоритет —Card3D(transfer), затем зона из_sourceByInstance, затемDeckPositionс рубашкой вверх.- Источники:
- Deck:
DeckPosition, рубашка вверх (baseYaw * X-flip 180°); - Soul:
SoulPosition, лицом вверх; - Drop:
DropPosition, лицом вверх; - FaceUpDamage:
DamagePosition, лицом вверх и на боку; - Hand/Guardian/Trigger/Field — через
previousByInstance/transfer.
- Deck:
- Fallback Deck используется только с комментарием и покрывается тестами.
PlaceImmediateполучает rotation с учётом flip.- Реализация: выполнена.
- Проверка:
New_Field_Unit_Starts_From_Hand_Not_Deck— проходит. - Зависит от: C1, C2.
- Файл:
-
C4.
MODIFYCardMoveAnimator.Enqueue— один активный/queued request на карту.- Файл:
client/Assets/Vanguard/Runtime/Board3D/Animation/CardMoveAnimator.cs. - В начале
EnqueueвызываетсяCancelCard(card), что удаляет queued/ active request этой же карты, не трогая другие. - Источник (
from*) передаётся изGameBoard3Dкак текущая визуальная позиция карты; target проверяется вCard3D.StartArcMove. CancelCard,CancelAll, timeout и снятиеcard-move-лока сохранены.- Реализация: выполнена.
- Проверка:
CardMoveAnimator_Coalesces_Requests_For_Same_Card,CardMoveAnimator_Releases_Input_Lock_When_Card_Is_Cancelled— проходят. - Зависит от: C1.
- Файл:
-
C5.
MODIFYGameBoard3D.Render+BattleView.Render— явно различать snapshot-рендер и локальный UI-рендер.- Файлы:
GameBoard3D.cs,client/Assets/Vanguard/Runtime/UI/BattleView.cs. GameBoard3D.Renderполучил параметрbool animateMoves = trueбез изменения DTO.BattleView.Render(bool animateMoves = true);OnSnapshotвызываетRender()(true); все локальные вызовы заменены наRender(animateMoves: false).- При
animateMoves: falsemove-анимации не запускаются; идентичность карт не меняется. Появление реально новой карты логирует warning. - Реализация: выполнена.
- Проверка:
Repeated_Render_Reuses_Hidden_Cards_And_Does_Not_Enqueue_Moves,ClientSmokeTests.Battle_Renders_3D_Damage_And_Rested_Cards— проходят. - Зависит от: C1, C2.
- Файлы:
-
C6.
MODIFYGameBoard3D.UpdateHandView— не перетирать движение руки.- Файл:
client/Assets/Vanguard/Runtime/Board3D/View/GameBoard3D.cs. - В
UpdateHandViewдобавлено условие: пропускать карту, еслиcard.IsMoving, чтобыSetPoseне менял target во время arc-перехода. GetHandViewPoseи привязка руки к камере не изменены.- Реализация: выполнена.
- Проверка:
Hand_Is_Anchored_To_Camera_Bottom_Center_And_Faces_Player,New_Field_Unit_Starts_From_Hand_Not_Deck— проходят. - Зависит от: C3, C4.
- Файл:
-
C7.
MODIFYAnimationConfig.asset+ проверка значений.- Файл:
client/Assets/Vanguard/Resources/AnimationConfig.asset. - Добавлены/сериализованы все поля из
AnimationConfig.cs:moveDuration: 0.5,importantMoveDuration: 0.9,moveDelayBetweenCards: 0.18,arcHeight: 0.45,cameraZoomDuration: 0.35,triggerRevealHold: 1.2,cameraReturnDuration: 0.4,cameraRezoomDuration: 0.3,growDuration: 0.35,dashScrollSpeed: 1.5,boostGrowDuration: 0.3,boostThickness: 0.8,arrowFadeDuration: 0.2. - Длительности feedback не менялись.
- Реализация: выполнена.
- Проверка:
CardOverlayTests.Animation_Config_Asset_Is_Loaded_From_Resources, полеarcHeightчитается вGameBoard3D.Render— проходят. - Зависит от: C5 (можно параллельно).
- Файл:
-
C8.
TESTGameBoard3DTests— регрессии анимаций.- Файл:
client/Assets/Vanguard/Tests/Editor/GameBoard3DTests.cs. - Добавлены тесты:
Repeated_Render_Reuses_Hidden_Cards_And_Does_Not_Enqueue_Moves;Revealing_Face_Down_Damage_Reuses_Card_And_Stays_In_Damage_Zone;New_Field_Unit_Starts_From_Hand_Not_Deck;CardMoveAnimator_Coalesces_Requests_For_Same_Card.
- В
CardMoveAnimatorдобавлены read-only seam-свойства:PendingCount,ActiveCount,IsCardQueued,IsCardActive. - Исправлен
ClientSmokeTests.Battle_Renders_3D_Damage_And_Rested_Cardsдля вызоваRenderс новой сигнатурой через reflection. - Реализация: выполнена.
- Проверка: все новые тесты + старые — 46/46.
- Зависит от: C1–C6.
- Файл:
-
C9.
VERIFYsmoke / compile / EditMode.- Скопирован проект без
Library;Unity 6000.6.6f1 -batchmode -nographics -runTests -testPlatform EditMode. - EditMode: 46/46 passed (включая 15
GameBoard3DTests, 12ClientSmokeTests, 6CardOverlayTests, 12PlaymatLayoutTests, 1 Addressables stub). PLAYMAT_SMOKE success=True board=True cards=25 hand=5/5 fieldVisible=True.SCENE_SMOKE success=True active=Login env=True login=True.- Ошибок компиляции нет; warning CS8632 в
ClientSmokeTests— pre-existing. - Реализация: выполнена.
- Проверка: выполнена.
- Зависит от: C1–C8.
- Скопирован проект без
-
C10.
VERIFYручная проверка пользователем.- Пользователь проверяет: draw, call, ride, move/swap, guard, trigger, drive/damage, retire, search, открытие скрытого урона, руку противника, повторные клики по UI во время/после анимации.
- Ожидаемо: нет полёта скрытых карт из колоды при каждом клике; источник совпадает с зоной; нет рывков/двойных стартов; ввод не зависает.
- Реализация: не применима.
- Проверка: остаётся на пользователе.
- Зависит от: C9.
D. Последовательность выполнения
- C1 + C2: стабилизировать идентичность/ключи, убрать пересоздание скрытых карт. Без этого остальные исправления будут маскировать симптом.
- C3: добавить source map и корректные source pose/face. Проверить Deck, Hand, Trigger, Damage, Soul/Drop/Guardian.
- C4: coalescing в аниматоре и отмена старого request.
- C5: разделить snapshot/UI-рендер, убрать лишние запуски.
- C6: проверить hand target; не менять без подтверждённой необходимости.
- C7: синхронизировать asset.
- C8: тесты; C9: проверки; C10: ручная проверка пользователя.
E. Проверки
- Client EditMode:
GameBoard3DTests+ новые тесты из C8. - Compile/Assembly: Unity batch mode в копии без
Library. - Smoke:
Vanguard.Editor.SceneSmoke.RunPlaymat;Runтолько при изменении сцен/сборки. git diff --check, просмотрgit diff, отсутствие изменений в Rules/Server/Shared/Content.- Повторный
Renderс неизменным view: число карт и move-request не растёт. - Повторный snapshot во время движения: нет двойного старта/сброса в источник.
- Ручная проверка — пользователем; не заменять её автотестом.
Команды (подтверждены проектом):
# клиентская копия без Library
& 'C:\Program Files\Unity\Hub\Editor\6000.6.6f1\Editor\Unity.exe' `
-batchmode -nographics -projectPath <copy> `
-runTests -testPlatform EditMode -testResults <copy>\TestResults.xml
Серверные тесты не требуются, если не меняются Rules/Server/Shared; при
изменении контрактов — только ssh home/Docker.
F. Риски и неизвестные вопросы
- Анонимные карты:
InstanceId=0не является уникальной личностью; нельзя использовать его для глобального map. Нужны стабильные ключи по зоне/индексу и отдельная политика для reveal. - Источник вне видимых зон: Deck не содержит
InstanceId; для карты из колоды источник всегда Deck. Для карты из Soul/Drop/Guardian/Trigger/Damage источник можно восстановить из previous view; иначе fallback Deck. - Смена открытого урона: при перестановке face-up damage возможна неоднозначность. Не анимировать перемещение между слотами урона, если это не подтверждено; приоритет — не «телепорт из колоды».
AnimationConfig.asset: после добавления полей Unity может сохранить старые значения; проверить Inspector, не считать defaults автоматически настроенными.UpdateHandView: если менять target каждый кадр, можно ухудшить плавность; C6 требует подтверждения тестом/наблюдением, а не слепой правки.- Не менять
CardMoveAnimatortimeout/CancelCard/CancelAllбез сохранения защиты от зависшегоInputLock. - Не расширять задачу на рефакторинг
BattleViewили замену animation stack.
G. Результат
- Реализовано: C1–C8 (см. чек-лист).
- Проверки: EditMode 46/46 в копии без
Library; компиляция без ошибок. - Не завершено/заблокировано: C10 — ручная проверка пользователем.
- Skill обновлён: не требуется; изменения технические, не затрагивают подтверждённые правила/архитектуру.
- Git: будут изменены
GameBoard3D.cs,CardMoveAnimator.cs,BattleView.cs,AnimationConfig.asset,GameBoard3DTests.cs,ClientSmokeTests.cs,IMPLEMENTATION_PLAN.md,docs/SESSION-STATE.md,docs/HANDOFF.md.
TASK-2026-002 — Боевой UX и правила после TASK-2026-001
- Статус:
[ ] - Создано: 2026-10-24
- Анализ: DeepSeek выполнен, код не менялся
- Реализация: не начата
- Проверка: не выполнена
- Затронутые системы:
Card3D,CardMoveAnimator,GameBoard3D,BattleView,AnimationConfig,TriggerCheckSequence,CameraDirector,Vanguard.Rules(Tejas, Perfect Guard), тесты.
A. Цель и требования
Исправить семь замечаний после первого блока анимаций:
- При взятии карты из колоды она не должна сначала появляться в руке, затем исчезать и только потом лететь по дуге.
- При размещении карты из руки не должно быть двойного визуального размещения: сначала в поле, затем исчезновение и дуга.
- При призыве из души не должно быть отдельного перемещения перед дугой.
- Tejas должен атаковать задний ряд только в колонке, расположенной напротив его колонки, а не в зеркально противоположной.
- Perfect Guard: сначала поместить карты в защиту, затем проверить способности и активировать их; текущая логика оплаты/отмены должна быть приведена к этому порядку.
- После атаки, drive check, trigger-зоны, урона и других боевых событий нужны паузы около 1.5 с; анимации должны быть длиннее.
- При фокусе камеры на trigger-зону скрывать руку; после возврата камеры возвращать руку.
Ограничения:
- не менять серверные контракты без необходимости; при изменении протокола
пересобрать
Vanguard.Shared.dll; - серверные тесты запускать только на
ssh home/Docker; - не менять сцены/префабы без отдельного решения;
- геймплей вручную проверяет пользователь.
B. Анализ причин
- Предварительное smooth-движение queued-карт.
CardMoveAnimatorставит запросы в очередь со stagger. Пока запрос ждёт в_queue,Card3D.Tickуже выполняет обычное экспоненциальное движение к_targetPosition. КогдаStartNextвызываетStartArcMove, карта возвращается вFromPositionи начинает дугу. Визуально это выглядит как «сначала переместилась, потом исчезла, потом дуга». Это объясняет п.1 и п.3, а также часть п.2 при нескольких перемещениях. - Локальный
DropAtдо снапшота.GameBoard3D.FinishDragвызываетcard.DropAt(target)сразу после отпускания мыши. Карта визуально встаёт в поле, затем авторитарный снапшот делаетPlaceImmediate(hand/soul)и запускает дугу. Это отдельная причина п.2, не связанная с очередью. - 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, его нужно исправить вместе с правилом. - Perfect Guard сейчас — одна команда.
GuardIntentсодержит и карту защиты, иDiscardPrintingId;GameEngine.Guardсразу добавляет карту в Guardian, сбрасывает карту оплаты и ставитbattle.Nullified. Отдельной стадии «карты уже в защите → проверить/активировать способности» нет. ТестыPerfect_Guard_*описывают упрощённую модель. - Задержки не образуют боевой такт. Длительности feedback уже есть в
AnimationConfig(attackDeclare,driveCheck,trigger,damageи др.), но текст события и движение карт идут разными путями:OnEventsставит feedback в очередь, аRequestSnapshotAsyncотдельно запускаетCardMoveAnimator. Нет явной паузы после боевого события и нет гарантии, что движение карты завершилось до следующего события. Движение карт (moveDuration = 0.5,importantMoveDuration = 0.9) короче, чем нужно пользователю. - Рука следует за камерой.
TriggerCheckSequenceдвигаетCameraDirector, но не сообщаетGameBoard3Dо фокусе.UpdateHandViewкаждый кадр привязывает руку к камере, поэтому при зуме на trigger-зону рука оказывается перед камерой.
C. Чек-лист изменений
-
D1.
MODIFYCard3D— hold-at-source для queued-перемещений.- Файл:
client/Assets/Vanguard/Runtime/Board3D/View/Card3D.cs. - Добавить состояние «ожидает arc» и метод
HoldAtSource(position, rotation, scale). - Пока карта ожидает
StartArcMove,Tickне должен выполнять smooth interpolation к target; карта остаётся в source pose. StartArcMoveснимает hold и запускает дугу изFromPosition.StopMove/CancelHoldснимают hold; если запрос отменён, карта может продолжить обычное smooth-движение к актуальному target.- Реализация: не начата.
- Проверка: новый EditMode-тест «вторая карта в очереди не двигается до старта arc».
- Зависит от: D2.
- Файл:
-
D2.
MODIFYGameBoard3D.Render— ставить карту на hold до Enqueue.- Файл:
client/Assets/Vanguard/Runtime/Board3D/View/GameBoard3D.cs. - После
PlaceImmediateдля перемещаемой карты вызыватьHoldAtSource(source); target остаётся финальной позой изSetPose. - При
animateMoves: false,ReleaseCard,Clear,CancelAllhold снимать, чтобы карта не зависла в источнике. - Убедиться, что
UpdateHandViewне снимает hold и не двигает карту до старта arc. - Реализация: не начата.
- Проверка: тесты D1 + smoke; визуально одна дуга без предварительного перемещения.
- Зависит от: D1.
- Файл:
-
D3.
MODIFYdrag-and-drop — убрать двойное размещение.- Файл:
client/Assets/Vanguard/Runtime/Board3D/View/GameBoard3D.cs,client/Assets/Vanguard/Runtime/UI/BattleView.cs. - Решение пользователя: вариант A — после отпускания мыши не вызывать
DropAtдля server-authoritative перемещения. - Оставить карту в source (или показать временный ghost/индикатор цели), а после снапшота один раз анимировать из source в target.
- Ошибка/отказ сервера возвращает карту в source без второй анимации.
- Реализация: не начата.
- Проверка: call из руки и move/swap не показывают «поставилась → исчезла → дуга».
- Зависит от: D1–D2.
- Файл:
-
D4.
TESTклиентские регрессии перемещения.- Файл:
client/Assets/Vanguard/Tests/Editor/GameBoard3DTests.cs. - Добавить тесты:
- вторая карта в очереди не двигается до
StartArcMove; - отмена queued-запроса не оставляет карту в hold;
Removing_Card_From_Hand...и существующие тесты не ломаются;- повторный рендер после drag-drop не создаёт второй arc-запрос.
- вторая карта в очереди не двигается до
- Реализация: не начата.
- Проверка: EditMode в копии без
Library. - Зависит от: D1–D3.
- Файл:
-
D5.
MODIFYVanguard.Rules— колонка Tejas через границу игроков.- Файлы:
server/src/Vanguard.Rules/Engine/GameEngine.cs,server/tests/Vanguard.Rules.Tests/GameEngineTests.cs. - Оставить
SameColumnдля перемещений внутри поля. - Добавить
SameColumnAcrossPlayers(attackerSlot, defenderSlot):3↔2,4↔0,-1↔1— подтверждено пользователем. - Использовать новую функцию в
LegalCommandsдляAttackCommandи вAttackпри проверкеtargetSlot is 0/1/2. - Исправить тест
Tejas_Attacks_Back_Row_In_Its_Column: атакующий 3 → защитник 2; добавить 4→0, V→1 и негативный тест на противоположную колонку. - Проверить, что клиентские
LegalIntentsи подсветка цели не требуют изменений: сервер начнёт присылать правильныйTargetSlot. - Реализация: не начата.
- Проверка: серверные тесты на
ssh home/Docker. - Зависит от: нет.
- Файлы:
-
D6.
MODIFYPerfect Guard — стадийная защита и полная модель.- Файлы:
server/src/Vanguard.Rules/Engine/GameEngine.cs,server/src/Vanguard.Shared/Protocol/Intents.cs,client/Assets/Vanguard/Runtime/UI/BattleView.cs, тесты. - Решение пользователя: полная модель, оплата спрашивается после размещения всех карт защиты.
- Целевой порядок:
- игрок выбирает и помещает карты защиты в Guardian (включая Sentinel, пока без оплаты);
- после размещения сервер проверяет способности карт защиты;
- для Sentinel открывается pending ability «оплатить сброс карты того же клана»;
- при активации — сброс, выбор атакованного юнита, который не может быть поражён до конца боя; при отказе — щит 0, атака проходит.
- Текущее упрощение
battle.Nullified = trueзаменить на корректную модель «защищённый юнит не может быть поражён» (поле боя/флаг protected unit), если она не эквивалентна текущемуNullifiedв рамках одного защищающегося. GuardIntent.DiscardPrintingIdбольше не должен быть обязательной частью размещения; оплата идёт отдельным guard-ability intent (ResolveAbilityIntent/новый тип). При изменении протокола пересобратьVanguard.Shared.dllи обновить replay codec.- Обновить
Perfect_Guard_*тесты: размещение без оплаты → pending ability → оплата/отказ; добавить тест «сначала все карты, потом оплата». - Реализация: не начата.
- Проверка: серверные тесты на
ssh home/Docker; ручная проверка пользователем. - Зависит от: D5 (общий серверный блок можно делать параллельно).
- Файлы:
-
D7.
MODIFYAnimationConfig+ боевой такт.- Файлы:
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/boardlock и не начинать следующий такт, пока не истекла пауза и не завершились карточные перемещения. - Сохранить кнопку «Пропустить анимации» и корректное снятие всех lock.
- Реализация: не начата.
- Проверка: smoke + визуальная проверка пользователем.
- Зависит от: D1–D2.
- Файлы:
-
D8.
MODIFYtrigger-камера и рука.- Файлы:
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.
- Клиент: EditMode в копии без
-
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. Последовательность выполнения
- D1–D2: hold-at-source, убрать предварительное smooth-движение. Это устраняет основную причину п.1 и п.3.
- D3–D4: drag-and-drop и тесты; проверить п.2.
- D5: Tejas (отдельный серверный блок, можно параллельно).
- D6: Perfect Guard — полная модель и стадийная оплата после размещения.
- D7–D8: длительности, боевой такт, рука и trigger-камера.
- 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. Решения и риски
Зафиксировано пользователем:
- Drag-and-drop: вариант A — не ставить карту локально, одна дуга после снапшота.
- Perfect Guard: полная модель; оплата спрашивается после размещения всех карт защиты.
- Tejas: колонки
3↔2,4↔0,V↔1. - Длительности: паузы 1.5 с;
moveDuration = 0.8,importantMoveDuration = 1.3. - Рука: скрывать только на trigger-зуме.
Технические риски:
- Протокол Perfect Guard: изменение
GuardIntent/GuardCommandможет потребовать пересборкиVanguard.Shared.dll, обновления replay codec и клиентскогоBattleView. Делать одним согласованным блоком. - Полная модель Perfect Guard: может понадобиться отдельное состояние
«защищённый юнит» и событие выбора юнита; текущий
battle.Nullifiedнельзя считать полной моделью. - Drag-and-drop вариант A: карта может визуально вернуться в source, пока ждёт снапшот. Нужен ghost/индикатор цели, чтобы это не выглядело как откат.
- Длинные анимации: паузы 1.5 с удерживают
feedback/boardlock; кнопка «Пропустить анимации» должна снимать все 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 с триггером и без, боевые оверлеи, длинные имена/локали.
- EditMode: маппинг фаз/шагов в подписи, счётчики панели,
D. Порядок работ
- V1–V4: верхний HUD, временная кнопка панели, отказ от постоянной правой панели, подтверждения в правом нижнем углу.
- V5–V6: панели игроков, полноширинное поле, баннер хода.
- V7–V8: слоты, зоны урона/колоды, счётчик
x/6. - V9–V10: карточный шаблон, статы под картой, свет.
- V11–V12: анимации, локализация, русский текст способностей, адаптивность.
- V15–V16: drag&drop-сценарии и trigger-зум.
- V17: конкретные подписи trigger-действий.
- V13–V14, V18: UX-сверка, тесты, smoke, ручная приёмка.
E. Открытые вопросы
- [O] Временная кнопка «Показать/скрыть панель»: панель по умолчанию скрыта; когда её убирать — после стабилизации HUD.
- [O] Делать карточный шаблон процедурным или prefab-ассетом после появления финальных рамок?
- [O] Нужен ли фазовый баннер в world-space (на столе) или screen-space (поверх HUD)? Рекомендация — screen-space, чтобы не спорить с камерой.
F. Результат
- Реализовано: V1–V9, V12–V18.
- Частично: V10 — добавлены пластины слотов и рамки карт, без отдельного bloom-объёма; V11 — новых HUD-анимаций нет.
- Новые правки после мокапа: карты урона укладываются сверху вниз
(зеркально у противника), шаг
damageStackOffset = 0.3от ширины карты, новая карта приподнята по Y и рисуется поверх; trigger-карта повёрнута на 90° против часовой стрелки (локально-90°для себя,+90°для противника); на карте показывается бейдж триггера (имена убраны); no-trigger damage check определяется поDamageTakenViewс подавлением повторного зума после trigger-карты. - Правки по замечаниям: имена карт на поле убраны; панель действий
сжата до автоподстраиваемого столбца кнопок справа снизу (без заголовка,
фона и скролла); рука центрируется по экрану (
handViewportX = 0.5); подсказка муллигана перенесена в верхнийActionPrompt, при выборе карт показывается кнопка «Заменить N карт»; центральный баннер хода убран; левый нижний бар —Колода, Душа, Рука, Сброс, безЩит/Триг, все блоки одинаковые,Душа/Сброскликабельны. - Порядок триггера: камера всегда фокусируется на trigger-зоне;
после фокуса карта раскрывается анимацией (
triggerRevealDuration), затем паузаtriggerRevealHold = 0.75 с, затем резолв. Выбор цели открывается только после этой паузы. Сначала+5000на свою карту в RC/VC, затем эффект:Critical— RC/VC,Stand— только повёрнутый RC,Draw— автоматически,Heal— выбор карты урона при выполнении условий. Подсказки пишутся в верхнем информационном блоке. Нетриггерная карта просто пропускается. - Tejas: исправлена атака заднего ряда по колонке через границу
игроков:
V↔1,3↔2,4↔0. Сервер пересобран и задеплоен наssh home, контейнерserverперезапущен. - Основная фаза: в панели подтверждений остаётся только кнопка «Боевая фаза». «Закончить ход» и список всех действий скрыты; активация способностей — через меню карты.
- Рамки карт: собственная рамка карты — тонкая чёрная по её краю; подсветка выбора/цели тоже ужата до размера карты, больше не наезжает на соседние карты. В руке рамки скрыты, карты уложены широким веером: новая карта перекрывает предыдущую и за счёт шага по глубине всегда рисуется поверх неё. Своя рука заполняется слева направо: первая карта слева, новые добавляются правее и остаются поверх. Веер сохраняет ширину до 10 карт, а начиная с 11-й шаг плавно уменьшается. Рука противника — зеркальный камеро-зависимый веер ниже верхней панели: новая карта появляется слева и остаётся поверх предыдущей, наклон и порядок глубины зеркальны. Позы обеих рук пересчитываются каждый кадр под текущую камеру, поэтому добор не улетает к устаревшей цели.
- Стартовый Авангард: лежит закрытым во время муллигана; после
завершения муллиганов обоих игроков открывается с анимацией
(
vanguardRevealDuration = 0.8 с), затем начинается первая фаза. Переворот идёт вокруг длинной оси карты слева направо, у противника зеркально.MatchProjectionбольше не форсируетFaceUpдля Vanguard и передаёт реальное состояние карты; клиент блокирует ввод на время переворота. - Старт боя: камера стартует над стороной игрока лицом к столу, пауза 1,5 с; затем по дуге опускается на основную позицию, пауза 1,5 с; затем одновременно раздаются обе руки — своя и противника — с анимацией из колод. Панель муллигана появляется только после окончания всех доборов. Ввод заблокирован до конца интро.
- Взятие карт: карта создаётся ровно над колодой рубашкой вверх (рисунок рубашки ориентирован правильно) и летит в руку по дуге с переворотом лицом вверх. Любой добор идёт последовательно: следующая карта стартует только после завершения анимации предыдущей. В стартовой раздаче своя и чужая руки идут парами, внутри каждой пары — по одной.
- Урон: face-up/face-down переворот карт урона идёт вокруг длинной стороны карты, а не поперёк.
- Проверки: клиентские EditMode в копии без
Library— 50/50; серверныеVanguard.Rulesнаssh home— 155/155; standalone- сборка Windows — успешно; клиент запущен изclient/Builds/Windows/VanguardReMaster.exe. - Не проверено: ручная игровая приёмка пользователем.
- Открытые вопросы: bloom/свет, HUD-анимации, надёжность эвристики no-trigger damage check.
2026-10-27 — TASK-2026-004: убрать боевые баннеры
Решение пользователя: все боевые баннеры не нужны; убираем их
полностью, чтобы пересобрать анимации блоками. Верхний ActionPrompt
(муллиган/смена/подсказки), StatusToast и trigger-сообщения не являются
боевыми баннерами и остаются.
Анализ:
- Центральный
BattleFeedbackи полноэкранныйBattleFlash— это один feedback-блок: текст события, вспышка damage/heal, кнопка «Пропустить анимации», очередь и паузыeventPause/post*Pause. - Паузы feedback сейчас задают темп событий, но не являются источником
истины; после удаления темп держат
CardMoveAnimator, trigger-последовательность и серверные снапшоты. InputLock("feedback")и_feedbackLockHeldсуществуют только для этого блока; их нужно убрать вместе с ним._turnBanner/_turnBannerTextобъявлены, но не строятся; удалить как мёртвый код.AnimationConfig.attackDeclare/guard/driveCheck/trigger/battleResolved/ damage/heal/intercept/perfectGuard/eventPause/postDrivePause/ postTriggerPause/postDamagePause/retireDurationпосле удаления станут неиспользуемыми; пока оставляем поля, чтобы не менять контракт ассета.
Реализация:
- Удалить
BattleFeedback,BattleFlash,SkipFeedbackи весьFeedbackStep-механизм. - Убрать вызовы
ShowFeedback/EnqueuePause/ShowFeedbackFor. - Убрать
InputLock("feedback")и связанные поля/сбросы. - Удалить неиспользуемые
_turnBanner/_turnBannerText. - Оставить журнал событий,
Describe, trigger-последовательность,ActionPromptиStatusToast.
Проверка:
- EditMode в копии без
Library— 51/51. - Standalone Windows собирается.
- Вручную: в бою нет центральных баннеров и вспышек, события идут без зависаний; журнал и trigger-подсказки работают.
2026-10-27 — TASK-2026-005: порядок набора руки
Решение пользователя: рука локального игрока должна набираться слева направо: первая карта слева, новые добавляются правее. Рука противника по правилу зеркальности набирается с его стороны поля, то есть на экране справа налево: новая карта слева.
Реализация:
GetHandViewPose: вернутьx = center + offset * step,roll = -offset * roll.GetOpponentHandViewPose: сделать зеркально —x = center - offset * step,roll = +offset * roll.- Обновить тест
Hands_Fill_Mirrored_And_Newest_Is_On_Topпод новое направление и новые карты.
Проверка:
- EditMode в копии без
Library— 51/51. - Standalone Windows собирается.
- Вручную: новая карта в своей руке появляется справа; у противника — слева.
2026-10-27 — TASK-2026-006: возврат карт из руки в колоду
Решение пользователя: карта уходит из руки в колоду по дуге под низ колоды с переворотом рубашкой вверх во время движения. Возврат идёт справа налево; следующая карта стартует только после завершения предыдущей анимации. Для противника — то же зеркально с его стороны.
Анализ:
- Сервер уже сообщает о возврате:
MulliganView(игрок, количество) иCardReturnedToDeckView(игрок,PrintingId). Отдельного per-card события для муллигана нет, поэтому карты определяются по исчезновению из старой руки. - Старый
Card3Dруки сейчас просто освобождается вGameBoard3D.Render; чтобы анимировать, его нужно вынуть из_cards, оставить в отдельном списке, провести дугой и только потом вернуть в пул. - Для последовательности используем
CardMoveAnimatorсsequential: true; для скрытия после дуги — callback завершения. Это переиспользует общий arc/flip и очередь, не создаёт отдельный аниматор. - Цель — позиция колоды минус небольшой
Y, рубашкой вверх; во время движенияStartArcMoveинтерполирует поворот от лица к рубашке.
Реализация:
GameBoard3D.QueueHandReturnToDeck+ обработкаMulliganViewиCardReturnedToDeckViewизBattleView.- Выбор старых карт руки, удаление из
_cards, дуга под колоду, переворот, возврат в пул после завершения. CardMoveAnimator.EnqueueполучилonCompleted; последовательная очередь используется для возврата.- Тесты:
Hand_Return_To_Deck_Starts_Right_To_Left_And_Targets_Deck_Bottom,CardMoveAnimator_Runs_Sequential_Requests_And_Completes_Each.
Проверка:
- EditMode в копии без
Library— 53/53. - Standalone Windows собирается.
- Вручную: при муллигане/возврате карты уходят по дуге под колоду, переворачиваются рубашкой вверх, следующая стартует после предыдущей; у противника зеркально.
2026-10-27 — TASK-2026-007: общий добор карты
Решение пользователя: добор после муллигана должен выглядеть так же, как добор в муллигане: карта появляется над колодой полного размера, рубашкой вверх, летит в руку по дуге и переворачивается лицом вверх.
Анализ:
AnimateHandDrawуже стартует с_config.cardScaleиcard.FaceDownRotation.- Обычный добор шёл через
ResolveSourcePose: для карты руки он бралspec.Scale * cardScale(размер руки) и плоскую ориентацию колоды, поэтому над колодой появлялась маленькая карта. - Нужно вынести общий старт добора: позиция над колодой, полный масштаб, рубашка в ориентации будущей карты руки.
Реализация:
GameBoard3D.GetDrawSource— общая позиция/масштаб старта добора.ResolveSourcePoseдляBoardZone.Handиспользует полный масштаб иspec.BaseRotation * FaceDownFlip.AnimateHandDrawиспользует тот жеGetDrawSource.- Длительность нового добора —
introDrawDuration(0.6 с), как в муллигане. - Тест проверяет, что новая карта стартует над колодой в
cardScale.
Проверка:
- EditMode в копии без
Library— 53/53. - Standalone Windows собирается.
- Вручную: добор после муллигана визуально совпадает с муллиганом; карта не маленькая.
2026-10-27 — TASK-2026-008: длительность возврата как у добора
Решение пользователя: анимация возврата карты в колоду должна идти столько же, сколько добор из колоды.
Реализация:
- Общая
GameBoard3D.DrawDuration(introDrawDuration, 0.6 с). StartHandReturnиспользуетDrawDurationвместоmoveDuration.- Обычный добор использует ту же
DrawDuration.
Проверка:
- EditMode в копии без
Library— 53/53. - Standalone Windows собирается.
- Вручную: возврат в колоду по длительности совпадает с добором.
2026-10-27 — TASK-2026-009: последовательный возврат карт
Проблема: карты возвращались одновременно. Card3D.SetPose ставил
цель под колодой, а _returningCards тикали все сразу; очередь
CardMoveAnimator стартовала дуги по одной, но карты уже плавно уезжали
к цели параллельно.
Решение: пока карта ждёт в очереди, удерживать её в исходной позе руки
через Card3D.HoldAtSource. Старт следующей — только после завершения
предыдущей (sequential: true).
Реализация:
StartHandReturnвызываетHoldAtSourceдоCardMoveAnimator.Enqueue.- Тест проверяет, что обе возвращаемые карты остаются в руке до старта своей анимации.
- Порядок: самая правая карта первой; у противника зеркально.
Проверка:
- EditMode в копии без
Library— 53/53. - Standalone Windows собирается.
- Вручную: сначала уходит самая правая карта, затем следующая.
2026-10-28 — TASK-2026-010: рука перестраивается при выходе карты
Решение пользователя: при любом выходе карты из руки остальные карты должны перестраиваться под новое количество одновременно с её перемещением.
Анализ:
UpdateHandViewуже считает позы по новомуHandCountиз свежегоRender.- Перекомпоновка руки (
Hand → Hand) не ставится вCardMoveAnimator(NeedsMoveвозвращает false), поэтому не ждёт анимацию уходящей карты. - Уходящая карта удаляется из
_handBindings/_cards, а остальные карты сразу получают новые цели; hand reflow идёт своим smooth-слоем.
Реализация:
- Проверка поведения для hand → field.
- Проверка поведения для возврата в колоду: остальные карты получают новые цели, пока возвращаемая карта ещё ждёт/летит.
- Тесты
Hand_Reflows_While_A_Card_Leaves_From_Handи расширенныйHand_Return_To_Deck_Starts_Right_To_Left_And_Targets_Deck_Bottom.
Проверка:
- EditMode в копии без
Library— 54/54. - Вручную: при выходе карты рука сдвигается одновременно с её движением.
2026-10-28 — TASK-2026-011: ступенчатый reflow руки при муллигане
Решение пользователя: во время муллигана рука должна сдвигаться после каждого ухода карты, а не один раз под окончательное количество.
Проблема: новый Render сразу содержал финальный HandCount (старые
остатки + новые карты). Остальные карты получали финальные позы, а
возвращаемые удерживались. Визуально рука перестраивалась один раз.
Решение:
- Ступенчатый список
_stagedReturnCards: старые карты руки в исходном порядке, включая ожидающие возврата; новые карты в него не входят. - Пока идёт возврат,
UpdateHandViewстроит позы только для этого списка. - После завершения каждой карты она убирается из списка, и оставшиеся карты (включая ожидающие) плавно сдвигаются под новое количество.
- Ожидающие карты не удерживаются на месте: при старте своей анимации они
получают цель под колодой через
OnStartи стартуют из текущей позы (fromCurrent). - После последнего возврата staged-режим выключается; новые карты добираются в финальную руку.
Реализация:
BuildStagedReturnHand,_stagedReturnCards,_stagedReturnPlayer.CardMoveAnimatorполучилonStartиfromCurrent.OnHandReturnCompletedубирает карту из staged-списка и сразу пересчитывает руку.- Тест проверяет: до первого ухода рука не сдвигается, после первого — сдвигается; ожидающая карта остаётся в руке до своей очереди.
Проверка:
- EditMode в копии без
Library— 54/54. - Standalone Windows собирается.
- Вручную: муллиган с двумя возвратами — рука сдвигается после каждого.
2026-10-28 — TASK-2026-012: добор по одной карте и ступенчатый reflow
Проблема: при доборе все новые карты сразу появлялись над колодой рубашкой вверх, а рука перестраивалась один раз под финальное количество.
Решение:
- Новые карты своей руки скрываются на время очереди и показываются только
в
CardMoveAnimator.onStart— по одной. _stagedDrawCardsхранит уже видимые карты (старые остатки + завершённые доборы); новые карты добавляются только после завершения их дуги.- После каждого добора рука пересчитывается под текущее видимое количество, а не под финальное.
- После последнего добора staged-режим выключается; финальная рука совпадает с текущей.
Реализация:
BuildStagedDrawHand,_stagedDrawCards,_stagedDrawTargetCount.BeginHandDrawArcпоказывает карту, ставит цель в следующий слот и запускает дугу из текущей позиции.OnHandDrawCompletedдобавляет карту в staged-список и пересчитывает руку.- Повторный snapshot без новых доборов не сбрасывает уже начатую staged раздачу — иначе остатки руки перестают сдвигаться.
- Тест
Hand_Draw_Stages_New_Cards_One_By_One.
Проверка:
- EditMode в копии без
Library— 55/55. - Standalone Windows собирается.
- Вручную: при доборе над колодой видна одна карта; рука расширяется после каждой.
2026-10-28 — TASK-2026-013: единый механизм раскладки руки
Проблема: остатки руки после муллигана переставали сдвигаться:
отдельные _stagedReturnCards и _stagedDrawCards не всегда содержали
одни и те же карты, а повторные snapshot/переходы между режимами ломали
раскладку.
Решение: один визуальный список _visualHandCards и один override
_handLayoutOverride:
- в список сразу входят все оставшиеся карты руки и ожидающие возврата;
- новые добранные карты добавляются только после завершения их дуги;
- после каждого возврата/добора список пересчитывается, поэтому остатки руки всегда участвуют в сдвиге;
- пока override активен,
UpdateHandViewиспользует только визуальный список; после завершения всех возвратов/доборов возвращается обычный_handBindings; - повторный Render не пересоздаёт список и не сбрасывает прогресс.
Реализация:
BuildVisualHand,_visualHandCards,_pendingDrawCards,_handLayoutOverride.- Удалены раздельные staged-списки возврата/добора.
- Тест
Mulligan_Remaining_Cards_Reflow_After_Each_Return.
Проверка:
- EditMode в копии без
Library— 56/56. - Standalone Windows собирается.
- Вручную: после муллигана остатки руки сдвигаются после каждого возврата и каждого добора.
2026-10-28 — TASK-2026-014: подъём карты в руке при наведении
Решение пользователя: карта под курсором плавно приподнимается на 10% своего размера; после ухода курсора плавно возвращается.
Реализация:
handHoverLift = 0.1теперь доля высоты карты, а не мировое смещение.GameBoard3D.UpdateHandHover: raycast по своей руке, только когда нет UI/лока/драга/анимаций; оппонент и поле игнорируются.SetHoveredHandCardпересчитывает руку;ApplyHandHoverдобавляет камера-вверх наcardHeight * scale * cardScale * handHoverLift.- Плавность — существующий
Card3D.SetPose/Tick; отдельного твина нет. - Тест
Hand_Hover_Lifts_Card_By_Ten_Percent(подъём и возврат цели).
Проверка:
- EditMode в копии без
Library— 57/57. - Standalone Windows собирается.
- Вручную: наведение поднимает карту, уход курсора возвращает.
2026-10-28 — TASK-2026-015: drag карты из руки, смена через VC
Решение пользователя: карта из руки, отпущенная на VC, делает смену; пока кнопка мыши удерживается, карта остаётся приподнятой; отпускание в неположенном месте плавно возвращает её в руку; без дёрганья.
Реализация:
Card3D.EndDragAtCurrentфиксирует карту в точке drop-а, не возвращая её в руку и не сбрасывая подъём/масштаб.GameBoard3D.FinishDrag: для карты из руки больше не вызываетReturnToOriginдо ответа сервера; держит карту в_dropPendingCard.GameBoard3D.ReturnDroppedCard: снимает hold и плавно возвращает в руку, если drop не стал интентом/отклонён.BattleView: возвращает карту при отсутствии интента, отказе сервера, ошибке отправки, guard-выборе и раннем выходе.- Валидный Ride/Call: карта ждёт снапшот и летит в target уже из точки drop-а; в руку не возвращается.
- Тест
Dropped_Hand_Card_Holds_At_Drop_Until_Returned.
Проверка:
- EditMode в копии без
Library— 58/58. - Standalone Windows собирается.
- Вручную: drag в VC на смену, удержание, возврат из неверного места.
2026-10-28 — TASK-2026-016: карта при drag под курсором
Проблема: при перетаскивании карта уезжала вниз-влево от курсора:
луч наводился на плоскость cardLift, а карта висела на dragLift; плюс
DragTo сбрасывал hand-ориентацию в плоский Euler(0,yaw,0).
Решение:
- Плоскость drag-а считается на фактической высоте карты
(
Card3D.DragLift), поэтому карта остаётся под курсором. - Для карты из руки
DragLift= hover-подъём 10% высоты; для карты поля — прежнийdragLift. DragToсохраняет_dragOriginRotation, поэтому hand-карта не переворачивается на стол при перетаскивании._dragPointerOffsetсохраняет точку захвата: карта не прыгает центром под курсор при старте drag-а.
Проверка:
- EditMode — 58/58.
- Standalone Windows собирается.
- Вручную: карта у курсора, hand-ориентация сохранена.
2026-10-28 — TASK-2026-017: drag не двигает карту в момент старта
Проблема: в момент старта drag-а вызывался UpdateDrag, карта сразу
переносилась к курсору/поднималась. Кроме того, после старта UpdateDrag
больше не вызывался каждый кадр — карта не следовала за мышью.
Решение:
OnPointerHeldтолько включает drag и запоминает точку захвата; позицию не меняет.HandlePointerпри_draggingкаждый кадр вызываетUpdateDrag, поэтому карта следует за курсором.OnPointerReleasedбольше не дублируетUpdateDrag; last target уже обновлён в этом кадре.
Проверка:
- EditMode — 58/58.
- Standalone Windows собирается.
- Вручную: при начале drag-а карта остаётся на месте, затем следует за курсором.
2026-10-28 — TASK-2026-018: выбор зоны без перемещения карты из руки
Решение пользователя: карта из руки при зажатии ЛКМ не должна перемещаться. Курсор наводится на зону; карта остаётся в руке приподнятой; при отпускании над VC выполняется смена.
Реализация:
- Для
source.IsHanddrag заменён на_handTargeting: карта не вызываетBeginDrag/DragToи остаётся в hand-раскладке. UpdateHandHoverудерживает hover-подъём нажатой карты, пока идёт targeting.UpdateHandTargetingсчитает только целевую зону по курсору и показывает drop indicator; карта не двигается.- Цель сначала определяется raycast-ом по карте/кругу (VC-карта даёт ровно Vanguard), затем fallback на плоскость.
FinishHandTargetingна release отправляетCardDropped; при валидном VC срабатывает RideIntent, при невалидном карта остаётся в руке.- Field-карты сохраняют старый drag с движением.
- По серверному логу
rides=[TD02/007EN, BT01/015EN]— только эти карты были доступны для смены. Валидные Ride-карты теперь подсвечиваются в руке; добавлен fallback direct RideIntent и диагностические логи[HandTargeting]/[Drop].
Проверка:
- EditMode — 58/58.
- Standalone Windows собирается.
- Вручную: ЛКМ на карте, наведение на VC, отпускание — смена; карта всё время остаётся в руке.
TASK-2026-019 — загрузочная сцена и кадр без покадровых аллокаций
- Статус:
[ ] - Создано: 2026-10-30
- Анализ: DeepSeek выполнен (пункты A1, C1–C3 в
docs/Development/CLIENT-SCENES-3D-ANIMATIONS.md) - Реализация: не начата
- Проверка: не выполнена
- Затронутые системы:
SceneFlow,VanguardApp,Boot, новыйLoadingOverlay,GameBoard3D(стрелки, рука),ArrowDirector,AttackArrowView,BattleView.UpdateClock,Localization,InputLockService, EditMode-тесты.
A. Цель и требования
Первый блок бэклога «сцены, 3D, анимации». Четыре независимых результата:
- Переход между экранами без вспышки:
Boot— загрузочная сцена с прогрессом и fade, затемLogin; тот же overlay используется для остальных переходов. - Стрелки атаки/буста не создают мусор в кадре.
- Рука не пересчитывается дважды за кадр и не пересчитывается впустую.
- Строки 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. Текущее состояние
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).GameBoard3D.UpdateBoostArrowsсоздаётnew List<(Vector3, Vector3)>на каждый вызов (GameBoard3D.cs:1219-1227),BattleCardиспользуетPlayers.FirstOrDefault(lambda)(:1319-1320),ArrowAnchorищет карту линейным перебором_cards.Values(:1293-1301,:1332-1341).GameBoard3D.Updateза кадр вызываетUpdateHandView,_moveAnimator.Tick,TickHandDraw,_cameraDirector.Tick,card.Tickпо всем картам,_returningCards, затем_arrowDirector.TickиUpdateAttackArrowPoints(:2220-2255). Стрелки обновляются двумя путями.AttackArrowView.ApplyColorсоздаётnew[] { _shaft, _head }(:267) и вызывается изTickиSetStyle, т.е. несколько раз за кадр на стрелку.UpdateHandViewвызывается изUpdate,Render, hover и колбэков (:183, 511, 607, 732, 1163, 2230, 2372); позы обеих рук считаются каждый кадр.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.
CREATEclient/Assets/Vanguard/Runtime/UI/LoadingOverlay.cs— персистентный overlay загрузки.- Canvas + фон + текст прогресса на объекте
VanguardApp,raycastTarget = false, поверх сцены; APIShow(float progress),Hide(). - Не использовать
UiKit.CreateCanvasдля сцены: Canvas должен переживатьLoadSceneMode.Single. - Реализация: не выполнена. Проверка: не выполнена.
- Зависит: нет.
- Canvas + фон + текст прогресса на объекте
-
C2.
MODIFYRuntime/App/SceneFlow.cs— переход с прогрессом и fade.- Показывать overlay до
LoadSceneAsync, обновлять прогресс поoperation.progress,allowSceneActivation = falseдо готовности, затем активировать сцену и скрывать overlay после первого кадра. - Защита от повторного вызова во время перехода (idempotent
LoadAsync). - Реализация: не выполнена. Проверка: не выполнена.
- Зависит: C1.
- Показывать overlay до
-
C3.
MODIFYRuntime/App/VanguardApp.cs+Scenes/Boot.unity— Boot как загрузочная сцена.Bootпоказывает overlay и грузитLoginпосле инициализации сервисов; прогрев не дублируется вStart, если сцена уже целевая.- Реализация: не выполнена. Проверка: не выполнена.
- Зависит: C1, C2.
-
C4.
MODIFYBoard3D/View/GameBoard3D.cs— стрелки без LINQ и покадровых списков.- Переиспользуемый буфер пар boost-стрелок, индексированный поиск игрока
вместо
FirstOrDefault, один проход по_cardsдля якорей. - Реализация: не выполнена. Проверка: не выполнена.
- Зависит: нет.
- Переиспользуемый буфер пар boost-стрелок, индексированный поиск игрока
вместо
-
C5.
MODIFYAnimation/ArrowDirector.cs+View/AttackArrowView.cs— одно обновление геометрии за кадр.- Убрать двойной путь
Tick+UpdateAttackArrowPoints; обновлять точки один раз; убрать массивnew[] { _shaft, _head }вApplyColor. - Реализация: не выполнена. Проверка: не выполнена.
- Зависит: C4.
- Убрать двойной путь
-
C6.
MODIFYBoard3D/View/GameBoard3D.cs—UpdateHandViewпо dirty-флагу.- Пересчитывать позы только при изменении состава руки, камеры, hover, staged-списка; оставить ровно один вызов за кадр.
- Реализация: не выполнена. Проверка: не выполнена.
- Зависит: нет.
-
C7.
MODIFYUI/BattleView.cs—UpdateClock. Обновлять текст таймера и банка только при изменении отображаемого значения.- Реализация: не выполнена. Проверка: не выполнена.
- Зависит: нет.
-
C8.
MODIFYUI/Localization.cs—LocalizedText. Кешировать результат перевода до смены языка или ключа, не вызыватьLoc.Translateкаждый кадр.- Реализация: не выполнена. Проверка: не выполнена.
- Зависит: нет.
-
C9.
MODIFYBattle/InputLockService.cs—Describe. Не собирать строку/LINQ в кадровом пути: кешировать описание при изменении набора причин.- Реализация: не выполнена. Проверка: не выполнена.
- Зависит: нет.
-
C10.
TESTрегрессии блока.- EditMode: overlay
Show/Hideидемпотентны;UpdateHandViewне меняет позы при неизменных входах (seam-счётчик), hover/добор/муллиган по-прежнему сдвигают руку;ApplyColorне создаёт массив (статическая проверка или отсутствие регрессии поведения). - Существующие
Hand_*,CardMoveAnimator_*,GameBoard3DTests— без изменений ожиданий. - Реализация: не выполнена. Проверка: не выполнена.
- Зависит: C1–C9.
- EditMode: overlay
-
C11.
VERIFYавтотесты, smoke, сборка.- EditMode в копии без
Library;SceneSmoke.Run;SceneSmoke.RunPlaymat; standalone-сборка. - Profiler: Allocation Call Stacks в бою без движения — 0 B/кадр.
- Реализация: не выполнена. Проверка: не выполнена.
- Зависит: C10.
- EditMode в копии без
-
C12.
VERIFYручная проверка пользователем.- Переход
Boot → Login → Lobby → Battleбез вспышки; видимый прогресс; стрелки атаки/буста без визуальных изменений; hover, добор, муллиган, reflow руки; таймер и подписи. - Реализация: не применима. Проверка: остаётся на пользователе.
- Зависит: C11.
- Переход
D. Последовательность выполнения
- C1 → C2 → C3: загрузочный overlay и Boot. Проверить переходы смоуком.
- C4 → C5: стрелки; визуально сравнить до/после.
- C6: рука по dirty-флагу; прогнать
Hand_*-тесты. - C7 → C8 → C9: строки и локализация.
- C10 → C12: тесты, проверки, ручная приёмка.
E. Проверки
- Клиентские EditMode в копии проекта без
Library(команда вdocs/SESSION-STATE.md). SceneSmoke.RunиSceneSmoke.RunPlaymat.- Standalone-сборка на копии: переходы и overlay.
- Profiler: Allocation Call Stacks до/после для боя без движения.
git diff --check; отсутствие изменений вVanguard.Rules,Vanguard.Shared,Vanguard.Server,content/.- Ручная приёмка — пользователем.
F. Риски и неизвестные вопросы
- Overlay на персистентном Canvas: может перехватывать клики или висеть
после загрузки —
raycastTarget = false, скрытие вfinallyи по таймауту. - Смоук-таймауты:
SceneSmokeждёт 6 с; если Boot начнёт грузить Login позже, таймаут придётся увеличить или ждать по состоянию. - Dirty-флаг руки: легко сломать hover, staged-reflow и муллиган; менять
только после зелёных
Hand_*-тестов. - Якоря стрелок: карты двигаются каждый кадр, поэтому кешировать можно буферы и результаты поиска карт, но не позиции.
- Порядок вызовов: у
BattleSceneController.UpdateиGameBoard3D.UpdateнетDefaultExecutionOrder; правки не должны завязываться на порядок. - HUD TASK-2026-003 ещё не принят вручную: в
BattleViewменять толькоUpdateClock, не разметку. - Открытых вопросов, блокирующих старт, нет.
G. Результат
- Реализовано: —
- Проверки: —
- Не завершено/заблокировано: —
- Skill обновлён: —
- Git: —
TASK-2026-020 — парсер карт и артов с официального сайта
- Статус:
[x] - Создано: 2026-10-30
- Анализ: DeepSeek
- Реализация: DeepSeek
- Проверка: побайтовое совпадение с текущими YAML (0 diff), 94/124, скачивание арта
- Затронутые системы:
tools/import_official.py(новый),README.md;content/sets/*.yamlиCardArtне изменялись.
A. Цель и требования
Один инструмент, который берёт с en.cf-vanguard.com и параметры карт, и
картинки, и пишет тот же YAML-формат, что и текущий импорт. Причины:
- сейчас параметры идут с сайта (
view=text), а триггеры и редкости — изcontent/cards-mvp.md(выгрузка из вики), арты — с Fandom; - официальная карточка
/cardlist/?cardno=…содержит иrarity, иgift(тип триггера), поэтому вики больше не нужна; - картинки официального сайта лежат рядом с данными:
/wordpress/wp-content/images/cardlist/{set}/{CODE}.png.
Ограничения: только стандартная библиотека Python; не менять схему YAML и
CardEntry/PrintingEntry; не терять ручные поля; не создавать в Resources
два ассета с одним именем.
B. Текущее состояние и что выяснено
tools/import_cards.py: список/cardlist/cardsearch/?expansion=N&view=text, регэксп по<li>, триггеры/редкости изcards-mvp.md, записьset/cards/printings, SP-принтыBT01/S01EN…S12ENпомечаютсяisParallel+basePrintingId.tools/import_card_art.py: Fandom-галереи, коды толькоTD\d{2}.- Сайт:
expansion=5→ BT01 (92 принта, 4 страницы),3→ TD01 (16),4→ TD02 (16); всего 94 определения и 124 принта — совпадает с README. - Карточка содержит
type,group(клан),race,nation,grade,power,critical,shield,skill,gift(напримерCritical Trigger +5000),effect,flavor,rarity,number. - Список сетов с id —
/cardlist/?change_ex(47 сетов,BT01=5,TD01=3,TD02=4), можно не хардкодить id. - Сайт отдаёт 404 на запросы без браузерного
User-Agent— нужен UA.
C. Чек-лист изменений
- C1.
CREATEtools/import_official.py— единый парсер.discover_sets()по/cardlist/?change_ex;--list-sets.list_codes()со всех страниц (max_pageиз HTML) и сверка с «N Results».parse_card()по карточке: тип, клан, grade/power/critical/shield, триггер изgift, редкость, текст, флагиsentinel/restraint.write_set()— тот же формат и порядок полей, что у старого импорта.- Реализация: выполнена. Проверка: см. C5.
- C2.
CREATEскачивание артов —{imageKey}.pngвCardArt.- Пропуск, если файл с любым расширением уже есть (иначе Unity увидит два
ассета с одним именем);
--force-artудаляет старый файл и его.meta. - Реализация: выполнена. Проверка: TD01, 16/16 файлов, ~350–430 КиБ.
- Пропуск, если файл с любым расширением уже есть (иначе Unity увидит два
ассета с одним именем);
- C3.
MODIFYдедупликация определений между сетами.- При прогоне одного сета читаются
nameIdиз остальных YAML папки, чтобы не дублировать карты, определённые в BT01. - Реализация: выполнена. Проверка: BT01 80 + TD01 7 + TD02 7 = 94.
- При прогоне одного сета читаются
- C4.
MODIFYсохранение ручных полей.nameRu,textRu,snipeBackRow,boostпереносятся из существующего файла поnameId(на сайте их нет; иначе терялся бы Tejas).- Реализация: выполнена. Проверка:
snipeBackRowу Tejas сохраняется.
- C5.
VERIFYидемпотентность.- Прогон
--sets BT01,TD01,TD02в копиюcontent/sets:Compare-Objectпо каждому файлу — 0 строк различий, размеры совпали (37116 / 3771 / 3989 байт). - Реализация: не применима. Проверка: выполнена.
- Прогон
- C6.
MODIFYREADME.md— команда нового импорта, режимы, предупреждение про заглушкуpythonв Microsoft Store (py).- Реализация: выполнена. Проверка: прочитано.
D. Последовательность выполнения
- C1: разбор списка и карточки, запись YAML.
- C2: арты и защита от дублей в
Resources. - C3–C4: дедупликация и ручные поля.
- C5: прогон в копию и сверка.
- C6: README.
E. Проверки
py tools/import_official.py --list-sets.py tools/import_official.py --sets TD01 --limit 4 --out <tmp> --art-dir <tmp>.- Полный прогон в копию
content/sets+Compare-Objectс репозиторием. dotnet run --project server/src/Vanguard.Cli -- validate— не запускался (нуженssh home/Docker); косвенно покрыт побайтовым совпадением YAML.- Скачивание арта проверено на TD01 (16 файлов) и на
--force-art.
F. Риски и открытые вопросы
- Разметка сайта нестабильна: любой редизайн ломает регэкспы. Симптом — «не найдено имя карты» или расхождение с «N Results».
- Сайт отвечает 404 без браузерного
User-Agent— UA обязателен,curlбез заголовка вводит в заблуждение. - Полный прогон — ~2–6 минут на 124 карточки (по одному запросу на карту,
пауза
--delay). Для нескольких сетов сразу это нормально, для правки одной карты — избыточно. - Официальные арты — PNG ~350–430 КиБ; текущие в репозитории — JPG. Массовая
замена не делалась:
--force-artменяет расширение и удаляет.meta. pythonв Microsoft Store — заглушка, нуженpy(отражено в README).- Открытый вопрос: переносить ли
skill(Boost/Twin Drive!!) в полеboost— сейчас не переносится, чтобы не менять игровые данные.
G. Результат
- Реализовано: C1–C6.
- Проверки: полный прогон в копию — 0 различий с
content/sets; 94 определения / 124 принта; арт TD01 16/16;--force-artзаменяет.jpgна.pngбез дублей. - Не завершено:
validateнаssh home; массовая замена артов на официальные PNG (решение пользователя). - Skill обновлён: не требуется.
- Git: не коммитилось.