ue 5.8 level instance navmesh crashes

UE 5.8 Bug Report: NavDataRegistrationQueue null crash with streaming sub-levels

Description

Crash in UNavigationSystemV1::SpawnMissingNavigationDataInLevel when a streaming sub-level contains a saved RecastNavMesh actor. The NavDataRegistrationQueue accumulates null TObjectPtr<ANavigationData> entries that are never removed, causing ContainsByPredicate to dereference null.

Root Cause

  1. A streaming sub-level has a NavMeshBoundsVolume and was built in the editor, saving a RecastNavMesh actor inside the sub-level.

  2. At runtime, when the sub-level is loaded, ARecastNavMesh::PostLoad fires and calls RequestRegistration(), which calls RequestRegistrationDeferred(), adding the NavData to NavDataRegistrationQueue.

  3. Immediately after, ARecastNavMesh::CheckToDiscardSubLevelNavData destroys the NavData because it is not part of the PersistentLevel (by design — sub-level NavData should be discarded).

  4. The destroyed NavData pointer remains in NavDataRegistrationQueue as a TObjectPtr. After GC runs, the pointer becomes null.

  5. ProcessRegistrationCandidates (NavigationSystem.cpp:2775) encounters null entries but only skips them — it does NOT remove them:

ANavigationData* NavDataPtr = NavDataRegistrationQueue[CandidateIndex];
ULevel* OwningLevel = NavDataPtr != nullptr ? NavDataPtr->GetLevel() : nullptr;
if (OwningLevel && OwningLevel->bIsVisible)
{
    // Process and remove
    NavDataRegistrationQueue.RemoveAtSwap(CandidateIndex);
}
else
{
    // Skip — null entries are NEVER removed
}
  1. When PerformNavigationBoundsUpdate runs (triggered by NavMeshBoundsVolume registration during AddToWorld), it calls SpawnMissingNavigationData()SpawnMissingNavigationDataInLevel(), which calls ContainsByPredicate on the queue:
const bool bIsWaitingForRegistration = NavDataRegistrationQueue.ContainsByPredicate(
    [&NavConfig](const TObjectPtr<ANavigationData>& NavData)
{
    return NavData->GetConfig().Name == NavConfig.Name;  // CRASH: NavData is null
});

Reproduction Steps

  1. Create a persistent level (e.g., PersistentLevel).
  2. Create a streaming sub-level (e.g., SubLevel) with a NavMeshBoundsVolume.
  3. In the editor, build navigation for the sub-level. This saves a RecastNavMesh actor in the sub-level.
  4. Start PIE with the persistent level. Have the sub-level load at runtime (via ULevelStreamingDynamic or Level Instance).
  5. When the sub-level is added to the world (AddToWorld), the NavMeshBoundsVolume auto-registers via OnNavigationBoundsUpdated.
  6. On the next NavSys::Tick, PerformNavigationBoundsUpdate runs. If NavDataSet is empty (no NavData registered yet), SpawnMissingNavigationData is called.
  7. SpawnMissingNavigationDataInLevel calls ContainsByPredicate on NavDataRegistrationQueue, which contains null entries from step 3’s destroyed RecastNavMesh.
  8. Crash: null dereference in the lambda.

Call Stack

TArray::FindByPredicate()                   Array.h:1714
TArray::FindByPredicate()                   Array.h:1725
TArray::ContainsByPredicate()               Array.h:1779
UNavigationSystemV1::SpawnMissingNavigationDataInLevel()   NavigationSystem.cpp:4649
UNavigationSystemV1::SpawnMissingNavigationData()          NavigationSystem.cpp:4549
UNavigationSystemV1::PerformNavigationBoundsUpdate()       NavigationSystem.cpp:4387
UNavigationSystemV1::Tick()                                NavigationSystem.cpp:1651
UWorld::Tick()                                              LevelTick.cpp:1647

Verbose Log Evidence

