Решили: тайминг удара переезжает из анимационного клипа в данные — доля замаха и позиции контактов объявляются в архетипе оружия, а замер по клипу становится офлайн-шагом с явной записью и гейтом «объявлено против замерено». План целиком — Planning - Weapon System.

Почему: аудит искал, где логика опирается на размеры спрайтов, и не нашёл — боевые величины уже декларативны (радиус тела через Size × BodyRadiusPerSize, дальность шестью ступенями AttackRange, размеры VFX долями H). Зато нашёл опору на разметку клипа: AttackTiming считает тики замаха как hitFrame × durationTicks / frameCount, а число и моменты Ударов — по позициям маркеров. Это доходит до CombatPositioning.CanLandWindup, то есть до решения «идти или бить», и наследуется балансными прогонами. Аниматор двигает маркер на два кадра — едет баланс, и никто не узнаёт.

Отвергнуто: оставить как есть (клип у нас теперь ОДИН на 48 юнитов — правка маркера двигает тайминг всему ростеру разом) и читать клип, но кэшировать (кэш не спасает от того, что владельцем числа остаётся ассет из другого окна).

Грабли:

  • Этот путь уже отвергали 01.06.2026, и возражение было по делу. Planning - Attack Timing §Альтернативы: «оставить в Presentation
    • запекать windup эдитор-кнопкой… добавляет шаг синхронизации и риск „забыл нажать”». Пересмотр держится на трёх изменениях, а не на смене вкуса: тогда данные и клип были ОДНИМ ассетом (дизайнер кликал кадры в том же SO — синхронизировать было нечего), тогда клип был свой у каждого юнита, и тогда в проекте не было практики гейта-теста. Сегодня разметка живёт в отдельном AnimationClip, клип общий на весь ростер, а «забыл нажать» ловится красным тестом. Прежнее решение читать обязательно — иначе пересмотр выглядит как незнание.
  • Декларативный путь уже написан и по коду ПЕРВИЧЕН. AttackTiming.cs:144-147 проверяет WindupShare до покадрового расчёта. Дыра не в коде, а в данных: долю задают 10 юнитов из 48. Поэтому «реализовать» здесь наполовину означает «заполнить», и заполнять надо ЗАМЕРОМ из текущих клипов — иначе тайминги сорока восьми юнитов сдвинутся молча.
  • Второго числа не существует вовсе. Позиции контактов серии даёт только AttackHitPositions, декларативного аналога нет. Заводить надо списком на каждую атаку: Attack2/Attack3 уже лежат в контроллере невыбранными, и как только их начнут выбирать, у каждой атаки будет свой тайминг.
  • Прецедент правильного паттерна был под рукой. RigStride / LocomotionStrideMeter меряет подошву по графике офлайн и ЗАПИСЫВАЕТ темп числом в поле префаба; рантайм меш не спрашивает. Ту же схему просит вылет оружия и тайминг удара — искать её отдельно не пришлось бы, знай я про неё раньше.
  • Побочная выгода, неочевидная из постановки: AnimationClip — движковый объект, поэтому сегодня тайминг недоступен быстрым headless-тестам, и EffectTestSupport вынужден фабриковать клип с событием, чтобы задать сим-тайминг. Декларативные доли снимают это.
  • Кончик оружия считается ТРЕМЯ реализациями с разными опорными точками: рантайм UnitPartGeometry.TryGetTip (от нуля рендерера), офлайн RigProfile.MeasureAxis (от точки хвата), RigSweep.SpriteQuad (самая длинная пара вершин). В игре и в гизмо кончик может разойтись. Свести к одной — часть фазы 3.

Владелец правды: Combat/Systems/AttackTiming.cs (сегодня — покадровый путь; после фазы 2 — только декларативный), план tech/40-planning/weapon-system, ГД-журнал 2026-08-06/4.