Решение принято в Фазе 1 (до 19.06.2026), точная дата не зафиксирована; запись извлечена из замороженного
20-explanation/simulation.
Решили: RuntimeUnit — POCO с публичными изменяемыми полями, без свойств и инкапсуляции.
Никакого MonoBehaviour/ScriptableObject: юнит живёт один бой и хранит HP, щит, позицию (и
PreviousPosition для интерполяции вида), цель, кулдаун атаки, эффекты, флаги контроля.
Почему: сознательный размен в пользу скорости и простоты, Data-Oriented стиль — системы читают и пишут эти поля много раз за тик. Инкапсуляция дала бы геттеры на горячем пути и ничего не защитила бы: дисциплина «мутирует только такая-то система» всё равно держится соглашением, потому что систем много и все они внутри одной сборки. Цена честная — компилятор дисциплину не навязывает, нужна аккуратность; выгода — ноль аллокаций и ноль вызовов на пути тика.
Это же решение — причина, по которой инвариант «вид только читает» держится не типами, а правилом
(IUnitBodyVisual и презентационный шов). Открытый пункт H7 в
Meta - Tech Debt — про ту самую цену.
Грабли: создавать RuntimeUnit только через RuntimeUnitFactory — там единая точка сборки со
значимым порядком: дефолты статов → моды реликвии → таланты → пассивки → способности →
CurrentHP = MaxHP. Порядок не косметика: пассивка на +MaxHP обязана примениться до
инициализации CurrentHP, иначе юнит выходит в бой не с полным здоровьем. Собранный руками юнит
молча получает неверное HP.
Владелец правды: Assets/_Project/Scripts/Combat/Units/RuntimeUnit.cs и
RuntimeUnitFactory рядом с ним.