# Передача сессии Состояние проекта на **28 октября 2026**. Документ нужен, чтобы новая сессия продолжила работу без повторного разбора истории. Краткая оперативная сводка — `docs/SESSION-STATE.md`; этот файл хранит подробную историю. --- ## 2026-10-31 — TASK-2026-023: переделка DeckBuilder - Один экран вместо двух: слева коллекция, справа панель «Мои колоды» ⇄ «Колода»; переключение без смены сцены. - Мультифильтры клана, грейда и триггера в модальном оверлее: чипы, черновик, «Сбросить», «Отмена», «Применить»; сводка в топбаре. - Список колод: имя, `N/50`, `T N/16`, `Classic`, `G0…G3`, бейдж «Готова / Не готова», подсветка открытой. - Список карт колоды: группировка по `nameId`, сортировка грейд → имя, превью-иллюстрация (верхние 59% карты) без искажений, имя, `×N`, G, триггер и клан. - Live-валидация 50/16/4 управляет «Сохранить», «В бой», «С ботом»; изменение имени помечает колоду как несохранённую. - Новые: `CardImageService.ApplyIllustration`, `DeckListFilter.cs` (`DeckFilter`, `DeckListModel`, `DeckValidation`), 5 тестов `DeckBuilderTests`. - Проверки: EditMode **67/67**, `SCENE_SMOKE success=True`, `PLAYMAT_SMOKE success=True`, standalone собран. - Ручная проверка (фильтры, панели, добавление/удаление, сохранение, «В бой»/«С ботом», ПКМ) — за пользователем. --- ## 2026-10-31 — TASK-2026-022: Lobby по референсу - `Lobby` переведён на 2D-дашборд: процедурный фон, топбар (инициалы, ник, MMR, сезон-заглушка), плашки BATTLE / DECK BUILDER / SHOP, миссии-заглушка, балансы, чат- и друзья-заглушки. 3D-окружение в `Lobby` больше не строится. - BATTLE открывает оверлей выбора колоды и режима (Ranked / С ботом) с серверной валидацией; Ranked уходит в существующую очередь. - Rating — заглушка лидерборда; Community — отдельная сцена с вкладками «Гайды / Колоды / Тикеты» (каркас). - Общее меню вынесено в `HamburgerMenu` (Login и Lobby); в Lobby добавлены «История матчей» и «Выйти». Esc закрывает меню и оверлеи. - `MetaView` удалён; онбординг в Lobby не показывается, выбор триал-деки остаётся отдельным экраном. - Проверки: EditMode **62/62**, `SCENE_SMOKE success=True`, `PLAYMAT_SMOKE success=True`, `WINDOWS_BUILD result=Succeeded`. - Визуальная приёмка и сетевые сценарии (очередь/бот, магазин, история, Community) — за пользователем. --- ## 2026-10-31 — TASK-2026-021: Login по референсу - Экран `Login` переведён на 2D key art (`Resources/Login/login_bg`) с неоновой панелью; 3D-окружение в этой сцене больше не строится (исключение из правила «3D в каждой сцене» для `Login`). - Новые компоненты: `NeonUi` (9-slice атомы, свечение, иконки), `LoginBackdrop`, `LoginMenu` (язык, версия, адрес сервера), `TabFocus`. - Tab/Shift+Tab переключают поля, Enter отправляет форму, Esc закрывает меню; в открытом меню Enter форму не отправляет. - `SceneSmoke` для `Login` требует `LoginBackdrop`, объект `Vanguard 3D Environment` — только для `Lobby`. - Проверки: EditMode **60/60** в копии без `Library`; `SCENE_SMOKE success=True active=Login env=False backdrop=True`; standalone собран. - Ручная сверка с референсом и поведение Enter/Esc — за пользователем. --- ## 2026-10-30 — реализацию выполняет DeepSeek - Решение пользователя: этап реализации больше не закреплён за Kimi. Оба этапа — анализ и реализация — выполняет DeepSeek, но раздельно: сначала план, потом код по нему. - Обновлены `AGENTS.md`, `docs/Development/AGENT-WORKFLOW.md`, `docs/Development/IMPLEMENTATION_PLAN.md` (процесс и шаблон задачи), `docs/SESSION-STATE.md`, `README.md` и Skill (`.opencode/skills/vanguard-remaster/SKILL.md`, `workflow-and-decisions.md`). - Двухэтапность и все запреты сохранены: анализ не меняет код, реализация не расширяет объём; `[x]` без проверки запрещён. - Исторические записи о Kimi (TASK-2026-001, раздел 2026-10-24) не переписывались. - Код, сцены, префабы и конфиги не затрагивались. --- ## 2026-10-28 — TASK-2026-018: карта из руки не двигается при выборе зоны - Для карты из руки зажатие ЛКМ больше не запускает обычный drag: включается `_handTargeting`, карта остаётся в hand-раскладке и hover-подъёме. - Курсор двигается по зонам, `UpdateHandTargeting` только подсвечивает валидную цель; цель берётся raycast-ом по карте/кругу, затем плоскостью. - На release над VC/RC отправляется `CardDropped`; невалидный drop оставляет карту в руке. - Карты поля сохраняют обычный drag с движением. - EditMode — **58/58**, standalone собран, клиент запущен. --- ## 2026-10-28 — TASK-2026-015: drag из руки, смена через VC - `Card3D.EndDragAtCurrent` фиксирует карту в точке drop-а без возврата в руку и сохраняет подъём/масштаб. - Для карты из руки `FinishDrag` больше не вызывает `ReturnToOrigin` до ответа сервера: карта ждёт снапшот и летит в VC/RC из точки drop-а. - `ReturnDroppedCard` снимает hold и плавно возвращает карту, если drop не стал интентом, сервер отказал или сработал guard-выбор. - Карты поля сохраняют прежний путь (возврат к source до снапшота). - Тест `Dropped_Hand_Card_Holds_At_Drop_Until_Returned`. - Карта из руки при зажатии ЛКМ не двигается: включается `_handTargeting`, карта остаётся в руке приподнятой, курсор выбирает зону; на release над VC выполняется смена. Карты поля сохраняют обычный drag. - EditMode — **58/58**, standalone собран, клиент запущен. --- ## 2026-10-28 — TASK-2026-014: подъём карты в руке при наведении - Своя карта под курсором плавно поднимается камера-вверх на 10% своей высоты (`handHoverLift = 0.1`); после ухода курсора плавно возвращается. - Hover работает только для своей руки и отключается при UI, input-lock, драге и анимациях раскладки/возврата/добора. - Плавность — существующий `Card3D.SetPose`/`Tick`. - Тест `Hand_Hover_Lifts_Card_By_Ten_Percent`; EditMode — **58/58**, standalone собран, клиент запущен. --- ## 2026-10-28 — TASK-2026-013: единый механизм раскладки руки - Раздельные staged-списки возврата/добора заменены единым `_visualHandCards` + `_handLayoutOverride`. - В визуальный список сразу входят оставшиеся карты руки и ожидающие возврата; новые добранные добавляются после завершения дуги. - После каждого возврата/добора список пересчитывается, поэтому остатки руки всегда участвуют в сдвиге; повторный Render не сбрасывает прогресс. - Новые карты при доборе скрыты и показываются по одной. - Тест `Mulligan_Remaining_Cards_Reflow_After_Each_Return`. - EditMode — **58/58**, standalone собран, клиент запущен. --- ## 2026-10-28 — TASK-2026-011: ступенчатый reflow руки при муллигане - При возврате нескольких карт рука больше не перестраивается один раз под финальное количество. - `_stagedReturnCards` хранит старые карты руки в порядке слева-направо, включая ожидающие возврата; новые карты в него не входят. - После завершения каждой карты она убирается из staged-списка, оставшиеся сдвигаются под новое количество; ожидающие возврата тоже перестраиваются. - Ожидающая карта получает цель под колодой только в момент старта своей анимации (`CardMoveAnimator.onStart` + `fromCurrent`). - Тест проверяет: до первого ухода рука не сдвигается, после первого — сдвигается; EditMode — **58/58**. --- ## 2026-10-28 — TASK-2026-010: рука перестраивается при выходе карты - При любом выходе карты из руки оставшиеся карты получают новые позы в том же `Render`, не дожидаясь окончания анимации уходящей карты. - Перекомпоновка `Hand → Hand` не ставится в `CardMoveAnimator` (`NeedsMove` → false), поэтому hand reflow идёт одновременно с movement. - Добавлен тест `Hand_Reflows_While_A_Card_Leaves_From_Hand`; тест возврата в колоду дополнительно проверяет сдвиг остальной руки. - EditMode — **58/58**. Ручная проверка — за пользователем. --- ## 2026-10-27 — TASK-2026-007: общий добор карты - Добор после муллигана приведён к виду добора в муллигане: карта стартует над колодой **полного размера** (`cardScale`), рубашкой вверх в ориентации будущей карты руки, летит в руку по дуге и переворачивается лицом вверх. - `GameBoard3D.GetDrawSource` — общий старт для `AnimateHandDraw` и `ResolveSourcePose`; новая карта руки больше не стартует в размере руки. - Длительность обычного добора — `introDrawDuration = 0.6 с`, как в муллигане. - Тест проверяет позицию над колодой и `cardScale` у новой карты. - EditMode — **58/58**, standalone собран, клиент запущен. Ручная проверка — за пользователем. --- ## 2026-10-27 — TASK-2026-006: возврат карт из руки в колоду - Добавлена анимация возврата: старая карта руки уходит по дуге **под низ колоды**, во время движения переворачивается рубашкой вверх; следующая карта стартует только после завершения предыдущей. - Старт — **справа налево**; у противника зеркально с его стороны поля. - `GameBoard3D.QueueHandReturnToDeck` получает `MulliganView` и `CardReturnedToDeckView` из `BattleView`; старые карты определяются по исчезновению из руки, вынимаются из `_cards`, анимируются и возвращаются в пул после завершения. - `CardMoveAnimator.Enqueue` получил необязательный `onCompleted`; возврат использует `sequential: true`. Ожидающие карты удерживаются в руке через `HoldAtSource`, поэтому не уезжают под колоду параллельно. - Длительность возврата — общая с добором: `DrawDuration` (`introDrawDuration = 0.6 с`). - Тесты: `Hand_Return_To_Deck_Starts_Right_To_Left_And_Targets_Deck_Bottom`, `CardMoveAnimator_Runs_Sequential_Requests_And_Completes_Each`. - EditMode — **58/58**, standalone собран, клиент запущен. Ручная проверка — за пользователем. --- ## 2026-10-27 — TASK-2026-005: направление набора руки - Своя рука снова набирается **слева направо**: первая карта слева, новая добавляется справа и остаётся поверх. - Рука противника зеркальна с его стороны поля: на экране набирается **справа налево**, новая карта появляется слева. - `GetHandViewPose` / `GetOpponentHandViewPose` возвращены к зеркальным формулам; тест `Hands_Fill_Mirrored_And_Newest_Is_On_Top` обновлён под новое направление. - EditMode — **51/51**, standalone собран, клиент запущен. Ручная проверка порядка набора — за пользователем. --- ## 2026-10-27 — TASK-2026-004: удаление боевых баннеров - Удалён центральный feedback-блок `BattleView`: текст событий (`АТАКА`, `GUARD`, `DRIVE`, `TRIGGER`, `HIT/BLOCK`, `DAMAGE`, `HEAL`, `RETIRE`, `INTERCEPT`, `PERFECT GUARD`), полноэкранная вспышка, кнопка «Пропустить анимации», очередь `FeedbackStep` и `InputLock("feedback")`. - Удалены мёртвые `_turnBanner`/`_turnBannerText`. - Журнал событий, `Describe`, trigger-последовательность, верхний `ActionPrompt` и `StatusToast` не тронуты. - Паузы `eventPause`/`post*Pause` больше не применяются; темп держат `CardMoveAnimator` и trigger-последовательность. Поля `AnimationConfig` пока оставлены неиспользуемыми, чтобы не менять контракт ассета. - EditMode — **51/51**. Ручная проверка боевого темпа — за пользователем. - Инвентаризация анимаций: `docs/Development/ANIMATION-INVENTORY.md`. --- ## 2026-10-24 — TASK-2026-001: плавность карточных перемещений - Реализована стабильная идентичность анонимных карт (`InstanceId == 0`) в `GameBoard3D.Render`: скрытая рука противника и закрытый урон не пересоздаются на каждом render, а переиспользуются по стабильному ключу зоны/индекса. - Добавлен source map из предыдущего `MatchView` для зон без собственных `Card3D` (`Soul`, `Drop`, `FaceUpDamage`); fallback — колода с рубашкой вверх. Новые юниты из руки/поля/триггера/урона берут источник из фактической старой позиции. - `CardMoveAnimator.Enqueue` теперь отменяет queued/active запрос той же карты перед добавлением нового, исключая дубли и сброс в середину пути. - `BattleView.Render` разделён на snapshot-рендер (`animateMoves: true`) и локальный UI-рендер (`animateMoves: false`); все локальные вызовы обновлены. - `GameBoard3D.UpdateHandView` больше не перетирает target движущейся карты. - `AnimationConfig.asset` синхронизирован с кодом: добавлены длительности, stagger, arcHeight, camera/grow/dash-поля. - Добавлены EditMode-тесты: повторный render скрытых карт, открытие урона, source из руки, coalescing аниматора; добавлены read-only seam-свойства `PendingCount`/`ActiveCount`/`IsCardQueued`/`IsCardActive`. - Исправлен `ClientSmokeTests.Battle_Renders_3D_Damage_And_Rested_Cards` для вызова `Render` с новой сигнатурой через reflection. - Проверки: EditMode в копии без `Library` — **46/46 passed**; `PLAYMAT_SMOKE success=True`; `SCENE_SMOKE success=True`; компиляция оригинального проекта без ошибок. Сервер/правила/Shared/контент не затронуты. - Ручная проверка игровой механики (draw/call/ride/damage/etc.) остаётся за пользователем (C10). --- ## 2026-10-25 — TASK-2026-003: боевой HUD по мокапу - **HUD:** убрана постоянная правая панель действий и `PhasePanel`; подтверждения перенесены в нижний правый `ConfirmationBar`. Верхний HUD показывает `Turn`, текстовую фазовую шкалу `Standby › Main › Battle › End`, таймер и банк отдельно. Debug-панель (журнал) скрыта по умолчанию, открывается кнопкой «Показать/скрыть панель». - **Панели игроков:** новый `PlayerStatsPanelView` — `Hand/Deck/Damage/ Shield/Trigger` и кнопки `Drop/Soul`. `Deck` неинтерактивен; `Guardian` в бар не входит. `UpdateZoneStrips` переведён на новый view. - **Поле:** `BoardSlotView` получил тёмную пластину под рамкой; над зоной урона добавлен счётчик `x/6` (у противника зеркально к центру). `Card3D` получил рамку лица; статы вынесены под карту, крит показывается только при `>1`, вместо `P/C` — символы `▲/◆`. - **Триггеры:** `BattleView` всегда запускает trigger-зум на `DriveCheckedView` и damage-триггере; карты trigger-зоны скрыты до `ZoomCompleted`, затем показываются. При отсутствии триггера в зоне пишется «БЕЗ ТРИГГЕРА». Подписи распределения: `+5000`, `Крит +1`, `Поднять юнита`, `Взять карту`, `Лечить`. - **Дополнительно:** карты урона укладываются сверху вниз (у противника зеркально) с шагом 30% от ширины карты, новая карта приподнята по Y и рисуется поверх; trigger-карта повёрнута на 90° против часовой стрелки; на карте показываются имя и бейдж триггера; добавлен центральный баннер хода; no-trigger damage check определяется по `DamageTakenView` с подавлением повторного зума после trigger-карты. - **Правки по замечаниям:** имена карт на поле убраны; панель действий сжата до автоподстраиваемого столбца кнопок справа снизу; рука центрируется по экрану (`handViewportX = 0.5`); подсказка муллигана перенесена в верхний `ActionPrompt`, при выборе карт появляется кнопка «Заменить N карт»; центральный баннер хода убран; левый нижний бар — `Колода, Душа, Рука, Сброс`, без `Щит/Триг`, `Душа/Сброс` кликабельны. - **Триггеры:** фокус камеры всегда первый; после фокуса карта раскрывается анимацией, затем пауза 0,75 с, затем резолв. Выбор цели открывается после паузы. Сначала `+5000` на RC/VC, затем эффект: `Critical` — RC/VC, `Stand` — только повёрнутый RC, `Draw` — автоматически, `Heal` — выбор карты урона при условиях. Подсказки — в верхнем информационном блоке. Нетриггерная карта пропускается. - **Урон:** переворот face-up/face-down идёт вокруг длинной стороны. - **Tejas:** атака заднего ряда по колонке `V↔1`, `3↔2`, `4↔0`. Сервер пересобран и задеплоен на `ssh home`, контейнер `server` перезапущен. - **Основная фаза:** в панели подтверждений только кнопка «Боевая фаза»; «Закончить ход» и список всех действий скрыты, способности — из меню карты. - **Рамки карт:** тонкая чёрная рамка по краю карты; подсветка выбора ужата до размера карты и не перекрывает соседние карты. В руке рамки скрыты, карты лежат широким веером: своя рука заполняется слева направо, новая карта добавляется правее и перекрывает предыдущую; ширина веера сохраняется до 10 карт и плавно уменьшается начиная с 11-й. Рука противника — зеркальный камеро-зависимый веер ниже верхней панели: новая карта слева, наклон и глубина зеркальны. Позы обеих рук пересчитываются каждый кадр, поэтому добор не улетает к устаревшей цели; порядок закреплён тестом `Hands_Fill_Mirrored_And_Newest_Is_On_Top`. - **Стартовый Авангард:** до конца муллиганов закрыт (`MatchProjection` передаёт реальный `FaceUp`, без принудительного открытия); после обоих муллиганов открывается анимацией 0,8 с вокруг длинной оси слева направо (у противника зеркально), затем начинается первая фаза. На время переворота клиент блокирует ввод. - **Старт боя:** камера над стороной игрока лицом к столу, 1,5 с → дуга вниз на основную позицию → пауза 1,5 с → одновременный добор своей и чужой руки → после всех доборов появляется муллиган. Ввод заблокирован до конца интро. - **Взятие карт:** карта создаётся ровно над колодой рубашкой вверх и летит в руку по дуге с переворотом лицом вверх. Любой добор последовательный: следующая карта стартует только после завершения предыдущей. В стартовой раздаче своя и чужая руки идут парами, внутри каждой пары — по одной. - **Проверки:** EditMode 50/50; серверные `Vanguard.Rules` 155/155 на `ssh home`; standalone Windows собран, клиент запущен. Ручная приёмка — за пользователем. - **Частично:** bloom/свет, HUD-анимации; эвристика no-trigger damage check требует ручной проверки. --- ## 2026-10-25 — TASK-2026-002: боевой UX и правила - **D1–D2:** в `Card3D` добавлено состояние hold-at-source; пока запрос ждёт в очереди `CardMoveAnimator`, карта не плавно двигается к target. `GameBoard3D.Render` вызывает `HoldAtSource` после `PlaceImmediate` для перемещаемых карт; отмена/очистка снимает hold. - **D3:** `GameBoard3D.FinishDrag` больше не вызывает `DropAt` для server-authoritative перемещений; карта возвращается в source и ждёт снапшота, после которого идёт одна дуга. - **D4:** добавлены EditMode-тесты: queued-карта остаётся в source до старта arc, отмена queued-запроса возвращает нормальное движение, переключение видимости руки. - **D5:** в `Vanguard.Rules` добавлена `SameColumnAcrossPlayers` (3↔2, 4↔0, V↔1) и используется в legal intents/validation для атаки заднего ряда. Тесты Tejas обновлены: 3→2, 4→0, негативный тест на противоположную колонку. - **D6:** Perfect Guard переделан в стадийную способность: `Guard` размещает Sentinel без оплаты; в `TriggerFromCard` для Sentinel в `OnGuard` добавляется синтетическая опциональная способность с оплатой сбросом карты того же клана. Принятие способности открывает выбор из руки, отказ оставляет щит 0. `GuardCommand` больше не генерирует варианты с `DiscardPrintingId`; клиентская discard-подсказка перед размещением убрана. - **D7:** в `AnimationConfig` добавлены `eventPause`, `postDrivePause`, `postTriggerPause`, `postDamagePause` (по умолчанию 1.5 с); увеличены `moveDuration` (0.8), `importantMoveDuration` (1.3), stagger, camera durations и `triggerRevealHold` (1.5). `BattleView` ставит паузу после боевых событий. - **D8:** `TriggerCheckSequence` получил событие `FocusChanged`; `BattleView` подписывается и скрывает руку на trigger-зуме, восстанавливая при возврате камеры. `GameBoard3D.SetHandVisible` управляет видимостью своей руки. - **Проверки:** клиентские EditMode в копии без `Library` — **50/50 passed**; компиляция чистая. Серверные тесты (`Vanguard.Rules`) не запускались на рабочем ПК; проверка только на `ssh home`/Docker. - **Ручная проверка** draw/call из руки и души/Tejas/Perfect Guard/триггер-камеры остаётся за пользователем. --- ## 2026-10-23 — защита и оплата способностей - Панель «Фазы» переименована в «Действия»; защита перенесена туда. - Защита выбирается кликом/драгом по картам в руке и перехватчикам на поле. В панели одна кнопка: «Не защищаться» без выбора, «Защищаться» с выбранными картами. Щит выбранных карт сразу входит в `POWER vs SHIELD`; после подтверждения карты отправляются в Guardian, шаг защиты завершается. - Дубли карт в Guardian (`player.Guardian` + `Battle.GuardCards`) устранены дедупликацией по `InstanceId`. - Оплата активируемых способностей: сброс выбирается в руке, Counter Blast — кликом по урону. Доступные и выбранные карты цены подсвечиваются; оплата подтверждается кнопкой «Подтвердить оплату» в панели «Действия». Урон больше не подсвечивается вне режима оплаты. Правый клик отменяет выбор. - Призыв в занятый RC заменяет юнита: прежний уходит в Дроп; правило распространено на призыв из руки, души и колоды. Сервер задеплоен. - Клик по карте в руке открывает меню действий с валидными пунктами (Призвать / Сменить / Активировать способность / Подробнее); способность в руке (Gancelot) доступна как `FromHand`-активация. Списки всех смен убраны из панелей, смена/призыв — драгом. - Защита из руки ограничена рангом авангарда. - Draw-триггер после выбора цели силы сразу тянет карту; Heal после цели силы просит карту только в своей зоне урона и только при выполнении условий. - Tejas может атаковать задний ряд не только с RC, но и с VC (центр). - Перемещение RC с единственным доступным кругом выполняется без выбора цели. - Проведён аудит всех 66 карт с текстом: `docs/ability-implementation.md`. Исправлены Conroe AUTO, зоны у Maiden of Libra/Starlight/Hungry/Clay/ Joka/Rakshasa, запрет поддержки Alfred. - Тесты: сервер **149/149** правил + **45/45** серверных, клиент **38/38** EditMode; `SCENE_SMOKE` и `PLAYMAT_SMOKE` успешны. --- ## 1. Проект Фан-ремастер **Cardfight!! Vanguard** базовой эры (2011), формат **Classic**. MVP: бустер **BT01** + триал-деки **TD01/TD02** — 94 карты, 124 принта. Стек: - сервер — **.NET 10**, **Orleans** (опционально), PostgreSQL, Redis; - клиент — **Unity 6000.6.6f1**, URP, 3D-сцены (каркас готов); - лаунчер — **WPF** (ещё не начат); - контент — YAML + C#-скрипты карт; - транспорт — **WebSocket + MessagePack** (реализован в сервере и клиенте). Репозиторий: - ветка `master`; - remote `ssh://git@192.168.31.158:2222/mpakobecex/VanguardReMaster.git`; - актуальный HEAD смотреть через `git log -1`; - рабочее дерево чистое. Главные принципы: - сервер авторитарный, клиент тонкий; - скрытая информация (колода, рука противника, закрытый урон, seed) не уходит клиенту; - `Vanguard.Rules` — чистая библиотека без сети, БД и Orleans: команда на вход, события на выход; - каждый блок работы коммитится и пушится в `master`. --- ## 2. Текущее состояние | Проверка | Результат | |---|---| | Тесты сервера | **194/194** (149 правил/контрактов + 45 сервер) | | `validate` | 0 ошибок, 0 предупреждений | | `translations check` | 94/94 | | `stress 1000` | 1000/1000 партий, 0 ошибок | | Карты | **94/94** (66 с текстом + 28 ванильных) | | Протокол и контракты | `docs/protocol.md`, `Vanguard.Shared` готовы | | Сервер | сеть, матчмейкинг, PostgreSQL + Redis, Orleans, Docker | | Мета-API | JWT, коллекция, колоды, магазин, экономика, история, реплеи | | Клиент | Unity 6.6, 3D-сцены Login/Lobby/DeckBuilder/Battle, логин/коллекция/колоды/очередь/бой, 124 арта, RU/EN, анимации карт | | Лаунчер | не начат | Блок «Игровое ядро» из `IMPLEMENTATION_CHECKLIST.md` закрыт по его критериям: боты доигрывают 1000 партий, инварианты держатся, реплей воспроизводится бит-в-бит по хэшу событий. --- ## 3. Как проверить `dotnet` установлен в `%LOCALAPPDATA%\dotnet`, поэтому в PowerShell: ```powershell $env:PATH = "$env:LOCALAPPDATA\dotnet;$env:PATH" dotnet test server\Vanguard.slnx dotnet run --project server\src\Vanguard.Cli -- validate dotnet run --project server\src\Vanguard.Cli -- translations check dotnet run --project server\src\Vanguard.Cli -- stress 1000 ``` Ожидаемо: ``` Пройден! : не пройдено 0, пройдено 149 (Vanguard.Rules.Tests) Пройден! : не пройдено 0, пройдено 45 (Vanguard.Server.Tests) OK: ошибок 0, предупреждений 0 Переведено: 94/94 Партий: 1000, завершено: 1000, ошибок: 0 ``` Клиент (Unity): ```powershell & 'C:\Program Files\Unity\Hub\Editor\6000.6.6f1\Editor\Unity.exe' ` -batchmode -nographics -projectPath client ` -runTests -testPlatform EditMode -testResults client\TestResults.xml & 'C:\Program Files\Unity\Hub\Editor\6000.6.6f1\Editor\Unity.exe' ` -batchmode -nographics -projectPath client ` -executeMethod Vanguard.Editor.SceneSmoke.Run ``` Сервер локально: ```powershell dotnet run --project server\src\Vanguard.Server --urls http://localhost:5080 # GET http://localhost:5080/health ``` Тестовый стенд на `ssh home` (Linux, Docker): ```bash ssh home cd ~/VanguardReMaster # .env: VANGUARD_PORT=18080, VANGUARD_WS_URL=ws://192.168.31.158:18080/game docker compose up -d --build curl http://127.0.0.1:18080/health # остановить: docker compose down ``` `Storage:Provider=None` по умолчанию: локальный запуск без базы. Стенд использует `Postgres`, `Redis` и восстанавливает матчи после рестарта. Solution — **`server/Vanguard.slnx`**, не `.sln`. --- ## 4. Что сделано ### 4.1. Контент - 94 карты, 124 принта в `content/sets/BT01.yaml`, `TD01.yaml`, `TD02.yaml`. - Импортёр `tools/import_cards.py`. - Валидатор контента: щиты, Sentinel, параллельные арты, лимиты колоды. - Токенизация текстов, `content/keywords.yaml`. - Русские переводы `content/translations/ru.yaml` — 94/94. - Канон перевода `docs/translation-rules.md` v0.8. ### 4.2. Игровое ядро `server/src/Vanguard.Rules/Engine/GameEngine.cs`. Реализовано: - фазы `Mulligan → Stand → Draw → Ride → Main → Battle → End`; - стартовый Авангард, маллиган, добор; - райд, призыв, перемещение в пределах колонки (занятый круг меняется местами); - атака по Авангарду и переднему ряду Арьергарда; - поддержка, перехват, идеальная защита (Sentinel); - drive check и damage check с выбором целей; - триггеры: Critical, Stand, Draw, Heal; - победа/поражение, детерминированный RNG; - стоимости: Counter Blast, Soul Blast, сброс, поворот себя, уничтожение себя; - поиск в колоде без просмотра, поиск с призывом и с райдом; - просмотр верхней карты; - способности: `Triggered`, `Continuous`, `Activated`; - модификаторы силы/крита с источником и сроком жизни (`StatModifier`); - выдача и снятие способностей (`GrantedAbility`); - запреты (`CardRestriction`): не вставать, не атаковать, не поддерживать, не перехватывать, не быть выбранным; - снятие Сдерживания эффектом; - возврат карты с поля в колоду после боя и в конце хода; - способности из руки, сброса и круга защиты; - `AskUnitMultiChoice` для «до N» / «ровно N»; - `LegalCommands()` — все легальные команды текущего момента; - `GameInvariants.Check()` — проверка 50 карт, уникальных `instanceId`, урона ≤ 6. Триггеры (`AbilityTrigger`): `OnRideOver`, `OnPlace`, `OnBoost`, `OnBoosted`, `OnBoostedHit`, `OnDriveCheck`, `OnGuard`, `OnRetireFromRC`, `OnOpponentRetire`, `OnLeaveGuardian`, `OnPhaseStart`, `OnAttackDeclared`, `OnAttackHit`, `OnDamageTaken` (объявлен, но пока не вызывается — карт с ним нет). ### 4.3. Карты - **59** скриптов в `CardScripts.cs`, `CardScriptsBT01.cs`, `CardScriptsTD.cs`. - **6** карт Sentinel/Heal обрабатываются данными и движком без скрипта. - **Tejas** реализован флагом `snipeBackRow` в YAML. - **28** ванильных карт скрипта не требуют. - Итого **66/66** карт с текстом закрыты. ### 4.4. Реплей `server/src/Vanguard.Rules/Replay/`: - `CommandCodec` — команда в стабильную строку и обратно; - `MatchRecord` — seed, колоды, команды, хэш событий; - `MatchRecorder` — записывает только принятые команды; - `ReplayRunner` — воспроизводит и проверяет хэш; - `EventHasher` — FNV-1a по тексту событий. Запись переживает JSON и воспроизводится бит-в-бит. ### 4.5. Диагностика - `server/src/Vanguard.Rules/Diagnostics/GameInvariants.cs`; - CLI-команда `stress [партий]` — случайный бот, лимит 5000 шагов на партию. ### 4.6. Тесты `server/tests/Vanguard.Rules.Tests/GameEngineTests.cs` — **117** тестов: - механики фаз, боя, защиты, перехвата, триггеров; - все ключевые карты и стоимости; - 50 детерминированных партий случайного бота с инвариантами; - JSON-реплей полной партии с проверкой хэша. `server/tests/Vanguard.Rules.Tests/ProtocolTests.cs` — **12** тестов: - round-trip всех кадров клиента и сервера через MessagePack, включая вложенные union'ы `MatchView` и `EventView`; - отказ на пустом и слишком большом кадре; - соответствие интентов и `GameCommand` в обе стороны; - скрытие руки противника, колоды и закрытого урона; - отсутствие seed/RNG в DTO; - `LegalIntents` только у решающего игрока; - скрытие поиска в колоде от противника и сохранение глобальных номеров событий при фильтрации. ### 4.7. Сеть: протокол и контракты - `docs/protocol.md` — версия 1: WebSocket + MessagePack, handshake по одноразовому тикету, `intentId` и идемпотентность, `eventSeq` и resync, heartbeat, реконнект 0/2/10/30 с, таймер 90 с + банк 5 мин, авто-поражение через 5 минут отсутствия, таблица видимости зон. - `Vanguard.Shared` — мультитаргет `netstandard2.1;net10.0`: - `netstandard2.1` — DTO и MessagePack для Unity, без ссылки на правила; - `net10.0` — дополнительно проекция состояния поверх `Vanguard.Rules`. - DTO: кадры клиента и сервера, 18 интентов, 30 событий, `MatchView`, `PlayerView`, `CardView`, `ClockView`. - `ProtocolResolver` — `StandardResolver` + интернирование строк; `MessagePackSecurity.UntrustedData`; лимит кадра 256 КиБ. - `MatchProjection` — отдельный `MatchView` на каждого игрока; `EventProjection` — фильтрация скрытых событий и диапазон по `eventSeq`; `IntentMapping` — единственное место связи DTO с командами движка. - Проекция вызывается для наблюдателя без побочных эффектов и не трогает `LegalCommands()` чужого игрока (иначе утекала бы рука решающего). ### 4.8. Сервер `server/src/Vanguard.Server` — ASP.NET Core 10, один процесс. Матчи, очередь и тикеты сохраняются в PostgreSQL/Redis, если они настроены; по умолчанию (`Storage:Provider=None`) сервер работает без внешних сервисов. - **HTTP**: `POST /api/game/queue`, `GET /api/game/queue/{id}`, `DELETE /api/game/queue/{id}`, `POST /api/game/tickets`, `GET /health`. Контракт — `docs/server-api.md`. - **Матчмейкинг**: две очереди (Classic Ranked / Casual), скрытый MMR, окно ±100 с расширением на 50 каждые 5 с до 2000, выбор ближайшего MMR. Ранкед меняет MMR по Эло (K = 32), casual — нет. - **Тикеты**: одноразовые, TTL 60 с, привязаны к `(playerId, matchId, seat)`; при настроенном Redis изымаются через `GETDEL` (одноразовость между инстансами), иначе — из памяти. - **`IMatchGrain`**: 1 матч = 1 экземпляр, все входы (интенты, подключения, тики) проходят через один семафор; внутри нет ни ASP.NET Core, ни WebSocket — только `IMatchConnection`. Orleans-хост (`MatchActorGrain`) подключается поверх того же контракта; соединения живут в `IMatchConnectionRegistry`, чтобы состояние грана оставалось сериализуемым. - **WebSocket** `/game`: handshake, MessagePack, `intentId`, idempotency, `eventSeq`, `resync`, heartbeat (20 с простоя → ping, ещё 20 с без ответа → `4408`), реконнект, захват сессии (`4409`), rate limit 16 интентов/с. - **Таймеры**: 90 с на решение + банк 300 с, отсутствие 300 с → поражение; `ServerClock` каждые 5 с. - **Результат**: сдача, явный выход, таймер и отсутствие завершают партию через `GameEngine.Finish(winner)`; событие `GameFinished` уходит обоим, результат пишется в профили. - **Проекция**: своя рука, свой выбор, свои легальные интенты; у противника только количества и открытые зоны. `seed` и RNG не покидают сервер. - **PostgreSQL**: таблицы `matches` и `match_intents`. В заголовке — seed, колоды, игроки, часы, банки, статус; в журнале — `intentId`, команда, результат. При старте незавершённые матчи восстанавливаются повтором принятых команд на детерминированном движке, включая ожидания выбора: делегаты движка не сериализуются. Журнал реплея сверяется по `eventSeq`. Банк и дедлайн пишутся каждые ~5 с; потеря при аварии — не больше тика. Схема — EF-миграции (`Migrations/InitialCreate`), на старте `Migrate`. - **Redis**: тикеты и записи очереди (JSON + sorted set по режимам, TTL); очередь поднимается при старте. Ключи с префиксом `vanguard:`. - **Конфигурация**: `Storage:Provider` = `None` / `Memory` / `Postgres`, `Storage:ConnectionString`; `Redis:Configuration`. `Memory` — EF InMemory для тестов, `Postgres` — постоянное хранилище. - **Orleans**: `Orleans:Enabled` (`false` по умолчанию, `true` в compose). Включённый режим поднимает silo с localhost-clustering в том же процессе (`Orleans:SiloPort` / `GatewayPort`, по умолчанию 11111/30000); `MatchActorGrain` держит доменный `MatchGrain`, `IMatchConnectionRegistry` остаётся в процессе. Активный матч держится тикером, завершённые матчи выгружаются после `CompletedMatchTtlSeconds`; страховочный CollectionAge 12 часов. Один silo на инстанс; для нескольких реплик нужен настоящий кластер и внешний реестр соединений. Тест кластера — `OrleansMatchTests`. - **Docker**: `Dockerfile` + `docker-compose.yml` поднимают postgres, redis (AOF) и сервер; `/health` на `http://localhost:8080` (порт меняется через `VANGUARD_PORT`, адрес WebSocket — `VANGUARD_WS_URL`). - **CI**: `.gitea/workflows/ci.yml` — restore, тесты, `validate` и `translations check` на push/PR. Actions включены для репозитория, `home-runner` выполняет job (проверено: все шаги зелёные). `setup-dotnet` указан явным URL с GitHub: в зеркале `code.forgejo.org` его нет. - **Тесты серверного ядра** (36 из 45 в `Vanguard.Server.Tests`): - тикеты: одноразовость и TTL; - heartbeat: ping после 20 с простоя, закрытие через 20 с, сброс любым кадром; - grain: attach, чужое место, duplicate intent, чужой ход, сдача, явный выход, команды после финала, банк времени, отсутствие, старт часов, reconnect с `eventSeq`, захват сессии; - матчмейкинг: подбор, разные очереди, окно, плохая колода, отсутствие стартового Авангарда, отмена, замена очереди, истечение, MMR, перезагрузка очереди из хранилища; - персистентность: EF-хранилище, восстановление матча повтором команд, `MatchRecoveryService` после «рестарта»; - внешние хранилища (`ExternalStorageTests`, запускаются при `VANGUARD_TEST_POSTGRES` / `VANGUARD_TEST_REDIS`): реальные PostgreSQL и Redis, изъятие тикета, очередь; - E2E: два клиента играют полный матч по WebSocket и сходятся в победителе; обрыв и возврат по новому тикету с догоном состояния; - Orleans: грана инициализируется, принимает attach и интент, граница Orleans сериализуется (тестовый `TestCluster`); полный матч двух клиентов через HTTP/WebSocket при `Orleans:Enabled=true`. Проверено на `ssh home`: 132 + 45 теста, включая реальные PostgreSQL и Redis; `docker compose up` поднимает стенд, матч создаётся через HTTP-очередь, а после `docker compose restart server` восстанавливается (`Recovery: 1 of 1`). ### 4.9. Мета-API - **Аккаунты**: `register`, `login`, `refresh`, `logout`, `me`. BCrypt, JWT access 15 минут, refresh 30 дней с ротацией; в БД хранится хэш refresh-токена, старый после ротации отзывается. - **Онбординг**: одна из двух триал-дек, 50 карт в коллекцию и 5 токенов; повтор невозможен. Триал-деки — `content/trial-decks.yaml`. - **Коллекция**: по `printingId`, без верхнего предела; разбор C 5 / R 10 / RR 25 / RRR 100 пыли, крафт 20 / 40 / 100 / 400, SP нельзя. - **Колоды**: CRUD, валидация 50 карт / ≤4 по `nameId` / Sentinel / Heal / стартовый Авангард, проверка владения картами, лимит 30, шаринг по коду. - **Правило атаки**: атакуют только Вэнгард и Арьергард переднего ряда (слоты 3/4); задний ряд (0–2) — только поддержка/boost. Задний ряд также нельзя атаковать без `snipeBackRow`. `LegalCommands` не предлагает атаки из заднего ряда; добавлен регрессионный тест. - **Магазин**: бустер 100 Gold или 1 токен; вторая триал-дека 500 Gold; состав бустера 4 C + 1 R+ (R 70 / RR 22 / RRR 7 / SP 1) из `content/pack_odds.yaml`; duplicate protection по `printingId` с порогом 4. - **Экономика**: журнал транзакций, баланс вычисляется, `idempotencyKey` на всех покупках, разборе и крафте. - **Очередь с JWT**: `{ mode, deckId }`, игрок и колода берутся из токена и коллекции. Dev-режим `playerId + deck` остался только при `Game:AllowDevQueue=true`. - **Rate limiting**: 60/мин на auth, 300/мин на прочие мутации, плюс глобальный лимит по IP. ### 4.10. История матчей и реплеи - `MetaProfileService` после подтверждённого завершения матча пишет историю обоим игрокам, меняет MMR в ранкеде и начисляет Gold/XP. Победа 30 Gold / 150 XP, поражение 15 / 75; сдача, явный выход, таймер и отсутствие дают 0, но остаются в истории. - `GET /api/matches`, `GET /api/matches/{id}` — история игрока. - `GET /api/replays/{id}` восстанавливает матч из seed, колод и журнала принятых команд и отдаёт: - `events` — события, видимые этому игроку; - `frames` — `MatchView` до и после каждой команды. - Реплей собирается под зрителя: рука противника и закрытый урон скрыты, `legalIntents` пуст, часы обнулены. Смотреть может только участник. - Проверено на `ssh home`: живой Postgres, регистрация → онбординг → бустер за токен → колода → очередь → матч; `/api/replays/{id}` отдаёт 11 стартовых событий и кадр, рука противника пуста. После рестарта сервера матч восстановлен (`matches: 1`). Счётчик `Vanguard.Server.Tests` вырос до 45: добавлены auth, мета-профиль, мета-E2E (онбординг → бустер → колода → матч → история → реплей), проверки реплея, явного выхода и истории/реплея после выхода, матч против бота. --- ## 5. Структура и ключевые файлы ``` /server/src/Vanguard.Rules /Engine/GameEngine.cs основной движок (~2500 строк) /Engine/GameState.cs состояние, зоны, InTransit /Engine/GameModels.cs CardInstance, Step, Phase, PendingChoice /Engine/GameCommands.cs команды /Engine/GameEvents.cs события /Engine/Modifiers.cs StatModifier, GrantedAbility, CardRestriction /Engine/Abilities/Abilities.cs AbilityDefinition, ActivatedAbility, триггеры /Engine/Abilities/CardScripts.cs реестр Default + базовые карты /Engine/Abilities/CardScriptsBT01.cs /Engine/Abilities/CardScriptsTD.cs /Cards/CardDefinition.cs статы, Sentinel, Restraint, snipeBackRow /Decks/DeckValidator.cs правила колоды /Replay/ запись и воспроизведение /Diagnostics/GameInvariants.cs /server/src/Vanguard.Content загрузка YAML и валидация /server/src/Vanguard.Cli validate, stats, translations, stress, deck /server/src/Vanguard.Shared /Protocol/*.cs кадры, интенты, события, view, кодек /Projection/*.cs проекция состояния, фильтр событий, маппинг /server/src/Vanguard.Server /Program.cs ASP.NET Core, DI, WebSocket endpoint /Api/ очередь, тикеты, /health /Auth/ JWT, register/login/refresh, BCrypt /Meta/ коллекция, колоды, магазин, экономика, история, реплеи /Matchmaking/ очереди, MMR, Redis-хранилище очереди /Matches/ IMatchGrain, таймеры, тикер, регистр /Realtime/ WebSocket-обвязка, чтение кадров /Tickets/ одноразовые тикеты: память или Redis /Storage/ EF-хранилище, восстановление матчей /Profiles/ интерфейс MMR /Dockerfile /server/tests/Vanguard.Rules.Tests 132 теста /server/tests/Vanguard.Server.Tests 45 тестов, включая E2E и внешние хранилища /content/sets карты /content/trial-decks.yaml две триал-деки /content/pack_odds.yaml веса бустера /content/translations/ru.yaml переводы /docs/translation-rules.md канон перевода /docs/protocol.md протокол матча, версия 1 /docs/server-api.md HTTP-очередь, тикеты, /health /docs/HANDOFF.md этот файл /docker-compose.yml postgres, redis, server /.env.example VANGUARD_PORT и VANGUARD_WS_URL ``` --- ## 6. Принятые решения Их не нужно пересматривать без причины. - **Общение:** ответы короткие, без повторов и длинных вступлений. - **Сомнения:** любое сомнение/вопрос — диалог с 2–4 вариантами; выбор делает пользователь. Не угадывать. - Формат называется **Classic**, не Standard. - Имена карт английские; `nameRu` зарезервировано. - Термины: Авангард, Арьергард, ранг, смена, поддержка, проверка, фазы Подъёма/Добора/Смены/Основная/Боевая/Завершающая, завершающий шаг, Сдерживание, «вы», буква ё. - Способности ищутся по `nameId`, а не по принту. - Лимит 4 копий — по `nameId`, только правило декбилдера. - Дубль в коллекции — по `printingId`; порог 4, замена внутри редкости. - Бустер: 5 карт = 4 C + 1 R+; веса R 70 / RR 22 / RRR 7 / SP 1. - Gold за матч: 30/15/20 (победа/поражение/ничья); Gold = XP / 5. - Таймер хода 90 с + банк 5 мин. - Транспорт матча — **WebSocket** + MessagePack; TCP с префиксом длины — резерв для Unity, если WebSocket не подойдёт. - Тикет на подключение — одноразовый, TTL 60 с, передаётся в `ClientHello`, не в URL; reconnect получает новый тикет. - `playerId` в HTTP-API — dev-заглушка до JWT. Выставлять сервер в интернет в таком виде нельзя. - Состояние сервера: PostgreSQL — снапшот/журнал матчей, Redis — тикеты и очередь; без конфигурации работает in-memory. `IMatchGrain` отделён от транспорта, Orleans подключается поверх и включается конфигом. - Rate limit — 16 интентов в секунду на соединение. - Скрытый MMR: старт 1000, Эло K = 32, casual рейтинг не меняет. - Аккаунты: BCrypt, access JWT 15 минут, refresh 30 дней с ротацией; в БД хранится только хэш refresh-токена. - Онбординг: одна триал-дека бесплатно + 5 токенов; вторая триал-дека за 500 Gold; бустер 100 Gold или 1 токен. - Коллекция по `printingId` без лимита; лимит 4 по `nameId` — только декбилдер. - Пыль: разбор C 5 / R 10 / RR 25 / RRR 100, крафт 20 / 40 / 100 / 400, SP не разбирается и не крафтится. - Матч: победа 30 Gold / 150 XP, поражение 15 / 75; Gold = XP / 5. Сдача, явный выход, таймер и отсутствие наград не дают. - История матча пишется обоим игрокам один раз; реплей доступен только участникам и собирается под зрителя, без руки противника. - Порядок триггеров выбирает игрок; опциональные способности спрашиваются. - Непрерывные эффекты пересчитываются при каждом запросе. - Поиск в колоде: 0 — уведомление и перемешивание, 1 — сразу, 2+ — выбор. - Эффекты не вкладываются: цепочка строго друг за другом. - Каждый блок — коммит и push в `master`. --- ## 7. Что осталось ### 7.1. Ближайший блок — сервер Сделано в этом блоке: 1. **[x] `docs/protocol.md`** — версия 1: кадры, handshake по тикету, `intent` с `intentId` и `eventSeq`, resync, реконнект 0 / 2 / 10 / 30 с, отсутствие 5 минут → авто-поражение, таймер 90 с + банк 5 мин, heartbeat, коды ошибок, таблица видимости. 2. **[x] `Vanguard.Shared`**: DTO интентов и событий, MessagePack-резолвер, проекция состояния на каждого игрока, фильтрация скрытых событий, маппинг интентов. 3. **[x] `Vanguard.Server`** — сетевой матч работает: ASP.NET Core + WebSocket, тикеты, `IMatchGrain` (in-process), матчмейкинг, MMR, идемпотентность, таймеры, реконнект, `/health`, Dockerfile и compose. Контракт HTTP — `docs/server-api.md`. 4. **[x] Проекция состояния** подключена к грану и проверена E2E-тестом: своя рука и свой выбор полностью, у противника только количества и открытые зоны, seed и RNG не уходят. 5. **[x] `Docker Compose`** — postgres, redis (AOF) и сервер; `/health`, порт через `VANGUARD_PORT`. Сервер запускается с `Orleans:Enabled=true`. 6. **[x] Персистентность**: - PostgreSQL — заголовок матча + журнал интентов, восстановление повтором команд после рестарта, `MatchRecoveryService`; - Redis — одноразовые тикеты (`GETDEL`) и очередь с перезагрузкой. Осталось в серверном направлении: 7. **Мета-API** — сделано: JWT, аккаунты, коллекция, колоды, магазин, экономика, онбординг, матчмейкинг по `deckId`, история и реплеи. `playerId` остался только как dev-режим при `Game:AllowDevQueue=true`. 8. **[x] Orleans** — `MatchActorGrain` + `OrleansMatchAdapter`; silo включается `Orleans:Enabled=true`, в compose включён. `MatchGrain` остался доменным ядром, соединения вынесены в `IMatchConnectionRegistry`, `MatchActorGrain` восстанавливается из снапшота на активации. Покрыто `OrleansMatchTests` (тестовый кластер) и `OrleansServerE2ETests` (полный матч через HTTP/WebSocket при включённом silo). Критерий готовности серверного блока выполнен: два клиента играют полный матч по сети; kill клиента восстанавливается через новый тикет и `resumeFromSeq`; рестарт сервера восстанавливает матчи из PostgreSQL. CI подключён и проходит; серверный блок закрыт. ### 7.2. Мета-API Сделано: - `User`, BCrypt, JWT (access 15 мин + refresh 30 дней, ротация). - `register`, `login`, `refresh`, `logout`, `me`. - Коллекция по принтам, без верхнего предела; разбор и крафт. - Колоды: CRUD, `validate`, лимит 30, шаринг по коду, проверка владения. - Магазин: бустеры, триал-деки, токены; duplicate protection. - Пыль: разбор C 5 / R 10 / RR 25 / RRR 100, крафт 20 / 40 / 100 / 400; SP не разбирается и не крафтится. - Онбординг: триал-дека + 5 токенов. - Матчмейкинг по JWT и `deckId`; dev-режим только для разработки. - Результат матча: Gold/XP, MMR, история, реплеи. - Rate limiting. Осталось после MVP: косметика, квесты, баттлпасс, Draft, друзья/чат, история покупок в клиенте. ### 7.3. Клиент Первый блок начат: **Unity 6000.6.6f1**, URP-проект в `client/`. Сделано: - проект создан из URP-шаблона Unity 6.6; пакеты: URP, Input System, Addressables, Localization, Newtonsoft.Json, VContainer (OpenUPM); - визуальное направление зафиксировано: **3D**, отдельные сцены-экраны Login/Lobby/DeckBuilder/Battle (`docs/client-visual-direction.md`); `DeckBuilder` совмещает коллекцию и колоды; UI пока code-generated gray-box; - сцена `Boot` + runtime-инициализатор, поэтому клиент стартует и в пустой сцене; - composition root на VContainer: `AppConfig`, `TokenStore`, `ApiClient`, `AuthService`, `MetaService`, `PlayerState`, `SceneFlow`; - HTTP-клиент на `UnityWebRequest` + Newtonsoft.Json, разбор `ApiError`, таймаут 20 с; - login/register/refresh/me/logout, восстановление сессии по refresh, сохранение токенов в `PlayerPrefs` (dev); - экран входа с выбором адреса сервера (стенд по умолчанию `http://192.168.31.158:18080`) и главный экран с балансами, MMR и переходами в разделы; - онбординг: список триал-дек из `/api/store`, выбор одной, 50 карт + 5 токенов, обновление профиля; - коллекция: `/api/collection`, балансы, разбор в пыль и крафт; ПКМ по карте открывает оверлей с текстом способностей; - колоды: `/api/decks`, список справа, редактор; карточная сетка коллекции с сортировкой клан→ранг→имя; - магазин в Lobby: `/api/store`, бустер за Gold/токен с показом 5 карт, покупка второй триал-деки; - история матчей и реплей: `/api/matches`, `/api/replays/{id}`, просмотр событий и кадров состояния; - очередь classic-ranked, опрос статуса, отмена, тикет и `wsUrl`; - `GameConnection` (WebSocket + MessagePack), handshake `ClientHello`, snapshot/events, интенты с ответом сервера, ping/pong, закрытие; - серый боевой экран: доска, рука, часы, все легальные интенты кнопками, сдача с подтверждением, результат; - `Vanguard.Shared.dll` (netstandard2.1) и MessagePack 3.1.12 подключены как плагины; `client/README.md` описывает запуск и ограничения; - сцены `Login`, `Lobby`, `DeckBuilder`, `Battle` и `SceneFlow`; `Boot` — стартовая служебная сцена, `VanguardApp` и сервисы живут между сценами; - 3D-фон на каждой сцене: камера, свет, пол и тематические примитивы; UI пока code-generated uGUI; - EditMode smoke `Vanguard.Tests.Editor` и `Vanguard.Editor.SceneSmoke` проверяют запуск Boot → Login и создание экранов/3D-окружения; live-probe (не входит в набор) проверил онбординг, коллекцию, создание/удаление колоды, а также два WebSocket-подключения, маллиган, сдачу и `GameFinished` против стенда. Дальше по клиенту: 1. Боевой UI: арты, зоны, анимации, скип/ускорение; реконнект и таймауты. 2. Полный декбилдер: фильтры, счётчики, валидация. 3. 3D-наполнение сцен: модели/арт, prefab-экраны, камера. 4. Локализация RU/EN, настройки, DOTween. **Зафиксированное визуальное направление** — `docs/client-visual-direction.md`: 3D-клиент на URP, отдельные сцены-экраны Login / Lobby / DeckBuilder / Battle; Boot и общие сервисы остаются персистентными. Сцены и `SceneFlow` уже созданы; UI пока code-generated gray-box. - Unity 6 LTS, URP, Addressables, Input System, Localization, DOTween. - VContainer, `ApiClient`, `GameConnection`, `SceneFlow`. - Сцены: `Boot` (служебная), `Login`, `Lobby`, `DeckBuilder`, `Battle`; очередь/онбординг — оверлеи Lobby. - История матчей и экран реплея: `GET /api/matches`, `GET /api/replays/{id}`, проигрывание `frames` + `events`. - Локализация RU/EN на лету. ### 7.4. Лаунчер - WPF: обновления, запуск клиента, выбор сервера. ### 7.5. Мелкие технические долги - `OnDamageTaken` не вызывается — добавить, когда появится карта с таким триггером. - Глобальный лимит шагов внутри движка не enforced — только в стресс-тесте. - `GameEngine.cs` большой; при росте разнести на partial-файлы. - `MatchRecord` сейчас JSON; для сети позже MessagePack. - В `LegalCommands()` не входят активируемые способности со стоимостью «сброс карты» — для UI их нужно будет перечислить с вариантами сброса. --- ## 8. Порядок действий в новой сессии 1. Прочитать `docs/SESSION-STATE.md`, `docs/HANDOFF.md`, `IMPLEMENTATION_CHECKLIST.md`, `README.md`, `docs/game-rules.md`. 2. Прогнать проверки из раздела 3. 3. Следующий блок — клиент: магазин/бустеры в Lobby и история/реплей, затем полный декбилдер и 3D-наполнение сцен. Сервер и мета-API готовы: `docs/protocol.md`, `docs/server-api.md`, `Vanguard.Shared`. 4. Перед началом сверить `docs/client-visual-direction.md`: 3D-сцены, `Boot` и общие сервисы персистентны. 5. После каждого блока: тесты, `validate`, `translations check`, коммит, push. 6. Не ломать `Vanguard.Rules`: сервер должен зависеть от него, а не наоборот. --- ## 9. Подводные камни - **Клиент: HTTP.** В Unity по умолчанию запрещены незащищённые соединения: для dev-стенда в `client/ProjectSettings/ProjectSettings.asset` стоит `insecureHttpOption: 2` (Always). Для релиза — HTTPS и вернуть запрет. - **Клиент: WebSocket.** `GameConnection` использует `System.Net.WebSockets.ClientWebSocket`; это окно для Windows-десктопа. Для WebGL/мобильного IL2CPP нужен отдельный транспорт/плагин. - **Кодировка.** Не использовать PowerShell `Get-Content`/`Set-Content` для UTF-8 файлов с русским текстом: однажды это уже испортило комментарии. Правки — только через `edit`/`write`, либо Python с `encoding="utf-8"`. - **PATH.** `dotnet` лежит в `%LOCALAPPDATA%\dotnet`; без добавления в PATH команды не находятся. - **Solution.** `server/Vanguard.slnx`, не `.sln`. - **Тесты.** `StartedEngine()` принудительно ставит Conroe стартовым Авангардом, чтобы Barcgal не срабатывал. - **Shared.** Мультитаргет: файлы `Projection/` компилируются только для `net10.0`; в `netstandard2.1` (Unity) ссылки на `Vanguard.Rules` нет. - **Видимость.** `LegalCommands()` описывает решающего игрока. Вызывать его для наблюдателя нельзя — в списке есть printingId из чужой руки. - **Сервер: rate limit.** 16 интентов в секунду на соединение. Быстрый бот в E2E специально притормаживается; живой UI в лимит не упирается. - **Сервер: хранилище.** По умолчанию `Storage:Provider=None`: матчи живут в памяти (`NullMatchStore`), мета-API — в EF InMemory. Для рестарта нужен `Postgres` + `Storage:ConnectionString`; `Memory` — EF InMemory для всего. Схема — EF-миграции (`Migrations/InitialCreate`), на старте `MigrateAsync`; для InMemory — `EnsureCreated`. Миграции применяются автоматически. - **Сервер: снапшот матча.** Сериализуется не состояние движка, а seed, колоды, игроки, часы и журнал интентов. При восстановлении команды проигрываются заново; `EventSeq` обязан совпасть, иначе матч пропускается с ошибкой. Делегаты `PendingChoice`/`Continuation` восстанавливаются самим реплеем. - **Сервер: восстановление.** `MatchRecoveryService` поднимает только незавершённые матчи за последние 7 дней. Завершённые остаются историей в PostgreSQL и не восстанавливаются в память. Банк/дедлайн пишутся раз в ~5 с, поэтому авария теряет не больше тика. - **Сервер: Redis.** Тикеты изымаются через `GETDEL` (Redis 6.2+; в compose 7). Очередь — JSON + sorted set по режимам, TTL в два раза больше очереди. Префикс ключей — `Redis:Prefix` (`vanguard:`). - **Сервер: Docker.** На домашнем сервере порт 8080 занят, поэтому стенд поднимается с `VANGUARD_PORT=18080` и `VANGUARD_WS_URL` в `.env`. `docker compose restart server` проверяет восстановление матчей. - **Сервер: внешние тесты.** `VANGUARD_TEST_POSTGRES` и `VANGUARD_TEST_REDIS` включают `ExternalStorageTests`; без них тесты проходят как no-op. Postgres-тест делает `EnsureDeleted`, поэтому БД должна быть тестовой. - **Сервер: тесты.** E2E поднимает `WebApplicationFactory`, играет полный матч по WebSocket и проверяет, что победитель совпал. Случайные партии завершаются за ~350 действий; таймаут теста — 120 с. - **Мета: JWT.** `Jwt:SigningKey` — не короче 32 символов; дефолтный ключ только для разработки. Access 15 минут, refresh 30 дней с ротацией; в БД лежит SHA-256 от refresh, старый токен после ротации недействителен. - **Мета: dev-очередь.** `Game:AllowDevQueue=true` разрешает очередь по `playerId` без JWT. В compose выключено; не включать в production. - **Мета: колода.** Сохранение проверяет не только правила Classic, но и владение каждой картой. Триал-дека даёт карты в коллекцию, а не готовую колоду — её собирают декбилдером. - **Мета: бустер.** 4 C + 1 R+, веса в `content/pack_odds.yaml`. Duplicate protection считает `printingId` и порог 4; при насыщении редкости выбирается другая, иначе допускается дубль. SP не крафтится и не разбирается. - **Мета: награды.** Gold/XP только за `normal`; сдача, явный выход, таймер и отсутствие дают 0. История пишется один раз на `(userId, matchId)`, повтор результата не дублирует валюту. - **Реплей.** Доступен только участнику; собирается из seed, колод и журнала принятых команд. `frames` — состояние после каждой команды, `events` — только видимые зрителю события; рука противника и закрытый урон скрыты. Сырые колоды и seed наружу не отдаются. - **Сервер: таймеры.** Тик раз в секунду. После 90 с банк списывается по фактическому превышению; `TurnDeadlineMs` в этот момент сдвигается, поэтому UI должен показывать ещё и `BankMs`. - **Сервер: часы.** Отсчёт стартует только когда подключились оба места. До этого `ClockView.IsRunning = false`, но таймер отсутствия уже идёт. - **Сервер: проекция.** `ServerSnapshot` и `ServerEvents` собираются отдельно на каждое место; не переиспользовать один `MatchView` на двоих. - **Порядок фаз.** Вход в Основную — `EnterMainPhase()` с `OnPhaseStart`. - **Отложенные продолжения.** `State.Continuation` — что выполнить после паузы; `State.PendingQueue` + `_resolvingDepth` запрещают вложенность эффектов. - **`OfferAbility`.** Возвращает `Skipped / Paused / Resolved`; вызывающий код продолжает очередь только при `Skipped`. Ошибка здесь уже приводила к потере шага защиты. - **`InTransit`.** Найденная карта, ждущая выбора круга, лежит в `State.InTransit`, иначе инвариант «50 карт» падает. - **Задний ряд.** Обычные карты атакуют только передний ряд; Tejas — задний в своей колонке через `snipeBackRow`. - **Реестр карт.** Новая карта без записи в `CardScripts.Default` молча останется без способностей; проверять покрытие скриптом. - **Тестовые колоды.** `BuildDeck()` в тестах не содержит все карты; для конкретной карты использовать `MakeCard()` и класть вручную. ## 2026-10-21 — декбилдер и каталог карт - Добавлен `GET /api/cards` (Bearer): все принты с `grade`, `trigger`, `clan`, `rarity`, `isSentinel`, `isParallel`, `basePrintingId`. Клиент объединяет каталог с коллекцией по `printingId`; правила для фильтров/статистики не дублируются в клиенте. - Новый `DeckBuilderView` в духе HS: - список: сверху фильтры/сброс/создание, в центре сетка коллекции (изображение-заглушка, количество), справа узкий вертикальный список колод; клик по колоде открывает редактор; - редактор: вместо создания — статистика по грейдам и общему числу, справа вертикальный список карт колоды; клик по карте коллекции добавляет, клик по карте колоды убирает одну копию; - сортировка коллекции: клан → ранг → имя. - Ограничения в UI: 50 карт, ≤4 по `nameId`, не больше owned-копий; серверная валидация остаётся источником истины. - Сцена **Collection** и отдельный `CollectionView` удалены; коллекция теперь на экране **Коллекция** внутри `DeckBuilderView`. - ПКМ по карте коллекции/колоды открывает оверлей: изображение-заглушка, статы, текст способности, разбор в пыль и крафт (C/R/RR/RRR; SP и TD нельзя). - `MetaService.CraftAsync`; `GET /api/cards` отдаёт `power`, `shield`, `critical`, `type`, `textEn/textRu` и флаги способностей. - Проверки: серверные 132 + 45 на `ssh home`; Unity EditMode 2/2, `SCENE_SMOKE success=True active=Login`; live `GET /api/cards` — 124 принта; live UI-проба: загрузка 50 карт, создание колоды, добавление/удаление, ПКМ-оверлей, текст способности и разбор карты; live API-проба крафта: разбор 4 C → 20 пыли → крафт C. - `POST /api/game/practice` и `BotMatchService`: серверный случайный бот подключается к своему месту через in-process snapshot-соединение, выбирает легальный интент и играет casual-матч. Практика не пишет историю, не меняет MMR и не выдаёт награды. - Клиент: кнопка «С ботом» в редакторе колоды; `MetaService.StartPracticeAsync`, переход в `Battle`. Серверный E2E-тест `PracticeBotE2ETests` проходит полный матч против бота. - Визуал: `CardArt` генерирует процедурные градиентные арты по клану и рубашку; `BattleView` показывает рамки, имена, ранги, триггеры, силу/крит, REST и скрытые карты; поля обоих игроков, рука и панель действий. Коллекция использует те же арты. - Реальные арты: `imageKey` в `/api/cards`, `CardImageService` читает `Resources/CardArt/{imageKey}`; скачаны все 124 арта. Импорт: `tools/import_card_art.py`. - Boost UI: клик по своему `SUP`-слоту выбирает поддержку, затем цель. - Event-фидбек: всплывающие атака/guard/drive/урон/heal/retire, не блокирует ввод; очередь событий и кнопка «Пропустить анимации». - Боевой UX: клик по карте/зоне, подсветка целей, отдельная группировка действий, выбор силы/эффекта триггера, журнал последних событий и подсказки выбора. - Поле: `R V R` / `R R R` у каждого игрока, зона урона слева, колода и дроп справа, зеркально у противника, зона защиты между полями. - Проверки визуального блока: Unity EditMode 2/2, `SCENE_SMOKE success=True`, live-практика с ботом на стенде. ## 2026-10-22 — перемещения карт и локализация RU/EN - Перемещения карт в бою: - при call/ride карта въезжает из руки (свой) или сверху (противник), с scale-in; - атакующий и цель пульсируют; - карта, ушедшая из слота в дроп, оставляет призрак: летит вниз/вверх, уменьшается и растворяется; для закрытой карты противника используется рубашка, лицо не раскрывается; - позиции карт снимаются после layout, поэтому анимации не зависят от внешних твинов; при новом snapshot анимации пересобираются. - Локализация: - `Localization.cs`: `Loc` + `LocalizedText`; русская строка — ключ, английский перевод — словарь + фразовые подстановки для динамических строк; `да/нет` переводятся по границам слов; - `UiKit.CreateText` автоматически вешает `LocalizedText`, поэтому существующие экраны переведены без переписывания; - переключатель `EN`/`RU` в Login и Lobby; выбор в `PlayerPrefs` (`vanguard.ui.language`), применяется на лету; - имена/тексты карт: `Loc.CardName` / `Loc.CardText` выбирают `nameRu/nameEn` и `textRu/textEn`; карточные поля помечены literal, чтобы словарь не портил переводы карт. - Проверки: Unity EditMode **4/4** (экраны, импорт арта, перевод статики и динамики, live-смена языка, рендер доски с уроном), `SCENE_SMOKE success=True active=Login`. - Ограничения: звука пока нет; полноценные drive/damage-анимации карт и стола — следующий блок; локализация сделана runtime-словарём, без Unity Localization-таблиц (пакет остаётся подключён). ## 2026-10-22 — боевые карты, маллиган, урон - Боевые карты переделаны: арт растянут на всю карту, сверху слот/грейд, снизу плашка с именем и P/C/триггером; в руке — компактный вариант. `CardImageService` проверен тестом: `td01_005` реально загружается. - Rest теперь визуально поворачивает карту на бок; удалён старый текстовый маркер `REST`. Позванные rear-guard'ы входят стоящими (`Call`, `CallFromSoul`, `CallFromDeck` исправлены на `Rested = false`). - Зона урона: отдельная сетка видимых карт на боку, до 6 штук; закрытые показываются рубашкой, открытые — артом. При выборе карты урона (heal или Counter Blast) карты подсвечиваются и кликаются. - Heal: сервер больше не забирает последнюю карту автоматически, а создаёт `ChoiceTargetKind.Damage`; клиент выбирает карту кликом. Counter Blast принимает `DamageIndex`/`DamageIndexes` в `ActivateAbilityIntent`; при multi-target используются выбранные открытые карты. `ResolveAbilityIntent` пока сохраняет одиночный `DamageIndex`. - Маллиган: клиент выбирает карты в руке, подсвечивает их и отправляет `MulliganIntent`; сервер возвращает выбранные в колоду, перемешивает и добирает столько же. После обмена доступно «Закончить маллиган». - Протокол `Vanguard.Shared` расширен (`DamageIndex`/`DamageIndexes`, `ChoiceTargetKind`), DLL пересобран и обновлён в `client/Assets/Plugins/Vanguard.Shared`. - Проверки: сервер **139** правил + **45** серверных; клиент EditMode **4/4** (включая рендер доски с уроном и отдыхающей картой); `SCENE_SMOKE success=True active=Login`; `validate` и `translations check` — без ошибок. ## 2026-10-22 — конспект правил - Источник: `https://www.cardfight.ru/avangard-pravila` (редакция 22.05.2019). - Создан `docs/game-rules.md`: форматы, колода, поле, карта, старт/mulligan, победа, фазы, бой, триггеры, стоимости, инварианты и отдельная таблица сверки с текущим кодом. - Расхождения из первой сверки исправлены: урон входит открытым, Counter Blast переворачивает открытое в закрытое; heal доступен при `>=`; mulligan — возврат в низ → добор → shuffle, один раз, оба игрока; в первый ход атака запрещена; первый игрок случайный (для тестов можно зафиксировать); `DeckValidator` требует ровно 16 триггеров, декбилдер показывает счётчик. - Карты на поле (`V`, `R`) и в круге защиты принудительно отдаются клиенту открытыми; в круге защиты дополнительно рисуется ряд карт. - Classic-режим не включает Stride/Вознесение, Legacy, Gift, Front-триггер и Grade support. ## 2026-10-22 — 3D Playmat - Боевое поле переведено в 3D: `Runtime/Board3D` (`Config`, `Data`, `Logic`, `View`), HUD/кнопки остаются uGUI. - Раскладка: локальный игрок всегда внизу; передний ряд `RC–V–RC`, задний `RC–RC–RC`; у противника зеркально. Между половинами — общий круг защиты (карты обоих игроков и текущий Guard). Душа — стопка под V, колода и дроп сбоку, урон — две колонки по три карты на боку. Карты в V/R всегда рисуются лицом вверх, даже если снимок пришёл с `FaceUp=false`. - `PlaymatLayoutConfig` (ScriptableObject): размеры карт, позиции рядов, высоты, рука, камера, палитра. Ассет: `Runtime/Board3D/Config/PlaymatLayoutConfig.asset`. - `PlaymatLayout` — чистая геометрия и поиск цели броска; `BoardPlacementRules` — Ride только на V, Call только на пустой RC, Move только в своей колонке (0↔3, 2↔4, занятый круг меняется местами), Guard — в общий круг. - `Card3D` — тонкая коробка: отдельные лицо/рубашка/торец, коллайдер, арт из `CardImageService`, переворот вокруг X, Rest вокруг Y по часовой стрелке, урон — против часовой, плавный снап. Карты пулятся (`GameBoard3D`), материалы общие, instancing включён. Рубашка — стандартная VF Vanguard `Resources/CardArt/vanguard_back.png` (источник: Card Sleeve Gallery, `Sleeve_06.png`), общая с 2D-UI; при отсутствии файла остаётся процедурный fallback. - `GameBoard3D` строит стол, круги, зоны, стопки и веер руки; raycast-клики, drag&drop на плоскость стола с подъёмом, подсветкой допустимых кругов и индикатором точки броска. Ошибка интента возвращает карту на место. - Камера фиксирована по центру поля: позиция `(0, 4.2, -8)`, цель `(0, 0, 0)`, FOV 46; наклон только по X, без бокового смещения, игрок её не двигает. Рука не лежит на столе, а закреплена в экране снизу по центру (`handViewportX/Y`, `handViewDistance`) и всегда развёрнута лицом к игроку; уменьшена, чтобы не перекрывать задний ряд RC. Драг из руки уходит на стол. - Статистика карт вынесена в отдельные UI-панели: своя — снизу слева, противника — сверху справа (`Рука/Колода/Дроп/Урон/Душа`); 3D-подписи стопок и зон больше не дублируют числа. - Колода и дроп поменяны местами: колода ближе к игроку. Пустой дроп не рисует рубашку; верх дропа показывает последнюю положенную карту, ЛКМ открывает список всего дропа. - Душа — это юниты под картой VC, отдельной стопки на поле нет. ПКМ по VC открывает список души; кнопка души из топ-бара убрана. - Над каждой колодой — триггер-зона (`TRIGGER`), куда выкладывается карта проверки эффекта/урона. Урон — один вертикальный ряд карт на боку. Под картами V/R — динамические `P/C`, жёлтые при изменённой силе/крите. - Справа от круга защиты — панель фаз: «Закончить муллиган» (авто после обмена), «Пропустить смену», «В бой», «Закончить ход». Боевая фаза завершается сама, если нет атак/активаций/выборов. - Event-фидбек guard/drive/intercept показывает имя карты и держится 1.5–2 с; добавлены паузы, чтобы успеть прочитать защиту бота. - Атака проходит при силе атакующего **>=** силы защищающегося. - Песочница: `Assets/Vanguard/Scenes/GameBoard3D.unity` + `PlaymatSandbox`; собирается `Vanguard → Rebuild Playmat Sandbox`, проверяется `SceneSmoke.RunPlaymat`. - Проверки: EditMode **22/22** (геометрия, зеркальность, правила, рендер 3D-карт, пул, рука в поле зрения, обмен колоды/дропа, триггер-зона), `SCENE_SMOKE success=True`, `PLAYMAT_SMOKE success=True cards=25 hand=5/5 fieldVisible=True`. - Ограничения: retire/ghost-анимации старого 2D-поля пока не перенесены; модели карт/стола — процедурные примитивы. ## 2026-10-23 — UX Phase 1: единые списки карт и зоны - Добавлен общий `CardListView` (Screen Space, горизонтальный ScrollRect, порядок «последняя добавленная первой») и `CardDetailOverlay` (арт, имя, клан/тип/грейд, P/Shield/C, триггер, текст). Навигация: `OverlayNavigationService` — стек «список → детали → назад», закрытие возвращает к списку. - Зоны стали кликабельными блоками UI: `Рука / Колода / Дроп / Душа / Урон / Защита / Триггер`; своя панель снизу слева, противника — сверху справа. 3D-стопки и правый клик по VC используют тот же `CardListView`. - Скрытые данные: колода — только закрытые слоты по количеству; рука противника — только рубашки; закрытый урон — «Закрытая карта». Рука противника дополнительно рисуется веером закрытых карт у дальнего края стола (вариант 1), identities не запрашиваются. - Старые `SoulOverlay`/`ShowDrop`/`ShowSoul` удалены; единый оверлей используется для Deck/Drop/Soul/Damage/Guardian/Trigger/Hand. - Проверки: клиент EditMode **22/22** (включая `CardListView`/Detail и зоны), `PLAYMAT_SMOKE success=True cards=25 hand=5/5 fieldVisible=True`. - Дальше по плану: Phase 2 — `InputLockService` + `AnimationConfig` + контекстное меню; Phase 3 — Power/Shield и стрелка; Phase 4 — Guardian стопка, Trigger mirror, debug-подписи. ## 2026-10-23 — UX Phase 2–4: ввод, контекст, бой и раскладка - `InputLockService` — reference-count по причинам (`feedback`, `board`, `overlay`, `card-menu`); ввод возвращается только после последней причины. `AnimationConfig` (ScriptableObject в `Resources/AnimationConfig.asset`): скорости карт, длительности фидбека/флипа/retire, хвост блокировки и стрелка. `Card3D` получает скорости из конфига, `GameBoard3D` блокирует доску на время снапа/переворота, `BattleView` — на время очереди событий и открытых оверлеев. Кнопки интентов/фаз и клики по столу не проходят, пока идёт анимация; «Пропустить анимации» остаётся доступной. - Контекстное меню `CardActionMenuView`: ЛКМ по своей карте V/R открывает список действий, отфильтрованных по `LegalIntents` (атака, атака с поддержкой, перемещение, способность, подробнее). Действия с целями переводят в режим выбора цели; карты противника и зоны открывают `CardDetailOverlay`/списки. Меню закрывается на новом снимке и держит `card-menu` в InputLock. - Боевая плашка `POWER X vs SHIELD Y` в верхней части экрана: сила атакующего против силы защищающегося (с учётом перехватчика) плюс `GuardShield`; пересчитывается на каждом снимке, `NULLIFIED` показывается отдельно. `AttackArrowView` рисует затухающую 3D-стрелку с наконечником от атакующего к цели; точки обновляются каждый кадр по трансформам карт, поэтому стрелка едет за анимацией. - Guardian-карты повёрнуты на 90° вокруг Y, уложены с перекрытием около 30% (spacing 0.62 при высоте 0.88), новые сверху за счёт микро-подъёма. Trigger-зона зеркальна по X и ориентации; 3D-подписи `DECK`/`DROP`/ `TRIGGER` скрыты флагом `showZoneLabels` (по умолчанию false). - Проверки: клиент EditMode **26/26** (InputLock, контекстное меню, стрелка атаки, загрузка `AnimationConfig`), `SCENE_SMOKE success=True`, `PLAYMAT_SMOKE success=True cards=25 hand=5/5 fieldVisible=True`. - Дальше: по желанию вынести overlay-экраны и меню в prefab-ассеты; проверить итоговые позиции на реальном разрешении и, при необходимости, подкрутить `AnimationConfig`/`PlaymatLayoutConfig`. ## 2026-10-23 — Overlay prefabs и проверка раскладки - `CardListView`, `CardDetailOverlay`, `CardActionMenuView` и `ZoneBlockStripView` теперь умеют брать статическую иерархию из `Resources/Overlays/*.prefab`; при отсутствии prefab-а сохраняется прежняя кодовая сборка. Динамические плитки/пункты/подписи по-прежнему создаются кодом. - `OverlayPrefabBuilder` (`Vanguard → Rebuild Overlay Prefabs`) собирает prefab-ассеты из тех же view-классов; `SceneBuilder.BuildAll` вызывает его автоматически. Runtime-загрузка — `OverlayPrefabs`. - Добавлен `Battle_Layout_Fits_Reference_Resolution`: проверяет блоки зон, панель фаз и боевую плашку на 1920×1080 и отсутствие пересечений; при другом разрешении CanvasScaler масштабирует reference-раскладку. - Проверки: клиент EditMode **30/30**, `SCENE_SMOKE success=True`, `PLAYMAT_SMOKE success=True cards=25 hand=5/5 fieldVisible=True`. - Дальше: только 3D-модели/наполнение сцен, анимации retire/ghost и декбилдер; prefab-оверлеи и раскладка закрыты. ## 2026-10-23 — Windows standalone - Добавлен `StandaloneBuild`: меню `Vanguard → Build Windows Standalone` и CLI `-executeMethod Vanguard.Editor.StandaloneBuild.BuildWindowsCli`. Собирает все включённые сцены в `client/Builds/Windows/VanguardReMaster.exe` (папка в `.gitignore`). - В `GraphicsSettings.m_AlwaysIncludedShaders` добавлены URP Lit/Unlit, чтобы runtime-материалы `SceneEnvironment`/`BoardMaterials` не теряли шейдеры при сборке. `Paint`/`CreateUnlit` теперь безопасны при отсутствующем шейдере. - Проверка сборки: Windows x64 build succeeded; при запуске с реальным D3D12-устройством исключений нет. `-nographics` для этого приложения не показателен: runtime-материалы требуют графического устройства. - Сервер для standalone по умолчанию — `http://192.168.31.158:18080`; его нужно поднять/держать доступным. ## 2026-10-23 — Исправления боя и камера - Первый ход: `EnterBattleCommand` больше не попадает в легальные интенты, пока действует правило первого хода; кнопка «В бой» не появляется. Атака по-прежнему запрещена. - Смена: в фазе Ride предлагается и карта того же ранга, и следующего (раньше legal-команды содержали только `+1`, хотя движок разрешал same grade). Кнопки «Сменить: карта» есть в панели действий и в «Фазах». - Маллиган: кнопка динамическая — без выбора «Закончить муллиган», с выбором «Заменить X карт». - Способности: `ActivateAbilityIntent` группируются по карте; в UI одна кнопка «Активировать способность». Если вариантов несколько — overlay выбора способности; карта для сброса выбирается кликом по руке. - Guard: варианты Perfect Guard/сброса показываются отдельным overlay; случайный первый интент больше не выбирается. В фазе защиты есть «Защититься» (при наличии допустимых карт) и «Не защищаться». Карта сброса выбирается кликом по карте в руке. - Правую панель действий можно временно скрыть/показать кнопкой в верхней строке, чтобы видеть поле. При скрытии панель фаз и UI противника сдвигаются вправо. - Кнопка муллигана в панели «Фазы» динамическая: без выбора — «Закончить муллиган», с выбором — «Заменить X карт». После обмена муллиган завершается автоматически, кнопка «Закончить муллиган» скрывается и не блокируется feedback/overlay-локом; при реконнекте авто-завершение взводится повторно из снапшота. - InputLock чистит зависшие причины, если owning-состояние уже неактивно (нет reconnect/overlay/ability/card-menu/feedback). Это убирает блокировку «Сменить» после хода противника, когда сервер уже прислал ride-интенты. Отдельно исправлено накопление `board`-лока: `GameBoard3D` больше не делает повторный `Acquire` на каждом снапшоте во время анимации. - Опциональные AUTO-способности (`PendingAbility`, например Barcgal после рейда на Marron) показываются модальным оверлеем с названием карты и кнопками «Активировать» / «Пропустить»; из боковой панели их кнопки убраны. После подтверждения призыва карта из души не ставится в первый свободный круг автоматически: сервер предлагает `AskEmptyRearGuardChoice`, клиент подсвечивает свободные RC и принимает выбор кликом по кругу, без списка кнопок. Боковая панель пока оставлена для остальных действий. - Зафиксировано правило взаимодействия: выбор карты/круга/зоны/цели — клик или drag по объекту на поле; кнопки — только глобальные действия и подтверждения (`AGENTS.md`, `docs/client-visual-direction.md`). - Добавлен общий верхний `ActionPrompt` с текущим обязательным действием: муллиган, смена, защита, бой, выбор карты/круга/цели, триггер, порядок триггеров и опциональная способность. Промпт информационный, не перехватывает клики; обновляется из снапшота и не показывается в ход противника. - Trigger-зона противника зеркальна: над его колодой в сторону центра, а не за дальним краем. - Карты на столе и в руке масштабируются общим `cardScale = 1.25` (+25%); drag сохраняет базовый масштаб. - Рука центрируется при скрытии боковой панели (`handViewportX = 0.5`); при показе возвращается к значению из layout-конфига. - Камера боя: `(0, 4.2, -8)` → цель `(0, 0, 0)`, только наклон по X; поле по центру, без бокового смещения. - UI: reference-разрешение **1920×1080**, CanvasScaler `matchWidthOrHeight = 0.5`; standalone принудительно `1920×1080 FullScreenWindow`, `defaultIsNativeResolution = 0`. - Потеря WebSocket: полноэкранный `ReconnectOverlay` блокирует бой, автоматические попытки 0/2/10/30 с, после `ServerHello` overlay скрывается; в overlay есть выход в меню. Игра работает в фоне (`runInBackground = 1`), чтобы heartbeat не останавливался при потере фокуса. - Диагностика Ride: `MatchGrain` логирует создание/attach, каждый полученный интент, результат, события и снапшот. В строке состояния есть active, turn, phase, step, printing/ранг вэнгарда, рука с рангами и `rideReady` (карты, которые сервер должен предложить в фазе Ride). Смотреть: `ssh home "docker compose -f ~/VanguardReMaster/docker-compose.yml logs -f server"`. - Боевые drag-жесты: задний ряд → свой передний круг/Авангард объявляет поддержку и ограничивает следующую атаку только поддерживаемой картой; передний круг → карта противника объявляет атаку. Поддерживать можно передний RC и Авангард (центральный задний круг). - Barcgal: добавлена ACT-способность `[Rest]` — поиск Llew/Flogal в колоде, выбор круга и призыв; AUTO-призыв Barcgal из души после рейда сохранён. Найденные карты показываются модальным оверлеем с карточными плитками (арт + имя), выбор — кликом по карте, без списка кнопок. - Morgana: после принятия опциональной способности карта для сброса выбирается кликом по карте в руке; доступные карты подсвечиваются, кнопок выбора в боковой панели нет. - Проверки: сервер **144** правил + **45** серверных, `validate` и `translations check` OK; клиент EditMode **36/36**. ## 2026-10-24 — 3D UX P0/P1 - Добавлены `CardMoveAnimator` (дуга, stagger, InputLock, diff по InstanceId) и расширенный `AnimationConfig`; новые карты/переходы больше не телепортятся. - Добавлены `CameraDirector` и `TriggerCheckSequence`: zoom → reveal → return, восстановление исходной камеры, владение камерой во время цепочки. - Trigger Zone: своя +90°, противника −90° относительно базы; Drop получил `DropZoneFaceDown`. - `ArrowDirector`/`ArrowConfig`: attack/boost стрелки, grow/arc/dash, наконечники у края карт. - Multi-target Counter Blast: `DamageIndexes` в `ActivateAbilityIntent` и `GameEngine.CounterBlast`; клиентский выбор нескольких карт урона и счётчик в панели «Действия». - Проверки: правила 151, сервер 45, клиентские EditMode 40/40; `validate`, `translations check`, standalone build; сервер задеплоен. ## 2026-10-24 — исправление зависшего InputLock - `GameBoard3D.Render` сначала распределяет старые `Card3D` по `InstanceId`, затем освобождает лишние. Обмен двух RC больше не освобождает карту, которая должна переехать в другой ключ (иначе дубли/неактивные карты). - `CardMoveAnimator` получил дедлайн активной анимации и `CancelCard`; `ReleaseCard` отменяет движение. Это устраняет вечный `card-move`-лок, из-за которого не открывалась смена и поле выглядело зависшим. - Клиентские EditMode: **42/42**. ## 2026-10-24 — аудит Skill и правил - Проверены Skill `vanguard-remaster`, `AGENTS.md`, docs и две локальные сессии OpenCode Vanguard (1259 и 5617 сообщений; история вне OpenCode недоступна). - Skill разделён на `SKILL.md`, `game-rules.md`, `architecture.md`, `card-implementation.md`, `workflow-and-decisions.md`. - Зафиксировано решение: смешивать кланы в колоде можно; эффекты с клановыми условиями могут не работать. `DeckValidator` это не ограничивает. - `CombatStats` strip принят как HUD, world-space Power/Shield — отложено. - Multi-target cost требует отдельный `CostPaymentOverlay`; текущий CB в панели «Действия» — промежуточная реализация. - Исправлен порядок отображения урона в `docs/protocol.md`: сначала открытые, затем закрытые. - Код, сцены и префабы в рамках аудита не менялись. ## 2026-10-24 — двухэтапный процесс DeepSeek → Kimi - Добавлены `docs/Development/AGENT-WORKFLOW.md` и `docs/Development/IMPLEMENTATION_PLAN.md` (пустой журнал + шаблон, без фиктивной задачи). - DeepSeek: анализ, вопросы, план; код не меняет. Kimi: реализация по плану, проверки, обновление статусов и Skill. - Передача — через файлы/Git; доступные Kimi-модели подтверждены каталогом OpenCode. Конкретную модель выбирает пользователь. - `AGENTS.md` и Skill дополнены ссылками и статусами `[ ]/[x]/[!]/[-]`. - Код, сцены, префабы и конфиги не менялись.