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 выполнена
- Проверка: автотесты 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.