VanguardReMaster/docs/Development/IMPLEMENTATION_PLAN.md

28 KiB
Raw Blame History

Implementation Plan

Активный журнал задач и планов изменений. Постоянные правила проекта хранятся в Skill vanguard-remaster; история старых блоков — в IMPLEMENTATION_CHECKLIST.md, DEVELOPMENT_CHECKLIST.md и docs/HANDOFF.md. Этот файл не заменяет их и не является источником игровых правил.

Процесс

  1. DeepSeek анализирует пожелание, код и зависимости, задаёт вопросы и создаёт/обновляет раздел задачи. Игровой код на этом этапе не меняется.
  2. Kimi читает AGENTS.md, Skill и раздел задачи, сверяет план с текущим кодом, реализует, проверяет и обновляет статусы.
  3. Передача — только через файлы репозитория и Git. Память модели и чат не считаются общим контекстом.
  4. Если план устарел или требования изменились, Kimi останавливает зависимую часть, фиксирует причину и при необходимости возвращает задачу на анализ.
  5. Нельзя удалять чужие задачи, невыполненные пункты и историю статусов.

Подробный процесс — 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.

Факты:

  1. Скрытые карты с InstanceId = 0 пересоздаются. В GameBoard3D.AddOpponentHandSpecs (OH:player:i) и GameBoard3D.AddDamageSpecs (D:player:i) для скрытых карт BoardCardRef.InstanceId равен 0. В GameBoard3D.Render распределение старых карт идёт только через previousByInstance и условие spec.Ref.InstanceId != 0. Поэтому при каждом Render такие карты арендуются заново, старые освобождаются, а новые получают moved = true и анимацию из SourcePose.
  2. BattleView.Render() вызывается не только на снапшот. Локальные действия (выбор guard, меню карты, подтверждение, статус) тоже вызывают BattleView.Render → GameBoard3D.Render. В сочетании с п.1 это даёт «ненужные анимации» на каждый клик.
  3. SourcePose всегда использует колоду. Метод GameBoard3D.SourcePose возвращает _layout.DeckPosition(localSeat) и базовый yaw без состояния лица. Новая карта, пришедшая из Soul, Drop, Guardian, Trigger или Damage, начинает полёт из колоды; карта из колоды начинает путь лицом вверх, хотя должна быть рубашкой вверх.
  4. У Damage ключ по индексу, а не по личности. D:player:i остаётся тем же при смене открытой карты или при открытии закрытой. Текущий branch sameInstance освобождает старую карту и создаёт новую с SourcePose (колода), хотя это может быть переворот/смена карты на месте.
  5. Нет coalescing в CardMoveAnimator. Enqueue не отменяет старый queued/active request той же Card3D. Если карта успевает сменить зону до старта предыдущего запроса, второй запрос может стартовать из устаревшей позиции; активная запись при этом может остаться в _active.
  6. Card3D.SetPose не является точкой начала движения. Для существующей карты Render вызывает SetPose (меняет target), затем Enqueue; для новой карты PlaceImmediate + SetPose. При повторном Render target может быть обновлён до/после старта, а старый arc не всегда согласован с новым request.
  7. Конфиг анимаций не синхронизирован с кодом. В AnimationConfig.asset нет новых полей moveDuration, importantMoveDuration, moveDelayBetweenCards, arcHeight, camera/arrow полей, хотя они объявлены в AnimationConfig.cs. Нужно проверить фактические значения после импорта Unity; нельзя считать asset настроенным, пока поля не сериализованы.
  8. UpdateHandView каждый кадр делает SetPose для руки. Это может перетирать target во время движения карты в руку и мешать стабильности arc; нужно проверить и, если подтверждено, не менять target у карты, которая уже летит, либо синхронизировать target с move request.
  9. Пул карт и InputLock уже имеют защиту (CancelCard, таймаут, CancelAll); её нельзя удалять. Исправления должны сохранить отсутствие вечного card-move-лока.

