Quick answer: A replicated actor never reaches a client through Replication Graph when its class is not registered with any node, when NetCullDistanceSquared is too small for the spatial grid cell size, or when an owner-required actor has no live OwnerActor. Register the class in InitGlobalActorClassSettings, raise NetCullDistanceSquared, use AlwaysRelevantForConnectionNode, validate owner relevancy, and call AddNetworkActor for runtime spawns.

Here is how to fix Unreal replicated actors that never reach a client when you switch from default replication to Replication Graph. The actor has bReplicates = true. The server clearly spawns it. Other replicated actors arrive on the client just fine. But this one is invisible — no OnRep fires, the client’s actor list does not contain it, and bandwidth profiles show zero bytes spent on its channel. Replication Graph trades the simplicity of per-actor relevancy for a node-based pipeline that is dramatically faster at scale, but every actor in your game now has to be explicitly routed through it.

The Symptom

You enable Replication Graph (net.RepGraph.Enable=1 or via UReplicationGraph set as the project’s replication driver) and one or more actor classes stop replicating. Common observations:

Worked before, broken now. Default UNetDriver replicated the actor without any extra setup. After switching to Replication Graph the same actor never appears on clients. Server logs show the actor exists; client logs show no spawn callback.

Replicates near origin, not at distance. The actor replicates fine when the player is near the world origin but disappears as the player moves away. Inverse failures (replicates at distance, not near origin) are also common when grid cell sizes are misconfigured.

Replicates to listen server’s own client only. On a listen server the host sees the actor, but remote clients do not. This usually means the actor is being routed through a node that depends on connection ownership and the remote connections are not in scope.

What Causes This

Actor class missing from any node’s replication list. Replication Graph builds a static map from UClass* to node policy in InitGlobalActorClassSettings. Classes not in that map fall through to the default policy, which in many sample graphs is “not replicated.” If you forgot to add your class, the graph silently ignores it.

NetCullDistance too small. Spatialized nodes (most commonly UReplicationGraphNode_GridSpatialization2D) cull actors that are further than sqrt(NetCullDistanceSquared) from any connection’s viewer. The default of around 150 meters is fine for shooters but wildly too small for open-world games.

Spatial frequency grid cell mismatch. The grid’s cell size is set on the graph itself. If the actor has a NetCullDistance smaller than the cell size, it never spans more than one cell and may end up in a cell no connection covers. The classic symptom is replication only working when the player and the actor are in the same cell.

GridSpatialization2D not registered. The grid node is created in InitGlobalGraphNodes via CreateNewNode and then added with AddGlobalGraphNode. Forgetting the second call means the node exists but never participates in the graph’s tick.

AlwaysRelevantNode missing. Game state, world settings, and other globally relevant actors must live on an UReplicationGraphNode_ActorList tied to AddGlobalGraphNode. Without it, they replicate only when they happen to fall inside a spatialized cell that includes a connection.

The Fix

Step 1: Register the actor class. In your custom UReplicationGraph subclass, override InitGlobalActorClassSettings and assign every replicated class to a node policy. The pattern below shows the three most common assignments: spatialized via the grid, always relevant to all connections, and always relevant for connection owner.

// MyReplicationGraph.cpp
void UMyReplicationGraph::InitGlobalActorClassSettings()
{
    Super::InitGlobalActorClassSettings();

    auto SetRule = [this](UClass* Class,
        EClassRepNodeMapping Mapping)
    {
        ClassRepNodePolicies.Set(Class, Mapping);
    };

    // Spatialized: routed via the GridSpatialization2D node
    SetRule(AEnemyPawn::StaticClass(),
        EClassRepNodeMapping::Spatialize_Dynamic);
    SetRule(APickupActor::StaticClass(),
        EClassRepNodeMapping::Spatialize_Static);

    // Always relevant globally
    SetRule(AGameStateBase::StaticClass(),
        EClassRepNodeMapping::RelevantAllConnections);

    // Always relevant for owning connection
    SetRule(APlayerController::StaticClass(),
        EClassRepNodeMapping::NotRouted);
    SetRule(AMyPlayerPawn::StaticClass(),
        EClassRepNodeMapping::Spatialize_Dynamic);
}

Step 2: Raise NetCullDistanceSquared. On your actor class, set NetCullDistanceSquared in the constructor to match your map’s expected visibility. Remember the value is in squared centimeters — a 100-meter cull distance is 10000.0 * 10000.0, or 100,000,000.

// EnemyPawn.cpp
AEnemyPawn::AEnemyPawn()
{
    PrimaryActorTick.bCanEverTick = true;
    bReplicates = true;
    bAlwaysRelevant = false;
    bNetLoadOnClient = true;

    // 200m visibility radius (in squared cm)
    NetCullDistanceSquared = 20000.0f * 20000.0f;
    NetUpdateFrequency = 30.0f;
    MinNetUpdateFrequency = 2.0f;
}

// In InitGlobalGraphNodes, set the grid cell size to span at
// least the largest NetCullDistance you support.
void UMyReplicationGraph::InitGlobalGraphNodes()
{
    Super::InitGlobalGraphNodes();

    GridNode = CreateNewNode<
        UReplicationGraphNode_GridSpatialization2D>();
    GridNode->CellSize = 10000.0f;        // 100m cells
    GridNode->SpatialBias = FVector2D(-200000.f, -200000.f);
    AddGlobalGraphNode(GridNode);

    AlwaysRelevantNode = CreateNewNode<
        UReplicationGraphNode_ActorList>();
    AddGlobalGraphNode(AlwaysRelevantNode);
}

Step 3: Use AlwaysRelevantForConnectionNode for owner-relevant actors. Player controllers, possessed pawns, and personal inventory should be relevant only to their owning connection. Override InitConnectionGraphNodes to add a per-connection node that returns the owning pawn, controller, and any per-player actors.

Step 4: Call AddNetworkActor for dynamic spawns. Most spawn paths route through the world’s OnActorSpawned delegate, which the graph hooks into by default. If you spawn from a low-level path that bypasses the delegate (subobject creation, deferred actor construction), call AddNetworkActor manually to make sure the graph sees the new actor.

Diagnosing With Console Commands

Replication Graph ships with strong introspection tools. Use them before guessing.

net.RepGraph.PrintGraph dumps the current graph and which nodes each actor lives on. If your actor is missing entirely, it is not registered. net.RepGraph.PrintAllActorInfo shows the resolved policy per actor. net.PktLag, net.PktLoss, and net.LogActorTraffic verify that the actor data is reaching the wire and not being dropped on the network layer.

Why This Works

Replication Graph replaces the per-frame, per-actor, per-connection loop of UNetDriver with a graph of nodes that pre-compute relevancy. Each node is responsible for emitting a list of actors that should be considered for replication to a given connection that frame. If your actor is not on any node’s list, the graph never produces it as a candidate and it is invisible to clients. Registering the class in InitGlobalActorClassSettings tells the graph which node owns each class, and the node-specific tuning (cell size, cull distance) controls when it actually emits the actor.

“Replication Graph is allow-listed by design. If you do not register a class with a node, that class does not replicate — even with bReplicates set. Treat the policy map as the source of truth for what your game can send.”

Related Issues

If actors replicate but their properties stay at default values, see Replicated Property Not Updating. If replication works but bandwidth is excessive, check Unreal Net Bandwidth Spikes.

PrintGraph first, edit policy second — Replication Graph bugs are usually map errors, not network errors.