Interaction System — TODO / известные проблемы
Список актуальных находок по итогам аудита после рефакторинга. Отсортировано по важности. Ничего из этого пока не исправлено — это бэклог, а не описание текущего поведения.
1. Первичная синхронизация непустого инвентаря = ложные reward-события
Где: Inventory/NetworkInventory.cs → OnItemsChanged.
Суть: PurrNet SyncDictionary.HandleInitialState при первом получении словаря шлёт
Cleared, затем Added на каждый ключ. Обработчик считает delta = count - 0 > 0
→ IsReward → Changed(reward) на каждый предмет. RebuildSnapshot() в OnSpawned не
спасает: на момент OnSpawned словарь ещё пуст (initial-state приходит отдельным RPC позже),
снапшот строится пустым.
Когда стреляет:
- Reconnect уже сейчас — объект игрока не деспавнится, инвентарь на сервере полный; при
повторном входе клиент заново становится observer → шквал ложных «+N gained» (лог-спам +
ложные reward-попапы, когда будет подключён UI).
- Save/Load в будущем — загрузка непустого инвентаря даёт то же самое.
Решение: флаг «первичный initial-state получен»; до него не эмитить reward/spend, а просто
пересобрать снапшот. ~5 строк. Обязательно до сейва, желательно до релиза.
2. Инвариант «инвентарь пуст до NoVisibility» держится на дисциплине, не на коде
Где: Inventory/NetworkInventory.cs + NetworkOwnershipVisibility/.
Суть: пока identity ещё AlwaysVisible (до Privatize()), PurrNet сериализует состояние
SyncDictionary в spawn-пакет всем текущим наблюдателям. Если будущий код положит предметы в
инвентарь в OnSpawned(asServer) до cull — содержимое утечёт чужим за один кадр, отменить
отправленные байты нельзя. Сейчас соблюдается (инвентарь стартует пустым), но кодом не защищено.
Когда стреляет: окно spawn→cull возникает у уже подключённых удалённых клиентов на вновь
входящего (не наоборот — поздний получает чужую приватку уже отфильтрованной, не частями). Значит
нужны ≥2 удалённых клиента; при «хост + 1 клиент» единственный старый наблюдатель = сервер, его не
куллит → не воспроизводится. Подробности — NetworkOwnershipVisibility/SpawnFlow.md.
Решение: assert/guard, запрещающий запись до применения приватного правила видимости.
3. NetworkOwnershipVisibility.targets заполняется руками в инспекторе
Где: NetworkOwnershipVisibility/NetworkOwnershipVisibility.cs.
Суть: забыли добавить NetworkInventory в targets[] → тихая утечка приватности, ноль
предупреждений (класс «silent misconfiguration»).
Решение: на сервере при спавне проверять, что все NetworkInventory/PlayerInteractor
из префаба реально попали в targets, иначе LogError.
4. BusyLockTracker: single-slot lock + глобальный stateEpoch
Где: Interactor/BusyLockTracker.cs.
Суть: лок держится в одном наборе полей (lockedTargetId/lockStart/lockCooldown/
lockPressEpoch/lockIsPickup) + глобальный stateEpoch. При смене цели / втором нажатии
слот затирается. Два проявления:
- Ложный Waiting: нажал A → быстро нажал B (перезатёр слот на B, но запрос к A ещё в
pending) → вернулся на A: locked=(A==lockedTargetId)=false → cooldown=0 вместо родного
cooldown A → A прыгает в Waiting через waitingAfter (0.25с), хотя это нормальный round-trip.
- Ложный ранний reveal: скользнул взглядом по B, B реплицировался → глобальный stateEpoch
сдвинулся → вернулся на A: stateEpoch != lockPressEpoch → A раскрывается до прихода своего SyncVar.
Почему не воспроизводится в песочнице: оба требуют ненулевого round-trip; на host RPC
синхронный, pending для A закрывается в том же кадре, ветки не срабатывают. Нужен реальный
клиент с пингом.
Оценка: косметика тултипа на доли секунды, самоисправляется, состояние сервера не трогает —
но именно на реальном пинге (ради чего anti-flicker и делался) вылезет.
Решение: перевести лок в per-target (Dictionary<EntityId, LockEntry>, каждый со своим
cooldown/pressEpoch; stateEpoch инкрементить пер-таргет). Чинит оба проявления, строк примерно
столько же, поведение на счастливом пути 1:1.
5. Мелкое / известное (низкий приоритет)
Hud/PlayerHud.cs→SetWalletText— ноль вызовов, кошелёк в HUD не обновляется. Недоделанная фича.Interactables/Interactable.cs→CacheModules— вызывается только вAwake; модули, добавленные в рантайме, молча игнорируются. Не задокументировано.Interactables/NetworkSeat.cs→Release(PlayerID)— публичный server-only API без вызовов (задел под будущий exit-UI, ок; просто ни разу не протестирован).