Что сохранить:

  • GameBoard3D как единственный рендер-дифф и владелец пула;
  • CardMoveAnimator как единый планировщик;
  • Card3D как визуальную карту и arc-интерполяцию;
  • InputLockService и существующие long-running последовательности;
  • серверный snapshot как источник состояния.

C. Чек-лист изменений

  • C1. MODIFY GameBoard3D.Render — стабильная идентичность анонимных карт.

    • Файл: client/Assets/Vanguard/Runtime/Board3D/View/GameBoard3D.cs.
    • Добавлена ветка reuse-by-key до поиска по InstanceId.
    • Переиспользуется Card3D по spec.Key, если старая/новая карта анонимна или совпадают реальные InstanceId; иначе используется previousByInstance или RentCard.
    • При смене 0→real в том же слоте карта сохраняется и обновляется Bind/SetArt/SetPose без анимации.
    • Карты с исчезнувшими ключами освобождаются через ReleaseCard.
    • Реализация: выполнена.
    • Проверка: Repeated_Render_Reuses_Hidden_Cards_And_Does_Not_Enqueue_Moves, Revealing_Face_Down_Damage_Reuses_Card_And_Stays_In_Damage_Zone — проходят.
    • Зависит от: C2, C4.
  • C2. MODIFY GameBoard3D.AddOpponentHandSpecs / AddDamageSpecs — стабильные ключи и явная политика анимации.

    • Файл: client/Assets/Vanguard/Runtime/Board3D/View/GameBoard3D.cs.
    • Ключи OH:player:i и D:player:i оставлены; InstanceId=0 не считается личностью.
    • Скрытые карты при стабильном ключе не анимируются; новая скрытая карта в конце анимируется из Deck.
    • Для урона: смена 0↔real или real→real в том же слоте обрабатывается reuse-by-key, а не полётом из Deck.
    • Реализация: выполнена.
    • Проверка: см. C1 + Board_Renders_Field_Hand_Guardian_Damage_And_Soul.
    • Зависит от: C1.
  • C3. MODIFY GameBoard3D — карта источника из предыдущего MatchView.

    • Файл: client/Assets/Vanguard/Runtime/Board3D/View/GameBoard3D.cs.
    • Сохраняются _previousView и _sourceByInstance до замены _view.
    • Построен Dictionary<int, CardSourcePose> для Soul, Drop, FaceUpDamage; источники Hand/Guardian/Trigger/Field покрываются существующими Card3D через previousByInstance.
    • ResolveSourcePose(spec): приоритет — Card3D (transfer), затем зона из _sourceByInstance, затем DeckPosition с рубашкой вверх.
    • Источники:
      • Deck: DeckPosition, рубашка вверх (baseYaw * X-flip 180°);
      • Soul: SoulPosition, лицом вверх;
      • Drop: DropPosition, лицом вверх;
      • FaceUpDamage: DamagePosition, лицом вверх и на боку;
      • Hand/Guardian/Trigger/Field — через previousByInstance/transfer.
    • Fallback Deck используется только с комментарием и покрывается тестами.
    • PlaceImmediate получает rotation с учётом flip.
    • Реализация: выполнена.
    • Проверка: New_Field_Unit_Starts_From_Hand_Not_Deck — проходит.
    • Зависит от: C1, C2.
  • C4. MODIFY CardMoveAnimator.Enqueue — один активный/queued request на карту.

    • Файл: client/Assets/Vanguard/Runtime/Board3D/Animation/CardMoveAnimator.cs.
    • В начале Enqueue вызывается CancelCard(card), что удаляет queued/ active request этой же карты, не трогая другие.
    • Источник (from*) передаётся из GameBoard3D как текущая визуальная позиция карты; target проверяется в Card3D.StartArcMove.
    • CancelCard, CancelAll, timeout и снятие card-move-лока сохранены.
    • Реализация: выполнена.
    • Проверка: CardMoveAnimator_Coalesces_Requests_For_Same_Card, CardMoveAnimator_Releases_Input_Lock_When_Card_Is_Cancelled — проходят.
    • Зависит от: C1.
  • C5. MODIFY GameBoard3D.Render + BattleView.Render — явно различать snapshot-рендер и локальный UI-рендер.

    • Файлы: GameBoard3D.cs, client/Assets/Vanguard/Runtime/UI/BattleView.cs.
    • GameBoard3D.Render получил параметр bool animateMoves = true без изменения DTO.
    • BattleView.Render(bool animateMoves = true); OnSnapshot вызывает Render() (true); все локальные вызовы заменены на Render(animateMoves: false).
    • При animateMoves: false move-анимации не запускаются; идентичность карт не меняется. Появление реально новой карты логирует warning.
    • Реализация: выполнена.
    • Проверка: Repeated_Render_Reuses_Hidden_Cards_And_Does_Not_Enqueue_Moves, ClientSmokeTests.Battle_Renders_3D_Damage_And_Rested_Cards — проходят.
    • Зависит от: C1, C2.
  • C6. MODIFY GameBoard3D.UpdateHandView — не перетирать движение руки.

    • Файл: client/Assets/Vanguard/Runtime/Board3D/View/GameBoard3D.cs.
    • В UpdateHandView добавлено условие: пропускать карту, если card.IsMoving, чтобы SetPose не менял target во время arc-перехода.
    • GetHandViewPose и привязка руки к камере не изменены.
    • Реализация: выполнена.
    • Проверка: Hand_Is_Anchored_To_Camera_Bottom_Center_And_Faces_Player, New_Field_Unit_Starts_From_Hand_Not_Deck — проходят.
    • Зависит от: C3, C4.
  • C7. MODIFY AnimationConfig.asset + проверка значений.

    • Файл: client/Assets/Vanguard/Resources/AnimationConfig.asset.
    • Добавлены/сериализованы все поля из AnimationConfig.cs: moveDuration: 0.5, importantMoveDuration: 0.9, moveDelayBetweenCards: 0.18, arcHeight: 0.45, cameraZoomDuration: 0.35, triggerRevealHold: 1.2, cameraReturnDuration: 0.4, cameraRezoomDuration: 0.3, growDuration: 0.35, dashScrollSpeed: 1.5, boostGrowDuration: 0.3, boostThickness: 0.8, arrowFadeDuration: 0.2.
    • Длительности feedback не менялись.
    • Реализация: выполнена.
    • Проверка: CardOverlayTests.Animation_Config_Asset_Is_Loaded_From_Resources, поле arcHeight читается в GameBoard3D.Render — проходят.
    • Зависит от: C5 (можно параллельно).
  • C8. TEST GameBoard3DTests — регрессии анимаций.

    • Файл: client/Assets/Vanguard/Tests/Editor/GameBoard3DTests.cs.
    • Добавлены тесты:
      1. Repeated_Render_Reuses_Hidden_Cards_And_Does_Not_Enqueue_Moves;
      2. Revealing_Face_Down_Damage_Reuses_Card_And_Stays_In_Damage_Zone;
      3. New_Field_Unit_Starts_From_Hand_Not_Deck;
      4. CardMoveAnimator_Coalesces_Requests_For_Same_Card.
    • В CardMoveAnimator добавлены read-only seam-свойства: PendingCount, ActiveCount, IsCardQueued, IsCardActive.
    • Исправлен ClientSmokeTests.Battle_Renders_3D_Damage_And_Rested_Cards для вызова Render с новой сигнатурой через reflection.
    • Реализация: выполнена.
    • Проверка: все новые тесты + старые — 46/46.
    • Зависит от: C1–C6.
  • C9. VERIFY smoke / compile / EditMode.

    • Скопирован проект без Library; Unity 6000.6.6f1 -batchmode -nographics -runTests -testPlatform EditMode.
    • EditMode: 46/46 passed (включая 15 GameBoard3DTests, 12 ClientSmokeTests, 6 CardOverlayTests, 12 PlaymatLayoutTests, 1 Addressables stub).
    • PLAYMAT_SMOKE success=True board=True cards=25 hand=5/5 fieldVisible=True.
    • SCENE_SMOKE success=True active=Login env=True login=True.
    • Ошибок компиляции нет; warning CS8632 в ClientSmokeTests — pre-existing.
    • Реализация: выполнена.
    • Проверка: выполнена.
    • Зависит от: C1–C8.
  • C10. VERIFY ручная проверка пользователем.

    • Пользователь проверяет: draw, call, ride, move/swap, guard, trigger, drive/damage, retire, search, открытие скрытого урона, руку противника, повторные клики по UI во время/после анимации.
    • Ожидаемо: нет полёта скрытых карт из колоды при каждом клике; источник совпадает с зоной; нет рывков/двойных стартов; ввод не зависает.
    • Реализация: не применима.
    • Проверка: остаётся на пользователе.
    • Зависит от: C9.

