Решили: «Оплот» перестал быть персональной пассивкой — класс переименован в BlockComponent и
описан как общий примитив, а кит задаёт только числа (заряды, перезарядку, сам щит). Переименование
сделано с атрибутом [MovedFrom("BulwarkComponent")].
Почему: имя кита в имени класса и было причиной, по которой блок читался как способность одного Защитника. Механика при этом с самого начала общая — «поднять щит под прямой удар, если есть заряд», — и второй такой же компонент для следующего кита означал бы второго владельца одного правила.
Грабли: SerializeReference хранит имя типа прямо в ассете — в YAML лежит
type: {class: BulwarkComponent, ns: …, asm: …}. Переименование класса без миграции обнуляет ссылку:
компонент молча становится null, эффект остаётся в игре, но ничего не делает. В консоли при этом
пусто.
Лечится штатным UnityEngine.Scripting.APIUpdating.MovedFromAttribute — Unity подставляет новый тип
при чтении старых данных. В проекте атрибут раньше не использовался, теперь есть прецедент.
Вторая грабля мельче, но того же рода: правку YAML руками здесь применить было нельзя — редактор
открыт, и он перезаписал бы файл своей версией (правило CLAUDE.md §«А ассеты — ровно наоборот»).
Миграция атрибутом обходит это целиком: ассет не трогается вовсе.
Как проверяли, что миграция сработала: не глазами, а существующим валидатором —
ContentValidationTests роняет прогон при null-компоненте у любого эффекта. Он зелёный, значит
ассет читается новым типом.
Владелец правды: BlockComponent (контракт Блока в <summary>, провенанс имени в <remarks>),
тесты ContentValidationTests и DefenderSliceTests.