Решили: тайминг удара переезжает из анимационного клипа в данные — доля замаха и позиции контактов объявляются в архетипе оружия, а замер по клипу становится офлайн-шагом с явной записью и гейтом «объявлено против замерено». План целиком — 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, клип общий на весь ростер, а «забыл нажать» ловится красным тестом. Прежнее решение читать обязательно — иначе пересмотр выглядит как незнание.
- запекать windup эдитор-кнопкой… добавляет шаг синхронизации и риск „забыл нажать”». Пересмотр
держится на трёх изменениях, а не на смене вкуса: тогда данные и клип были ОДНИМ ассетом (дизайнер
кликал кадры в том же SO — синхронизировать было нечего), тогда клип был свой у каждого юнита, и
тогда в проекте не было практики гейта-теста. Сегодня разметка живёт в отдельном
- Декларативный путь уже написан и по коду ПЕРВИЧЕН.
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.