Решили: самоинъекция дев-панелей идёт через DevSelfInject.WhenScopeReady<TScope> — ожидание построенного контейнера покадрово, а не однократный scope?.Container.Inject(this).

Почему: прежняя строка проверяла на null объект скоупа, а падала на Container. Объект LifetimeScope лежит в сцене с самого начала, контейнер он строит позже, и кто из них успеет к Start дев-панели — решает порядок объектов в сцене. То есть NRE появлялся и исчезал от любой правки сцены, никак не связанной с этими панелями: у нас он вылез после удаления пяти чужих объектов из CoreScene.

Грабли:

  • scope?.Container.Inject(this) — ложное чувство защищённости. ?. относится к scope, а не к Container; выражение остаётся небезопасным ровно там, где ломается.
  • Симптом плавающий, и поэтому дорого ловится. Два одинаковых запуска дают разный результат; воспроизводить надо запуском play и чтением консоли, а не чтением кода — по коду это выглядит рабочим.
  • Ждать событие построения нечем: публичного «контейнер готов» у LifetimeScope нет, поэтому ожидание кадрами (UniTask.NextFrame) с потолком в 120 кадров и предупреждением, если не дождались.
  • Панель, строящая UI из инъекции, обязана отложить и постройку. DevEncounterPanel собирал список боёв в Start — до инъекции его не из чего собирать; теперь BuildUi/Apply уходят в колбэк.

Владелец правды: Assets/_Project/Scripts/DevTools/DevSelfInject.cs; потребители — DevEncounterPanel, CombatUnitDebugView.