Решили: сценные презентеры боя (CombatPresenter, CombatDebugDraw, CombatAreaFlash) регистрируются в мировом скоупе и получают инъекцией только мировую половину зависимостей. Боевую — симуляцию, ленту, диспетчер, dev-режим — им выдаёт BattlePresenterBinder на время жизни боя и забирает, когда бой уходит.

Почему: это объекты персист-сцены, они переживают бои по построению, а инъекция в них случается ровно один раз. «Родить их вместе с боем» невозможно физически. Альтернатива — оставить всё в боевом скоупе и пересоздавать сцену на каждый бой — это возврат к загрузке сцены под каждый узел, от чего persist-мир и уходил.

Грабли:

  • Update презентера начинался с if (_simulation == null) return; — то есть без боя показ не делал вообще ничего. Пока симуляция была вечной, это читалось как проверка готовности; теперь это означало бы чёрную арену во дворе гильдии. Признак готовности сменился на мировой (_stage), а боевые куски проверяются по месту.
  • Виды хоронит отсутствие в кадре, поэтому «кадра нет» и «кадр пустой» обязаны вести себя одинаково. Раньше при отсутствии кадра SyncViewsToFrame не звалась вовсе — юниты последнего боя остались бы стоять на пустой арене.
  • Подписка живёт на пересечении двух условий — бой привязан И объект включён. Поэтому она вынесена из тела OnEnable в отдельный метод: иначе бой, начавшийся при выключенном объекте, оставил бы презентер немым, а включение при живом бое подписало бы его дважды.
  • Найдено попутно: с 29.07 не работали статус-кольца и пул боевых VFX. Коммит 1bdd43ac7, переводивший показ на ленту, удалил старый блок подписок вместе с двумя строками EnsureStatusOverlay() и EnsureVfx(), стоявшими в его конце. _vfx с тех пор всегда был null, и дуга взмаха с пылью под ногами молча не заводились. Вызовы вернулись на свои новые места: пул VFX — в Construct (переживает бои), статус-кольца — в BindBattle (читают симуляцию напрямую, поэтому умирают вместе с боем).

Владелец правды: Game/Flow/BattlePresenterBinder.cs, тест ScenePresenterScopeTests.