28 KiB
Implementation Plan
Активный журнал задач и планов изменений. Постоянные правила проекта хранятся
в Skill vanguard-remaster; история старых блоков — в IMPLEMENTATION_CHECKLIST.md,
DEVELOPMENT_CHECKLIST.md и docs/HANDOFF.md. Этот файл не заменяет их и не
является источником игровых правил.
Процесс
- DeepSeek анализирует пожелание, код и зависимости, задаёт вопросы и создаёт/обновляет раздел задачи. Игровой код на этом этапе не меняется.
- Kimi читает
AGENTS.md, Skill и раздел задачи, сверяет план с текущим кодом, реализует, проверяет и обновляет статусы. - Передача — только через файлы репозитория и Git. Память модели и чат не считаются общим контекстом.
- Если план устарел или требования изменились, Kimi останавливает зависимую часть, фиксирует причину и при необходимости возвращает задачу на анализ.
- Нельзя удалять чужие задачи, невыполненные пункты и историю статусов.
Подробный процесс — docs/Development/AGENT-WORKFLOW.md.
Статусы
[ ]— не выполнено;[x]— выполнено и подтверждено доступными проверками;[!]— заблокировано или ждёт решения;[-]— больше не требуется после согласованного изменения плана.
Для каждого пункта храни отдельно:
- Реализация: выполнена / не выполнена / не требуется.
- Проверка: выполнена (что именно) / не выполнена (почему).
[x] нельзя ставить, если реализация есть, а проверка не запускалась.
Журнал задач
| ID | Статус | Название | Фаза | Проверка |
|---|---|---|---|---|
| TASK-2026-001 | [ ] |
Плавность карточных перемещений и корректные источники | DeepSeek: анализ готов; Kimi: ожидает | не выполнена |
Новые задачи добавляются в конец файла отдельными разделами. Не используй один раздел для нескольких независимых задач.
Шаблон задачи (не является активной задачей)
TASK-YYYY-NNN — краткое название
- Статус:
[ ] - Создано: YYYY-MM-DD
- Анализ: DeepSeek
- Реализация: Kimi
- Затронутые системы: ...
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, не начата
- Проверка: не выполнена
- Затронутые системы:
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. - В
CardAssignmentдобавить признакReuseByKey/StateOnly(имя выбрать по стилю файла). - Если
_cardsуже содержитspec.Key— переиспользовать этуCard3Dдаже приInstanceId == 0; не вызыватьRentCard. - Для
InstanceId == 0не ставитьmoved = trueтолько из-за отсутствия записи вpreviousByInstance. - При смене
InstanceId0→real в том же слоте (открытие урона) сохранить карту, обновить art/bind/face state; не отправлять её в полёт из колоды. - При исчезновении ключа освобождать карту как раньше.
- Реализация: не выполнена.
- Проверка: не выполнена.
- Зависит от: 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 в том же слоте — обновление карты/переворот, не новый полёт из Deck.
- Реализация: не выполнена.
- Проверка: не выполнена.
- Зависит от: C1.
- Файл:
-
C3.
MODIFYGameBoard3D— карта источника из предыдущегоMatchView.- Файл:
client/Assets/Vanguard/Runtime/Board3D/View/GameBoard3D.cs. - Сохранять предыдущий
MatchView/его зоны до присваивания нового_view. - Построить
Dictionary<int, SourcePose>дляHand,Guardian,Soul,Drop,Trigger,FaceUpDamage(и при необходимости field), гдеSourcePoseсодержит position/rotation/scale/faceUp. SourcePose(spec): приоритет — текущаяCard3D(для transfer), затем previous-view зона поInstanceId, затемDeckPosition.- Источники:
- Deck:
DeckPosition, рубашка вверх (rotation с X-flip); - Soul:
SoulPosition, лицом вверх; - Drop:
DropPosition, лицом вверх; - Guardian: фактическая
Card3DилиGuardianPosition, +90°; - Trigger: фактическая
Card3DилиTriggerPosition, ±90°; - FaceUpDamage:
DamagePosition, лицом вверх и на боку; - Hand: карта в руке, лицом вверх;
- Field: фактическая
Card3D/CirclePosition.
- Deck:
- Не придумывать источник, если
InstanceIdнет в previous view и он не Deck: fallback Deck допустим только с комментарием/тестом. PlaceImmediateдля нового источника должен получать rotation с правильным flip, а не только yaw.- Реализация: не выполнена.
- Проверка: не выполнена.
- Зависит от: C1, C2.
- Файл:
-
C4.
MODIFYCardMoveAnimator.Enqueue— один активный/queued request на карту.- Файл:
client/Assets/Vanguard/Runtime/Board3D/Animation/CardMoveAnimator.cs. - Перед постановкой нового request отменять/удалять старый request этой же
Card3D(queued и active), не трогая другие карты. - Новый request должен начинаться с текущей визуальной позиции карты, а не
со старого
FromPosition; если target совпадает — не создавать запрос. - Не допускать дублей в
_active; сохранить timeout/CancelCard/CancelAllи снятиеcard-move-лока. - Реализация: не выполнена.
- Проверка: не выполнена.
- Зависит от: C1.
- Файл:
-
C5.
MODIFYGameBoard3D.Render+BattleView.Render— явно различать snapshot-рендер и локальный UI-рендер.- Файлы:
GameBoard3D.cs,client/Assets/Vanguard/Runtime/UI/BattleView.cs. - Добавить параметр
animateMoves(или эквивалентный options-объект) без публичного изменения серверных DTO. OnSnapshot→animateMoves: true.- Локальные
Render()(guard selection, card menu, status, cost counter) →animateMoves: false: обновлять highlight/UI, но не запускать новые move-анимации и не менять идентичность карт. - Если при локальном Render карта реально новая (не должно происходить), поставить в target pose без анимации и записать warning в debug-лог.
- Реализация: не выполнена.
- Проверка: не выполнена.
- Зависит от: C1, C2.
- Файлы:
-
C6.
MODIFYCard3D/GameBoard3D.UpdateHandView— не перетирать движение руки.- Файлы:
client/Assets/Vanguard/Runtime/Board3D/View/Card3D.cs,GameBoard3D.cs. - Проверить сценарий «карта летит из Deck в Hand»:
UpdateHandViewвызываетSetPoseкаждый кадр; target должен совпадать с target request. - Если подтверждено расхождение — обновлять target request при изменении
hand pose или не вызывать
SetPoseдля карты, которая ещё вCardMoveAnimator.IsBusyдля этого instance. - Не отключать руку от камеры и не менять
GetHandViewPoseбез необходимости. - Реализация: не выполнена.
- Проверка: не выполнена.
- Зависит от: C3, C4.
- Файлы:
-
C7.
MODIFYAnimationConfig.asset+ проверка значений.- Файл:
client/Assets/Vanguard/Resources/AnimationConfig.asset. - Добавить/сериализовать
moveDuration,importantMoveDuration,moveDelayBetweenCards,arcHeight, camera/grow/dash-поля изAnimationConfig.cs. - Зафиксировать разумные значения: движение ~0.4–0.6, важное ~0.8–1.0, stagger ~0.15–0.25, arc ~0.3–0.5; не менять длительности feedback без запроса.
- Проверить в Unity Inspector, что значения не нулевые и совпадают с кодом.
- Реализация: не выполнена.
- Проверка: не выполнена.
- Зависит от: C5 (можно параллельно).
- Файл:
-
C8.
TESTGameBoard3DTests— регрессии анимаций.- Файл:
client/Assets/Vanguard/Tests/Editor/GameBoard3DTests.cs. - Добавить тесты:
- повторный
Renderс тем же view/state не увеличивает числоCard3Dдля opponent hand/face-down damage; - повторный
Renderне создаёт новыйCardMoveAnimatorrequest для стабильного ключа; - открытие face-down damage переиспользует
Card3Dи не отправляет его из Deck; - новый unit из hand сохраняет source-позицию руки, а не Deck;
CardMoveAnimatorcoalesce: второй Enqueue той же карты не оставляет дубль в active/queue;CancelCardпо-прежнему освобождаетcard-move-лок.
- повторный
- Для проверки source/queue при необходимости добавить тестовый seam
(read-only snapshot последних запросов/позиций) в
CardMoveAnimatorилиGameBoard3D, не меняя публичное поведение. - Реализация: не выполнена.
- Проверка: не выполнена.
- Зависит от: C1–C6.
- Файл:
-
C9.
VERIFYsmoke / compile / EditMode.- Запустить EditMode в копии проекта без
Library. PLAYMAT_SMOKE;SCENE_SMOKE— если затронуты сцены/сборка.- Проверить отсутствие ошибок компиляции и предупреждений о missing serialized fields.
- Реализация: не выполнена.
- Проверка: не выполнена.
- Зависит от: C1–C8.
- Запустить EditMode в копии проекта без
-
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. Результат
- Реализовано: не начато.
- Проверки: не выполнены.
- Не завершено/заблокировано: ожидает этапа Kimi; критических вопросов пользователю на данный момент нет.
- Skill обновлён: не требуется до подтверждения новых постоянных правил.
- Git: изменён только план/документация, код не менялся.