Решили: форма авто-атаки живёт РАНТАЙМ-СНИМКОМ на RuntimeUnit (тип урона, доставка, on-hit
эффекты, канал), а боевая стойка (AttackStanceComponent) по дистанции устанавливает профиль
целиком — с гистерезисом (вход 3, выход 4.5) и только в AttackPhase.Idle. Все части снимка берутся из
кита одним вызовом RuntimeUnit.AdoptKit.
Почему:
- Установка, а не «наложить/снять эффект-форму». Запись профиля целиком идемпотентна: у неё нет обратной операции, нет порядка наложений и нет вопроса «что если форму сорвали диспелом посреди боя». Отвергнутая пара «эффект ближней стойки / эффект дальней стойки» приносит всё это разом, причём молча: сорванная стойка оставила бы юнита с профилем кита, то есть с третьей, невидимой формой.
- Снимок, а не чтение кита. Прецедент уже стоял в коде —
AutoAttackDamageTypeбыл полем, «снимком кита на бой». Читай стойка кит напрямую, переключение форм пришлось бы выражать вторым китом, потому что кит — общий ассет на всех носителей. - Профиль формы ПОЛНЫЙ намеренно. Форма, задающая часть профиля, оставила бы остальное от кита — и кит снова стал бы невидимой третьей формой, различия которой всплывали бы только в бою.
- Форма не меняется посреди удара. Замах и канал уже сняли цифры с прежней формы (тип урона едет с ударом до прилёта — это отдельное осознанное решение), поэтому смена в полёте дала бы удар, начатый одной формой и завершённый другой. Ждём окна покоя.
- Статы формы — одной группой на ключе эффекта-стойки. Снятие и наложение по ключу означает, что
«сменить форму наполовину» невозможно; отложенно (
deferred: true), как любой баф, — иначе форма менялась бы посреди тика и исход зависел бы от места юнита в обходе.
Грабли: перевод доставки и on-hit в снимок уронил два теста, собиравших RuntimeUnit руками
(ChargeAttackTests, CryomancerSliceTests): они молча получили ближний бой и пустой список on-hit.
Отсюда AdoptKit — не ради краткости, а потому что частей у формы четыре, и собрать бойца, забыв одну,
можно бесшумно: ни компилятор, ни бой этого не заметят. Второе: стойка НЕ меняет фокус авто-атаки
(карточка Десятины просит разный фокус по формам) — фокус живёт в AIProfile, который мозг читает один
раз при сборке, это отдельный шов и отдельная работа.
Владелец правды: RuntimeUnit.AdoptKit и поля-снимки рядом с ним, AttackStanceComponent,
AutoAttackSystem (читает снимок, а не unit.Unit.*); тесты AttackStanceTests,
ChannelledAutoAttackTests.