Решили: Атака может состоять из нескольких Ударов. Контакты размечаются маркерами клипа (все, а не первый), позиции считаются в нормированном времени × длительность свинга, а сила каждого Удара задаётся лично — по умолчанию полная, не «поровну» (вердикт Макса 2026-07-31). Первый носитель — «Двойной удар» Монаха: два маркера и доли 0.5 / 0.5.

Почему так, а не иначе. Три развилки, каждая решалась отдельно:

  1. Где живут позиции контактов. Взяты маркеры клипа, потому что тайминг удара уже приходит оттуда — UnitVisual не зря лежит в Data, симуляция читает разметку до боя. Альтернатива «список долей в данных юнита» отклонена: она завела бы второго владельца того же факта, и показ скрабил бы клип по своему маркеру, пока урон шёл бы по числам из ассета.
  2. Как серия соотносится с замахом. Серия висит на обычном замахе: первый контакт там же, где был бы единственный, остальные отстоят от него на расстояние из клипа. Поэтому ускорение атаки сжимает серию целиком, а ускоренный замах (разбег, взведённое комбо) двигает её как одно целое. Альтернатива «считать каждый контакт от начала свинга независимо» растягивала бы промежутки при любом множителе замаха и ломала бы ритм двойки.
  3. Как делится урон. Никак: движок не делит. Два контакта без настройки бьют вдвое, и это осознанно — силу серии объявляет автор кита, а не выводит формула из числа маркеров. Половинки Монаха живут в его данных.

Инварианты, которые пришлось ввести: соседние контакты отстоят минимум на тик (иначе второй Удар не виден ни показу, ни игроку), последний приходит не позже интервал − 1 (удар не совпадает со стартом следующей Атаки). Отсюда потолок скорости многоударного кита: атак/сек ≤ TickRate / N. Хвост-доигрыш считается от последнего контакта, якорь интервала остаётся на первом — темп Атак не зависит от числа Ударов внутри.

Грабли:

  • Рефанд кулдауна пришлось сузить до пустого свинга прямо в этом заходе, а не потом: как только контактов стало несколько, старое поведение превратилось в эксплойт — микростан после первого Удара обнулял таймер, и контроль ускорял жертву. Инвариант держал guard-тест, написанный заранее.
  • Одиночный удар обязан идти прежним путём бит-в-бит. Расчёт серии включается только при двух и более маркерах; иначе поехали бы тайминги всего ростера, а заметили бы это по чужому балансу.
  • Валидатор на длину долей нужен именно потому, что обе ошибки невидимы в игре. Список короче разметки — часть Ударов молча уходит в полную силу; длиннее — лишние числа не играют никогда, и автор крутит их, не понимая, почему ничего не меняется.

Чего этот заход НЕ сделал: показ по-прежнему скрабит клип к первому маркеру, то есть два Удара Монаха видны двумя цифрами урона, но не двумя взмахами. Клип покадровый, второго взмаха в нём не нарисовано — это работа арта, а не тайминга.

Владелец правды: ClipMarkers.MarkerNormalizedAll, UnitVisual.AttackHitPositions, AttackTiming.ContactTicks (<remarks> — оба инварианта и потолок скорости), UnitData.HitDamageShare, AutoAttackSystem.Interrupt (правило рефанда); тесты — группа «Атака из нескольких Ударов» в WindupAutoAttackTests и AnimationValidationTests.HitDamageShares_MatchMarkerCount.