200 lines
16 KiB
Markdown
200 lines
16 KiB
Markdown
# Решения, процесс и антипаттерны
|
||
|
||
## 1. Источники аудита
|
||
|
||
**[C]** Проверены:
|
||
|
||
- `.opencode/skills/vanguard-remaster/SKILL.md` (прежняя версия);
|
||
- `AGENTS.md`, `docs/SESSION-STATE.md`, `docs/HANDOFF.md`,
|
||
`docs/game-rules.md`, `docs/ability-implementation.md`,
|
||
`docs/protocol.md`, `docs/client-visual-direction.md`,
|
||
`docs/3d-ux-backlog.md`, `docs/translation-rules.md`;
|
||
- локальная база OpenCode: две сессии Vanguard ReMaster (1259 и 5617
|
||
сообщений), включая сообщения пользователя и финальные тексты ассистента;
|
||
- исходный код server/client, YAML-контент, тесты, `README.md`,
|
||
`DEVELOPMENT_CHECKLIST.md`, `content/cards-mvp.md`.
|
||
|
||
История доступна локально, но не является «полной истиной»: часть решений
|
||
могла быть принята вне OpenCode. В спорных местах ниже стоит **[O]**, а не
|
||
выдуманное правило.
|
||
|
||
## 2. Подтверждённые решения
|
||
|
||
### Игра и контент
|
||
|
||
- **[D]** Фанатский ремейк Vanguard, формат **Classic**, BT01 + TD01/TD02,
|
||
94 карты / 124 принта.
|
||
- **[D]** Две триал-деки: TD01 «Blaster Blade», TD02 «Dragonic Overlord»;
|
||
полный декбилдер.
|
||
- **[D]** Официальные арты; локализация EN + RU.
|
||
- **[D]** В коллекции нет лимита копий/артов; 4 копии по имени — только при
|
||
сборке колоды.
|
||
- **[D]** Смешивать кланы в колоде разрешено; эффекты с клановыми условиями
|
||
могут не работать. Это осознанное правило проекта.
|
||
- **[D]** Сайд-дека нет, матч BO1.
|
||
- **[D]** Ride Deck/Persona Ride не входят в Classic/текущую игру.
|
||
- **[D]** Стартовый авангард — grade 0 без триггера; после обучения игрок
|
||
получает триал-деку на выбор и 5 токенов; 1 токен = 1 бустер.
|
||
- **[D]** Таймер: 90 с на ход + банк 5 мин; отсутствие/сдача/разрыв —
|
||
поражение.
|
||
- **[D]** В первый ход нельзя атаковать и переходить в Battle.
|
||
- **[D]** Локализация: RU+EN; «ё» разрешена; «усиленная карта»; «уничтожить»
|
||
оставляем; фазы — Основная/Боевая/Завершающая/Подъёма/Добора/Смены;
|
||
close step — завершающий шаг; call — призвать; названия зон не переводим;
|
||
имена карт остаются английскими (`nameRu` — задел). Детали —
|
||
`docs/translation-rules.md`.
|
||
|
||
### Бустеры и экономика
|
||
|
||
- **[D]** В бустере 5 карт: 4 C + 1 гарантированная R+.
|
||
- **[C]** Текущие веса R+: R 70% / RR 22% / RRR 7% / SP 1%
|
||
(`content/pack_odds.yaml`). Пользователь поручил подобрать вероятности, но
|
||
точные числа не перепроверял; это конфиг, а не вечный закон.
|
||
- **[D]** Дубли считаются по `printingId`, порог 4; SP — отдельный принт;
|
||
при отсутствии замены карта остаётся в коллекции и разбирается в пыль.
|
||
- **[D]** Gold начисляется за победу и поражение; **[C]** текущая конфигурация
|
||
— 30 / 15 / 20 (победа/поражение/ничья), Gold = XP / 5. Точные числа —
|
||
балансный конфиг, можно менять.
|
||
- **[C]** Бустер 100 Gold, триал-дека 500 Gold; за сдачу/АФК награды не
|
||
начисляются (`RewardsForForfeit = false` по умолчанию).
|
||
- **[C]** Пыль: разбор C/R/RR/RRR = 5/10/25/100, крафт ×4; фойл ×2.
|
||
- **[C]** XP пишется с первого дня; баттлпасс/квесты/Draft — после MVP.
|
||
|
||
### Клиент и UX
|
||
|
||
- **[D]** 3D-сцена, отдельные сцены Login/Lobby/DeckBuilder/Battle; HUD —
|
||
uGUI; reference 1920×1080.
|
||
- **[D]** Камера в покое фиксирована по центру поля с небольшим наклоном;
|
||
временно двигается только `CameraDirector` в trigger-последовательности.
|
||
- **[D]** Выбор карты/круга/зоны/цели — клик/драг по объекту; кнопки только
|
||
для глобальных действий/подтверждений.
|
||
- **[D]** Guard: карты из руки с grade ≤ grade своего Vanguard; одна кнопка
|
||
«Не защищаться»/«Защищаться»; выбранные подсвечиваются, щит считается
|
||
сразу, после подтверждения — в Guardian.
|
||
- **[D]** Карты противника нельзя драгать; можно выбирать целью, смотреть
|
||
детали и зоны. Клик по своей карте — меню валидных действий.
|
||
- **[D]** Зоны открываются общим горизонтальным `CardListView`; сверху —
|
||
последняя добавленная карта; из списка доступен `CardDetailOverlay`.
|
||
- **[D]** Call в занятый RC разрешён; старый юнит → Drop.
|
||
- **[D]** Move: RC↔RC в колонке; занятые меняются; при единственном
|
||
доступном круге клиент перемещает сразу.
|
||
- **[D]** Draw: после цели силы сразу тянет карту; Heal: только своя зона
|
||
урона и только при условиях; Tejas: задний ряд своей колонки.
|
||
- **[D]** Optional AUTO — оверлей «Активировать/Пропустить»; ACT из руки —
|
||
меню карты; оплата — по объектам, подтверждение в панели/оверлее.
|
||
- **[D]** Mulligan: выбор карт в руке; после замены этап завершается
|
||
автоматически, неактивная кнопка «Закончить муллиган» не остаётся.
|
||
- **[D]** Панель фаз переименована в «Действия» и содержит guard,
|
||
подтверждение оплаты и глобальные действия; список всех возможных смен
|
||
там не показывается.
|
||
- **[D]** Под картами V/R показываются динамические Power/Critical;
|
||
верхний `CombatStats` strip — допустимый HUD; world-space Power/Shield —
|
||
отложенный долг.
|
||
- **[D]** Отдельный `CostPaymentOverlay` для multi-target стоимости нужен;
|
||
текущий CB в панели «Действия» — промежуточная реализация.
|
||
|
||
## 3. Фактическое устройство (не путать с договорённостью)
|
||
|
||
- **[C]** `GameEngine` — авторитарное ядро; `MatchGrain` сериализует интенты;
|
||
`MatchProjection` скрывает информацию.
|
||
- **[C]** Multi-target CB уже поддержан `DamageIndexes` в
|
||
`ActivateAbilityIntent`; старый `DamageIndex` оставлен.
|
||
- **[C]** `CardMoveAnimator`, `CameraDirector`, `TriggerCheckSequence`,
|
||
`ArrowDirector` есть; анимации на `Tick`, без UniTask/DOTween.
|
||
- **[C]** `CardMoveAnimator` имеет таймаут и `CancelCard`; это исправление
|
||
зависшего `card-move`-лока (коммит `fd21a96`).
|
||
- **[C]** `DropZoneFaceDown` есть, default `false`.
|
||
- **[C]** `DeckValidator` не проверяет единство клана; это соответствует
|
||
решению «смешивать можно».
|
||
- **[C]** `CombatStats` — верхний uGUI strip; отдельного боевого overlay нет.
|
||
- **[C]** `docs/protocol.md` ранее указывал неверный порядок отображения
|
||
урона; фактически сначала открытые, затем закрытые.
|
||
|
||
## 4. Процесс разработки
|
||
|
||
Формальный двухэтапный процесс DeepSeek → Kimi описан в
|
||
`docs/Development/AGENT-WORKFLOW.md`; активный план задачи — в
|
||
`docs/Development/IMPLEMENTATION_PLAN.md`. Ниже — краткие правила, без
|
||
дублирования шаблона плана.
|
||
|
||
1. Прочитай Skill и связанные docs; найди существующую реализацию.
|
||
2. Проверь, не решён ли вопрос в коде/истории. Не спрашивай о том, что уже
|
||
установлено.
|
||
3. Если есть существенная неопределённость — `question` с 2–4 вариантами.
|
||
4. Вноси минимальные изменения; не трогай несвязанное.
|
||
5. Для правил: тест на сценарий; для карт — тест на триггер/активацию;
|
||
для клиента — EditMode/smoke.
|
||
6. Серверные проверки — только `ssh home`/Docker; Unity batch — копия без
|
||
`Library`.
|
||
7. После блока: `validate`, `translations check`, коммит/push.
|
||
8. Геймплей вручную проверяет пользователь; не выдавай свой тест за приёмку.
|
||
|
||
## 5. Антипаттерны
|
||
|
||
| Не делать | Почему | Как делать |
|
||
|---|---|---|
|
||
| Список кнопок вместо клика по карте/кругу | Нарушает принятый UX | Подсветка объектов + клик/драг |
|
||
| `transform.position = ...` в игровой логике | Ломает анимации/локи | `Card3D`/`CardMoveAnimator`/`SetPose` |
|
||
| Дублировать эффект в нескольких `CardScript` | Расходится поведение | Один `CardScript`, общий движок или флаг |
|
||
| `OnPlace` без проверки VC/RC | Срабатывает не в той зоне | `IsVanguardOf`/`IsRearGuardOf` |
|
||
| Менять `GameState` на клиенте | Рассинхрон с сервером | Только `IntentPayload`, рисовать snapshot |
|
||
| Запускать `dotnet`/Docker локально | Запрещено пользователем | `ssh home`/Docker |
|
||
| Второй Unity с тем же проектом | Ломает Library/импорт | Копия без `Library` |
|
||
| Заявлять тест без запуска | Вводит в заблуждение | Указать, что проверено, а что нет |
|
||
| Самому проходить геймплей как приёмку | Пользователь проверяет лично | Автотесты + передать сценарий |
|
||
| Менять `Vanguard.Shared` без пересборки DLL | Unity увидит старый контракт | Собрать netstandard2.1 и заменить DLL |
|
||
| Вечный `InputLock` без таймаута | Блокирует ход/смену | try/finally, timeout, `CancelCard` |
|
||
| Освобождать карту из пула во время анимации/обмена | Дубли/неактивные карты | Сначала распределить по `InstanceId`, потом release |
|
||
| Новый универсальный effect/DSL без нужды | Усложняет и дублирует | Расширять существующие `AbilityDefinition` |
|
||
| Хранить вычисленную силу как истину | CONT пересчитывается | `EffectivePower`/модификаторы |
|
||
| Показывать все возможные смены в панели | Пользователь это запретил | Действия из меню карты/валидных интентов |
|
||
| Перехватывать клики информационным оверлеем | Мешает выбору по объекту | `raycastTarget = false`/скрыть ввод |
|
||
| Считать `printingId` и `nameId` одним | Ломает лимиты/коллекцию | `nameId` — 4 копии, `printingId` — принт |
|
||
| Perfect Guard даёт щит без оплаты | Неверная защита | Без оплаты 0 щита и нет nullify |
|
||
| Heal просит цель при невыполненных условиях | Лишний выбор/зависание | Сначала серверная проверка, потом `AskDamageChoice` |
|
||
|
||
## 6. Известные ошибки и уроки
|
||
|
||
- **Зависший `card-move`-лок** (2026-10-24): карта освобождалась из пула,
|
||
оставаясь в `_active`; `Tick` больше не вызывался, `InputLock` не
|
||
снимался. Исправлено таймаутом/`CancelCard`. Не возвращать.
|
||
- **Дубли/нахлёст при обмене RC**: нельзя освобождать карту из занятого
|
||
ключа, если она должна переехать в другой ключ. Сначала распределение по
|
||
`InstanceId`, затем release.
|
||
- **Conroe** долго не имел AUTO `OnRideOver` — проверять весь текст, а не
|
||
только очевидные ACT/CONT.
|
||
- **Barcgal**: при выборе призыва из колоды клиент должен показать оверлей
|
||
доступных карт; не подставлять первый круг/карту автоматически.
|
||
- **Ride после хода противника** ломался из-за клиентского ввода/логики, а не
|
||
только сервера; проверять lock и `LegalIntents`.
|
||
- **Пустой Mulligan**: после замены карт кнопка «Закончить муллиган» не
|
||
должна оставаться неактивной; муллиган завершается автоматически.
|
||
- **Guard дубли**: Guardian и `Battle.GuardCards` могут содержать одну
|
||
карту; клиент дедуплицирует по `InstanceId`.
|
||
- **Perfect Guard**: без discard карта даёт 0 щита; не считать Sentinel
|
||
обычным щитом.
|
||
- **Trigger check**: карта сначала в Trigger, затем эффект, затем hand/
|
||
damage; не переставлять.
|
||
|
||
## 7. Открытые вопросы
|
||
|
||
- **[O]** Отдельная проверка «авангард исчез» в `GameEngine` отсутствует;
|
||
нужно ли добавлять — решать пользователю.
|
||
- **[O]** `OnPlace` + `OnRideOver` при pending-способности `OnPlace`:
|
||
текущая реализация может не запустить `OnRideOver`; нужно ли чинить —
|
||
отдельное решение.
|
||
- **[O]** Multi-target Soul Blast/другие стоимости: протокол и UI ещё не
|
||
сделаны.
|
||
- **[O]** `CostPaymentOverlay` требуется, но его форма/`EffectConfig` и
|
||
момент включения ещё не реализованы; текущий CB — промежуточный.
|
||
- **[O]** World-space Power/Shield: верхний strip принят как HUD, перенос —
|
||
долг.
|
||
- **[O]** `AttackResolutionQueue` и несколько boost-стрелок — будущая
|
||
архитектура, сейчас бой с одной целью.
|
||
- **[O]** `DropZoneFaceDown` UX: default `false`; нужен ли другой режим —
|
||
не решено.
|
||
- **[O]** Точная легальность формата/banlist и обязательность ровно одного
|
||
стартового авангарда в `DeckValidator` не выделены.
|
||
- **[O]** Полная история вне OpenCode недоступна; спорные места не считаются
|
||
восстановленными.
|