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, не начата
  • Проверка: не выполнена
  • Затронутые системы: 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.
    • В CardAssignment добавить признак ReuseByKey/StateOnly (имя выбрать по стилю файла).
    • Если _cards уже содержит spec.Key — переиспользовать эту Card3D даже при InstanceId == 0; не вызывать RentCard.
    • Для InstanceId == 0 не ставить moved = true только из-за отсутствия записи в previousByInstance.
    • При смене InstanceId 0→real в том же слоте (открытие урона) сохранить карту, обновить art/bind/face state; не отправлять её в полёт из колоды.
    • При исчезновении ключа освобождать карту как раньше.
    • Реализация: не выполнена.
    • Проверка: не выполнена.
    • Зависит от: 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 в том же слоте — обновление карты/переворот, не новый полёт из Deck.
    • Реализация: не выполнена.
    • Проверка: не выполнена.
    • Зависит от: C1.
  • C3. MODIFY GameBoard3D — карта источника из предыдущего 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.
    • Не придумывать источник, если InstanceId нет в previous view и он не Deck: fallback Deck допустим только с комментарием/тестом.
    • PlaceImmediate для нового источника должен получать rotation с правильным flip, а не только yaw.
    • Реализация: не выполнена.
    • Проверка: не выполнена.
    • Зависит от: C1, C2.
  • C4. MODIFY CardMoveAnimator.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. MODIFY GameBoard3D.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. MODIFY Card3D / 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. MODIFY AnimationConfig.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. TEST GameBoard3DTests — регрессии анимаций.

    • Файл: client/Assets/Vanguard/Tests/Editor/GameBoard3DTests.cs.
    • Добавить тесты:
      1. повторный Render с тем же view/state не увеличивает число Card3D для opponent hand/face-down damage;
      2. повторный Render не создаёт новый CardMoveAnimator request для стабильного ключа;
      3. открытие face-down damage переиспользует Card3D и не отправляет его из Deck;
      4. новый unit из hand сохраняет source-позицию руки, а не Deck;
      5. CardMoveAnimator coalesce: второй Enqueue той же карты не оставляет дубль в active/queue;
      6. CancelCard по-прежнему освобождает card-move-лок.
    • Для проверки source/queue при необходимости добавить тестовый seam (read-only snapshot последних запросов/позиций) в CardMoveAnimator или GameBoard3D, не меняя публичное поведение.
    • Реализация: не выполнена.
    • Проверка: не выполнена.
    • Зависит от: C1–C6.
  • C9. VERIFY smoke / compile / EditMode.

    • Запустить EditMode в копии проекта без Library.
    • PLAYMAT_SMOKE; SCENE_SMOKE — если затронуты сцены/сборка.
    • Проверить отсутствие ошибок компиляции и предупреждений о missing serialized fields.
    • Реализация: не выполнена.
    • Проверка: не выполнена.
    • Зависит от: 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. Результат

  • Реализовано: не начато.
  • Проверки: не выполнены.
  • Не завершено/заблокировано: ожидает этапа Kimi; критических вопросов пользователю на данный момент нет.
  • Skill обновлён: не требуется до подтверждения новых постоянных правил.
  • Git: изменён только план/документация, код не менялся.