D. Последовательность выполнения

  1. C1 + C2: стабилизировать идентичность/ключи, убрать пересоздание скрытых карт. Без этого остальные исправления будут маскировать симптом.
  2. C3: добавить source map и корректные source pose/face. Проверить Deck, Hand, Trigger, Damage, Soul/Drop/Guardian.
  3. C4: coalescing в аниматоре и отмена старого request.
  4. C5: разделить snapshot/UI-рендер, убрать лишние запуски.
  5. C6: проверить hand target; не менять без подтверждённой необходимости.
  6. C7: синхронизировать asset.
  7. C8: тесты; C9: проверки; C10: ручная проверка пользователя.

E. Проверки

  • Client EditMode: GameBoard3DTests + новые тесты из C8.
  • Compile/Assembly: Unity batch mode в копии без Library.
  • Smoke: Vanguard.Editor.SceneSmoke.RunPlaymat; Run только при изменении сцен/сборки.
  • git diff --check, просмотр git diff, отсутствие изменений в Rules/Server/Shared/Content.
  • Повторный Render с неизменным view: число карт и move-request не растёт.
  • Повторный snapshot во время движения: нет двойного старта/сброса в источник.
  • Ручная проверка — пользователем; не заменять её автотестом.

Команды (подтверждены проектом):

