Решили: парирование живёт на том же pre-damage-шве, что Блок и «Изворотливость», и триггерится прилетевшим ближним ударом (вердикт Макса). Сработав, оно гасит удар-триггер, открывает окно effect.parry_window на 0.3 с, вешает общий микро-стан на всех, кто целился в носителя спереди, и отвечает рекастом с зарядом effect.riposte_charge (×2). Парировавшего того, у кого дальность больше своей, дополнительно разгоняет effect.parry_haste (+30% скорости передвижения на 1 с).

Почему: альтернатива — триггер по намерению атакующего («кто выбрал парирующего целью») — позволяла бы отбивать до попадания, но требовала бы нового шва «кто в кого целится» прямо в pre-damage. Со шва по прилетевшему удару вся механика собирается из уже написанного: заряды (RuntimeEffect.TryConsumeCharge), гейт дееспособности (IRequiresAgencyComponent), отмена урона (PreDamageResult.Negated) и рекаст. Ответ намеренно не свой путь урона, а заряд плюс RecastAttack: тогда его можно сбить контролем, от него можно уклониться и он уходит в промах — контригра работает та же, что у любого удара.

Окно — отдельный эффект, а не таймер внутри компонента. Его видит показ, его снимает диспел, его длительность крутится в ассете. Таймер внутри ParryComponent был бы состоянием, о котором снаружи не знает никто.

Грабли:

  • «Перед ним» не из чего вычислить. Направления взгляда в симуляции нет вовсе — его выводит показ по цели и движению. Поэтому фронтальный сектор считается от того же правила (CurrentTarget, иначе вектор движения), чтобы «спереди» в бою совпадало со «спереди» на экране; неизвестное направление сектор не сужает.
  • Пока окно висит, второй заряд не тратится. Без этого замка серия из трёх Ударов сожгла бы все заряды разом, хотя первое же парирование их и так отбило. Проверка — по ссылке на тот же ассет окна, без служебных тегов.
  • Stats.Commit() в тесте обязателен. Статы эффектов ложатся отложенно (конец боевого тика), и EffectSystem.Tick их не публикует — разгон читался как «не применился».
  • RuntimeUnit.AttackType не выводится из реликвии сам. Он заполняется в Bind, а тест собирает юнит инициализатором — стрелок в тесте оказался ближним, и парирование «отбило стрелу».

Что осталось незакрытым: носителя у примитива пока нет — ни один кит парирование не объявляет (механика ждёт вердикта, кому её выдать). Плавный вход и выход разгона (дизайн: «не рывком») сейчас не реализован: ParryHaste даёт плоскую прибавку, рампа есть только у спринта. Оба долга записаны в tech-debt §3.10.

Владелец правды: ParryComponent (триггер, стан по целящимся, ответ), ParryWindowComponent (что гасит окно), ассеты Effects/Parry, ParryWindow, RiposteCharge, ParryHaste, MicroStun, тесты ParryTests.