Решили: «Оплот» перестал быть персональной пассивкой — класс переименован в 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.