Решили: вспышка защиты играет на ЛЮБОЕ поглощение щитом (shieldAbsorbed = ShieldDamage > 0), а не только на полный блок (HpDamage <= 0). Это обычная вспышка тела через _FlashAmount, той же яркости, что и у попадания, — просто перекрашенная в золото --gm-color-combat-block-flash (#FFCC33). UI защиты (полоса, цифра поглощённого) остаётся синим.

Почему: пользователь трижды сказал «визуал не работает ДО активки», и это подтвердилось живой трассой боя bone-vs-bone на реальных ассетах. Каждый удар до активки — ЧАСТИЧНЫЙ блок: щит Оплота 50 гасит 50, но ~14 всё равно проходит в HP (авто-атака 109 → ~64 после брони 70). Значит HpDamage <= 0 ложно на каждом ударе, и завязанная на него вспышка молчала. Только активка «Решительный удар» ослабляет цель на 30%, входящий падает ниже щита, удар гасится целиком (hp=0) — и вспышка впервые срабатывала. Отсюда «работает только после активки». Баг жил ещё с исходного синего варианта: та же завязка на полный блок.

Грабли — три моих промаха на этой задаче, все про НЕ ТУДА:

  • Взяла шире просьбы: перекрасила роль защиты целиком, утащив в золото полосу и цифру (просили только «на юните»).
  • Изобрела два механизма подряд — HDR-флар (пересвет, отвергнут), потом затемнение тела с отдельным тинтом части-щита. Оба — сложная надстройка вместо простого: взять исходную вспышку и перекрасить. Всё снято, шов IUnitBodyVisual вернулся к одному тинту.
  • Диагностировала «по ощущению» вместо замера, хотя пользователь прямо указал, где искать. Причина нашлась за один прогон трассы: SimEnvironment + OnDamageDealt, лог ShieldDamage/HpDamage по каждому удару. Надо было начать с этого.

Владелец правды: CombatPresenter.HandleDamageDealt (условие shieldAbsorbed), CombatColorPalette.BlockFlash, UI/Theme/tokens.semantic.uss (роли защиты и вспышки), трасса воспроизводится через SimEnvironment в headless-прогоне.