With log LogNavigation Verbose:

ARecastNavMesh::PostLoad RecastNavMesh ...SubLevel:PersistentLevel.RecastNavMesh-Default
UNavigationSystemV1::RequestRegistrationDeferred Adding to registration queue: RecastNavMesh ...SubLevel...
ARecastNavMesh::CheckToDiscardSubLevelNavData Discarding RecastNavMesh ...SubLevel... due to it not being part of PersistentLevel.

// Later, ProcessRegistrationCandidates repeatedly skips null entries:
UNavigationSystemV1::ProcessRegistrationCandidates   Skipped None because owning level not visible.
UNavigationSystemV1::ProcessRegistrationCandidates   Skipped None because owning level not visible.

// Then crash:
UNavigationSystemV1::PerformNavigationBoundsUpdate Add bounds Min=...

The crash only stops if I manually delete all NavMeshBoundsVolume actors from every Level Instance asset. However, this is not a viable long-term solution because the editor re-creates RecastNavMesh in the sub-level every time navigation is built or the level is saved.
The user is saying that UE 5.7 doesn’t have this bug. This is important information for the bug report - it means the bug was introduced in UE 5.8.

LoginId:1f8950c942f841ffe92b44827e3bcd98
EpicAccountId:5a53924e59904e62b4becb2534af8a89

Unhandled Exception: EXCEPTION_ACCESS_VIOLATION reading address 0x00000000000004b8

UnrealEditor_NavigationSystem!UNavigationSystemV1::SpawnMissingNavigationDataInLevel() [D:\build++UE5\Sync\Engine\Source\Runtime\NavigationSystem\Private\NavigationSystem.cpp:4649]
UnrealEditor_NavigationSystem!UNavigationSystemV1::SpawnMissingNavigationData() [D:\build++UE5\Sync\Engine\Source\Runtime\NavigationSystem\Private\NavigationSystem.cpp:4552]
UnrealEditor_NavigationSystem!UNavigationSystemV1::PerformNavigationBoundsUpdate() [D:\build++UE5\Sync\Engine\Source\Runtime\NavigationSystem\Private\NavigationSystem.cpp:4388]
UnrealEditor_NavigationSystem!UNavigationSystemV1::Tick() [D:\build++UE5\Sync\Engine\Source\Runtime\NavigationSystem\Private\NavigationSystem.cpp:1652]
UnrealEditor_Engine!UWorld::Tick() [D:\build++UE5\Sync\Engine\Source\Runtime\Engine\Private\LevelTick.cpp:1647]
UnrealEditor_UnrealEd!UEditorEngine::Tick() [D:\build++UE5\Sync\Engine\Source\Editor\UnrealEd\Private\EditorEngine.cpp:2171]
UnrealEditor_UnrealEd!UUnrealEdEngine::Tick() [D:\build++UE5\Sync\Engine\Source\Editor\UnrealEd\Private\UnrealEdEngine.cpp:546]
UnrealEditor!FEngineLoop::Tick() [D:\build++UE5\Sync\Engine\Source\Runtime\Launch\Private\LaunchEngineLoop.cpp:5859]
UnrealEditor!GuardedMain() [D:\build++UE5\Sync\Engine\Source\Runtime\Launch\Private\Launch.cpp:190]
UnrealEditor!GuardedMainWrapper() [D:\build++UE5\Sync\Engine\Source\Runtime\Launch\Private\Windows\LaunchWindows.cpp:123]
UnrealEditor!LaunchWindowsStartup() [D:\build++UE5\Sync\Engine\Source\Runtime\Launch\Private\Windows\LaunchWindows.cpp:277]
UnrealEditor!WinMain() [D:\build++UE5\Sync\Engine\Source\Runtime\Launch\Private\Windows\LaunchWindows.cpp:338]
UnrealEditor!__scrt_common_main_seh() [D:\a_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288]
kernel32
ntdll