# клиентская копия без Library
& 'C:\Program Files\Unity\Hub\Editor\6000.6.6f1\Editor\Unity.exe' `
  -batchmode -nographics -projectPath <copy> `
  -runTests -testPlatform EditMode -testResults <copy>\TestResults.xml

Серверные тесты не требуются, если не меняются Rules/Server/Shared; при изменении контрактов — только ssh home/Docker.

F. Риски и неизвестные вопросы

  • Анонимные карты: InstanceId=0 не является уникальной личностью; нельзя использовать его для глобального map. Нужны стабильные ключи по зоне/индексу и отдельная политика для reveal.
  • Источник вне видимых зон: Deck не содержит InstanceId; для карты из колоды источник всегда Deck. Для карты из Soul/Drop/Guardian/Trigger/Damage источник можно восстановить из previous view; иначе fallback Deck.
  • Смена открытого урона: при перестановке face-up damage возможна неоднозначность. Не анимировать перемещение между слотами урона, если это не подтверждено; приоритет — не «телепорт из колоды».
  • AnimationConfig.asset: после добавления полей Unity может сохранить старые значения; проверить Inspector, не считать defaults автоматически настроенными.
  • UpdateHandView: если менять target каждый кадр, можно ухудшить плавность; C6 требует подтверждения тестом/наблюдением, а не слепой правки.
  • Не менять CardMoveAnimator timeout/CancelCard/CancelAll без сохранения защиты от зависшего InputLock.
  • Не расширять задачу на рефакторинг BattleView или замену animation stack.

G. Результат

  • Реализовано: C1–C8 (см. чек-лист).
  • Проверки: EditMode 46/46 в копии без Library; компиляция без ошибок.
  • Не завершено/заблокировано: C10 — ручная проверка пользователем.
  • Skill обновлён: не требуется; изменения технические, не затрагивают подтверждённые правила/архитектуру.
  • Git: будут изменены GameBoard3D.cs, CardMoveAnimator.cs, BattleView.cs, AnimationConfig.asset, GameBoard3DTests.cs, ClientSmokeTests.cs, IMPLEMENTATION_PLAN.md, docs/SESSION-STATE.md, docs/HANDOFF.md.