NetworkOwnershipVisibility
Серверный компонент: делает перечисленные дочерние NetworkIdentity player-префаба приватными для
владельца. Они спавнятся у всех (AlwaysVisible), а после назначения owner куллятся у всех, кроме
owner + сервера. Здесь — про сам компонент и почему flow именно такой. Пошаговый порядок спавна,
как выглядят реплики и флоу инициализации (старт хоста / клиент / реконнект) — в
SpawnFlow.md. Сама система взаимодействий — в ../README.md.
Компонент
На руте player-префаба, работает только на сервере:
visibilityAsset— правило после owner (обычноNoVisibility).targets— что приватизировать (PlayerInteractor,NetworkInventory; публичный аватар — НЕ сюда).applyInLateUpdate(дефолт on) — см. «SpawnerIfClientOnly и applyInLateUpdate».OnSpawned/OnOwnerChangedпри наличии owner делают для каждого targetSetVisibilityRules(visibilityAsset)+EvaluateVisibility().
Почему AlwaysVisible-then-cull, а не статический NoVisibility
Статический NoVisibility в оверрайдах отсекает identity и от самого owner: на спавне owner ещё
не назначен (видимость считается до ownership — см. «Порядок спавна» в SpawnFlow.md), а пересчёта по
смене owner нет → владелец свою приватку так и не получит. Поэтому: спавним AlwaysVisible (весь
префаб приезжает атомарно, кросс-ссылки целы), а NoVisibility вешаем рантаймом после owner.
Чужие отписываются, поздние подключения куллятся правилом автоматически.
Инвариант: инвентарь пуст на спавне
Пока identity AlwaysVisible, её начальное состояние уходит всем текущим наблюдателям в spawn-пакете,
и отозвать байты нельзя. Значит до cull SyncDictionary обязан быть пуст (сервер наполняет его
только после перехода в NoVisibility — предметы приходят от взаимодействий позже). Иначе — утечка.
Активность vs видимость
NetworkOwnershipToggle управляет локальной активностью (SetActive / Behaviour|Collider|Renderer.enabled),
а не observers — приватность синка им не обеспечить (кому летит SyncDictionary, решают observers).
Он для камеры/ввода владельца; приватность инвентаря — на видимости (этот компонент). Как это
проявляется на репликах — в SpawnFlow.md, раздел «Как выглядят реплики».
SpawnerIfClientOnly и applyInLateUpdate
При DefaultOwner = SpawnerIfClientOnly AutoAssignOwnership даёт серверному спавну транзиентного
владельца-хоста, поэтому OnOwnerChanged на сервере фаерится дважды: null→host, затем host→owner.
Приватность owner-зависима, так что Privatize() при owner == host куллит реального клиента и
лишь потом на host→owner его возвращает — spawn→despawn→respawn в кадре спавна. (Это observers, а не
активность, поэтому force-refresh тоглера сюда не относится вовсе.)
applyInLateUpdate это гасит: OnSpawned/OnOwnerChanged только ставят dirty-флаг, а сам
Privatize() идёт один раз в LateUpdate — к этому моменту owner уже финальный (оба присвоения
синхронны и происходят до LateUpdate; детали — «Порядок спавна» в SpawnFlow.md). Промежуточного
Privatize(owner==host) нет. Механика — аналог NetworkOwnershipToggle._applyInLateUpdate
(коалесцирование; идемпотентный force-refresh к owner-зависимой видимости неприменим). Оговорка:
кастомный спавн, отдающий ownership кадром позже спавна, коалесцирование обошёл бы (он сломал бы и
тоглер).
Финальный конфиг
DefaultOwner = None + applyInLateUpdate включён. None убирает транзиентного владельца в корне;
applyInLateUpdate оставлен как страховка (и делает компонент устойчивым к любому DefaultOwner).