Решили: отсутствие UnitVisual или ViewPrefab у юнита стало ошибкой валидации контента —
показывает Doctor в Content Hub, роняет CI тест UnitVisualPresenceTests, который гоняет по всему
ростеру то же правило из ContentValidationService.ValidateUnitVisual.
Почему: отвергли гейт в OnValidate у самого SO. Юнита проектируют раньше, чем ему рисуют тело,
и запрет на сохранение заставил бы подсовывать заглушку ради обхода — то есть врать данным ровно там,
где мы их и проверяем. Пара «панель + тест» этого не требует: автору видно сразу, а мимо CI не
проедет.
Рантайм-эррор в UnitView при этом НЕ убран, хотя гейт делает его почти недостижимым: он ловит
второй случай — клип атаки без маркера контакта, — которого проверка при авторинге не видит, и
юнитов, собранных не из ассета.
Оба тултипа обещали молчаливый фолбэк («пусто = дефолтный из презентера», «null = статичный фолбэк»), и это обещание снято тем же диффом: именно оно прятало поломку, выпуская юнита на арену чужим телом. Внутри нашего кода фолбэк — баг разводки, а не поведение.
Сказано: «Надо гейты или ошибки на юните создать. А то когда визуала не было и я пытался ставить юнита - мне был еррор лог, а он ведь должен быть еще при создании SO, чтобы такое не повторилось!» (2026-08-03, сессия af8ef6b9). Место гейта выбрано инженерно, порядок работ — вердикт Макса: близнецов не разводим, гейт только про отсутствие тела.
Грабли: сторож по соседнему инварианту уже существовал и лежал КРАСНЫМ. UnitTintPolicyTests
проверяет, что юниты, делящие спрайт, различаются ступенью BodyShade, и падает на двух группах
(шесть юнитов на арте Goblin Fighter, из них трое без приглушения; TrashHerbalist и
LightShepherd с одинаковым Ash). Нарушения старше 03.08.2026 и в этом заходе не чинились —
осознанно, вердиктом Макса. Отсюда вывод на будущее: перед тем как заводить новый гейт, прогони
соседние — красный сторож не отличим от отсутствующего, а мы чуть не завели второй поверх
незамеченного первого.
Второе, мелкое: правило вынесено чистой функцией от UnitData (по образцу ValidateIdString), а не
методом от ContentEntry. Иначе тест не смог бы позвать его без построенного индекса и повторил бы
логику у себя — то есть у инварианта стало бы два владельца.
Владелец правды: ContentValidationService.ValidateUnitVisual, тесты
UnitVisualPresenceTests (весь ростер) и ContentValidationServiceTests.UnitWithoutBody_IsTwoIssues
(сторож самого сторожа).