fix(world): destroy proxy on DESPAWN + handle stale weak ptr on SPAWN

ZMMOWorldSubsystem mantinha proxies no mapa RemoteEntities apos DESPAWN
apenas marcando SetActorHiddenInGame(true) via SetEntityRelevant(false).
Entry ficava la apontando pra actor escondido — SPAWN subsequente do
mesmo entityId caia no fast-path "reactivar" e tudo bem... ate' o
TWeakObjectPtr ficar stale (GC, World Partition stream-out, etc): a
branch Contains(EntityId) saia com return incondicional sem spawnar
nada. Resultado: SPAWN silenciosamente ignorado, pawn permanente
invisivel ate' algo limpar a key.

Fixes:
1. HandlePlayerDespawned: Actor->Destroy() + RemoteEntities.Remove
   (destroi de verdade em vez de so esconder).
2. HandlePlayerSpawned: quando Contains mas weak ptr stale, remove a
   entry orfa e cai pro caminho de spawn novo — em vez de return.

Confirmado pelos logs de cliente durante teste de oscilacao do AOI
(servidor enviando DESPAWN+SPAWN ao oscilar ZI/ZD).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-01 16:26:55 -03:00
parent b2c1153229
commit e3aab64c8c

View File

@@ -104,7 +104,10 @@ void UZMMOWorldSubsystem::HandlePlayerSpawned(const int32 EntityId, const bool b
if (RemoteEntities.Contains(EntityId))
{
// Ja existe um proxy: reactivar e re-snap.
// Ja existe um proxy: tenta reactivar e re-snap. Se o weak ptr ficou
// stale (actor coletado pelo GC, stream-out do World Partition, etc.),
// remove a entry orfa e cai no caminho de spawn novo — evita SPAWN
// silenciosamente ignorado depois de oscilacoes na AOI.
if (AActor* Existing = RemoteEntities[EntityId].Get())
{
if (IZMMOEntityInterface* AsEntity = Cast<IZMMOEntityInterface>(Existing))
@@ -112,8 +115,12 @@ void UZMMOWorldSubsystem::HandlePlayerSpawned(const int32 EntityId, const bool b
AsEntity->SetEntityRelevant(true);
}
Existing->SetActorLocationAndRotation(PosCm, FRotator(0.0f, YawDeg, 0.0f));
UE_LOG(LogZMMO, Verbose, TEXT("ZMMOWorldSubsystem: re-snap EntityId=%d"), EntityId);
return;
}
return;
// weak ptr stale — limpa entry e cai pro spawn novo abaixo.
RemoteEntities.Remove(EntityId);
UE_LOG(LogZMMO, Log, TEXT("ZMMOWorldSubsystem: stale entry para EntityId=%d, respawnando"), EntityId);
}
UWorld* World = GetWorld();
@@ -166,15 +173,23 @@ void UZMMOWorldSubsystem::HandlePlayerDespawned(const int32 EntityId)
return;
}
// Destruir o actor pra valer + remover do mapa. Antes, despawn apenas
// fazia SetActorHiddenInGame(true) via SetEntityRelevant(false), o que
// deixava o entry no mapa apontando pra um actor escondido. SPAWN
// subsequente do mesmo entityId era detectado como "duplicate" e nada
// acontecia — pawn ficava invisivel ate' o weak ptr ser GC'd. Bug
// confirmado nos logs do AOI ao oscilar entrada/saida da ZI/ZD.
if (AActor* Actor = Entry->Get())
{
if (IZMMOEntityInterface* AsEntity = Cast<IZMMOEntityInterface>(Actor))
{
AsEntity->SetEntityRelevant(false);
}
Actor->Destroy();
}
RemoteEntities.Remove(EntityId);
UE_LOG(LogZMMO, Log, TEXT("ZMMOWorldSubsystem: despawn EntityId=%d"), EntityId);
UE_LOG(LogZMMO, Log, TEXT("ZMMOWorldSubsystem: despawn EntityId=%d (destroyed)"), EntityId);
}
void UZMMOWorldSubsystem::HandlePlayerStateUpdate(const int32 EntityId, const int32 InputSeq,