StreamingGeneration: Actor desc mutator phase now writes ActorDescView directly, breaking the cluster-invariant check in ValidateContainerInstanceDescriptors

Hey folks,

We hit a regression upgrading our project from 5.7 to 5.8. Map Check (and any other path that ends up calling UWorldPartition::CheckForErrors) assert inside FWorldPartitionStreamingGenerator::ValidateContainerInstanceDescriptors, on the RuntimeGrid check():

for (const FGuid& ActorGuid : Cluster)

{

const FStreamingGenerationActorDescView& ActorDescView =

ContainerCollectionInstanceDescriptor.ActorDescViewMap->FindByGuidChecked(ActorGuid);

check(ActorDescView.GetRuntimeGrid() == ReferenceActorDescView.GetRuntimeGrid());

check(ActorDescView.GetIsSpatiallyLoaded() == ReferenceActorDescView.GetIsSpatiallyLoaded());

check(ActorDescView.GetContentBundleGuid() == ReferenceActorDescView.GetContentBundleGuid());

check(ActorDescView.GetExternalDataLayerAsset()== ReferenceActorDescView.GetExternalDataLayerAsset());

}

What changed between 5.7 and 5.8

Phase ordering inside PreparationPhase did not change. The mutator is still called between two

ValidateContainerInstanceDescriptors() passes. Clusters are still built in UpdateContainerDescriptor → GenerateObjectsClusters from references only. What changed is what the mutator-apply loop writes into.

5.7:

FContainerCollectionInstanceDescriptor::FPerInstanceData PerInstanceData =
    ContainerCollectionInstanceDescriptor.GetPerInstanceData(ActorGuid);
 
if (ActorDescViewMutator.RuntimeGrid.IsSet())
{
    PerInstanceData.RuntimeGrid = ActorDescViewMutator.RuntimeGrid.GetValue();
}
 
ContainerCollectionInstanceDescriptor.AddPerInstanceData(ActorGuid, PerInstanceData);

Mutator writes a per-instance side-table. FStreamingGenerationActorDescView::GetRuntimeGrid() keeps returning the pre-mutation value, so the second ValidateContainerInstanceDescriptors pass sees a uniform grid per cluster and passes.

5.8 (current):

if (FStreamingGenerationActorDescView* ActorDescView =
        ContainerCollectionInstanceDescriptor.ActorDescViewMap->FindByGuid(ActorGuid))
{
    if (ActorDescViewMutator.RuntimeGrid.IsSet())
    {
        ActorDescView->SetRuntimeGrid(ActorDescViewMutator.RuntimeGrid.GetValue());
    }
    // ... same pattern for bIsSpatiallyLoaded, bIsHLODRelevant, HLODLayer
}

FPerInstanceData, AddPerInstanceData, GetPerInstanceData were removed; the mutator now writes the shared ActorDescView directly. The second ValidateContainerInstanceDescriptors pass now reads the post-mutation grid and trips the invariant whenever the mutator override touched only some of a ref-connected cluster.

Workaround:

We have made a small internal change that re-splits each ref-built cluster by the four invariant keys right after MutateContainerINstanceDescriptors, before the second validate:

if (MutateContainerInstanceDescriptors(...))
{
    SplitClustersByMutatedAttributes(); // bucket each cluster by (RuntimeGrid, bIsSpatiallyLoaded, ContentBundleGuid, ExternalDataLayerAsset)
    ValidateContainerInstanceDescriptors();
}

Don’t know if this was a known issue and if you we are doing something wrong

Thanks in advance,

Phil

[Attachment Removed]

Hi,

Thanks for the detailed report. I filled a bug report and notified the engine team. You will soon be able to follow its evolution on: https://issues.unrealengine.com/issue/UE\-381148

The problem should be fixed in the official release.

Regards,

Martin

[Attachment Removed]

Thanks for reporting this.

Actors that reference each other must always load together and that is what this check is protecting. The check ensures that all actors in a cluster end up in the same streaming grid. Otherwise, they cannot be placed in the same cell and therefore will not load together.

It seems that your mutator override changes the grid for some actors in the cluster but not for others. Because of that, the streaming system can no longer keep them together. Actor A could load while actor B (which A references) has not loaded yet, breaking the reference at runtime.

UE 5.7 didn’t assert in this situation, but the override was silently dropped instead. Your mutator was probably not doing what you expected in some cases, just without any warning.

Your workaround splits the clusters, which bypasses the check but introduces cross-cell references at runtime, which is the same problem the check is trying to prevent.

I submitted the change to UE5-Main (CL 54431689) that should detect these cases, drop mutator overrides that would split the cluster, keep the actors together, and generate a Map Check warning for each dropped mutator override.

Could you try that change and confirm whether it gets rid of the assertion? It will probably generate Map Check warnings that you’ll need to address in your mutator.

Thanks,

Andrzej

[Attachment Removed]

Actually, I slightly modified my Mutator Overrides logic to keep together referenced actors and it works like a charm.

Thanks for your help :slightly_smiling_face:

Phil

[Attachment Removed]