Решили: ввести четвёртое состояние цикла атаки — AttackPhase.CombatIdle (боевое ожидание), — и провести границу между ним и Idle по досягаемости цели, а не по её наличию. Значение дописано в конец enum: порядок существующих не меняем, он уезжает в снимок показа.

Почему: решение Макса 2026-07-30/10 существовало только на бумаге, а прежний Idle описывал две несовместимые ситуации — «не дерётся вовсе» и «дерётся, но сейчас между ударами». Пока свинг занимал весь интервал, разницы не было. С окном между атаками она принципиальна: парированию нужно знать, кто именно на него нацелен, и бегущий мимо боец — не то же самое, что ждущий своего удара.

Развилка внутри: считать боевым ожиданием любого с выбранной целью или только дотянувшегося. Взято второе — прямое уточнение Макса («бегущий к цели ещё не в бою»), и оно же дешевле: метрика та же, что у гейта атаки и у движения (CombatPositioning.InAttackRange), поэтому «в бою» и «бью» не могут разойтись на границе. Отдельно: под станом боевого ожидания не бывает — оглушённый выпал из цикла целиком, иначе таймер Комбо (07-30/11, «время вне лупа») считал бы неверно.

Попутно правило «идёт ли удар» получило одного владельцаRuntimeUnit.IsSwinging и его зеркало UnitSnapshot.IsSwinging. До этого три места перечисляли фазы руками (рут движения, смена боевой формы, выбор клипа в показе), и четвёртая фаза заставила бы обойти каждое, ничего не забыв.

Грабли: три, все найдены тестами, а не глазами.

  1. Смена боевой формы сверялась с Phase != Idle. С появлением боевого ожидания Десятина между ударами почти всегда стоит рядом с целью — то есть форма не менялась бы, пока враг не потерян, то есть никогда. Условие обязано звучать как «идёт ли удар», а не «равна ли фаза Idle».
  2. Оборванный канал переводится в Recovery намеренно (сворачивание потока — это и есть наказание контролем), поэтому безусловная простановка Idle в ветке потери дееспособности его затирала. Фаза покоя ставится только тем, кто в ударе не остался.
  3. У быстрого кита боевого ожидания не существует вовсе. Свинг сжимается до интервала (windup 21 + хвост 9 = интервал 30), хвост кончается ровно тогда, когда обнуляется кулдаун, и юнит уходит из доигрыша прямо в новый замах. Это не дефект, а «непрекращающийся град» из двух режимов темпа (07-30/15) — первая версия теста была написана на таком ките и падала справедливо.

Что из решения осталось незакрытым: пункт 07-30/14 «ожидание держит слот боевого контакта и линию» реализовать нечего — слотов контакта в коде нет, они приедут с позиционикой. Пункт вступит в силу вместе с ней.

Владелец правды: Combat/Units/AttackPhase.cs (смысл состояний и три следствия боевого ожидания), RuntimeUnit.IsSwinging, AutoAttackSystem.SetRestingPhase (где проходит граница), тесты WindupAutoAttackTests — группа «боевое ожидание» плюс FastKit_HasNoWaitingWindow_AtAll.