Replicated Patrol
Server-authoritative GOAP in a replicated actor. The server plans; clients observe the agent's current action via standard UE replication.
[ Example ]
Replicated Patrol — multiplayer
The canonical pattern for putting an Intent Forge agent in a replicated actor — the kind you'd ship in a co-op shooter, hosted PvE encounter, or any multiplayer scenario where AI behaviour must be consistent across all clients. The server runs the planner; clients receive the agent's current action via UE's standard replication and use it to drive cosmetic state (animation, VFX, audio) without ever planning themselves.
The actor shape
// In your project — not in IntentForgePlugin
UCLASS()
class AReplicatedPatrolGuard : public APawn
{
GENERATED_BODY()
public:
AReplicatedPatrolGuard();
protected:
virtual void BeginPlay() override;
UPROPERTY(VisibleAnywhere)
TObjectPtr<UIntentForgeComponent> IntentForge;
UFUNCTION()
void HandleReplicatedActionChanged(
const FIntentForgeReplicatedAction& NewAction,
const FIntentForgeReplicatedAction& PreviousAction);
};AReplicatedPatrolGuard::AReplicatedPatrolGuard()
{
bReplicates = true;
SetReplicatingMovement(true);
IntentForge = CreateDefaultSubobject<UIntentForgeComponent>(TEXT("IntentForge"));
// The component opts into replication automatically.
}
void AReplicatedPatrolGuard::BeginPlay()
{
Super::BeginPlay();
// Delegate fires only on non-authoritative clients.
IntentForge->OnReplicatedActionChanged.AddDynamic(
this, &AReplicatedPatrolGuard::HandleReplicatedActionChanged);
}
void AReplicatedPatrolGuard::HandleReplicatedActionChanged(
const FIntentForgeReplicatedAction& NewAction,
const FIntentForgeReplicatedAction& PreviousAction)
{
// Resolve ActionId via GetActionByName — GetActionByHandle is NOT
// available here (the wire snapshot omits the handle index;
// cosmetics key on action identity, not plan position).
if (const UIntentAction* Action = IntentForge->GetActionByName(NewAction.ActionId))
{
// Action->ExecutorClass, Action->DefaultParams, or your
// designer-authored cosmetic metadata is now available
// for the anim BP / VFX pick.
}
// NewAction.ActionId == NAME_None is the idle sentinel.
}Authoring the archetype
The archetype, schema, actions, and goals are identical to the single-player Patrol / Chase example. Networking doesn't change the planning surface — only where the planner runs.
DA_Schema_Patrol, DA_Action_Patrol, DA_Action_Chase, DA_Goal_Patrol, DA_Goal_Chase from the single-player example.DA_Archetype_PatrolGuard.UIntentForgeComponent in your replicated pawn Blueprint or C++ class.What replicates (and what doesn't)
| Replicated | Read on client via |
|---|---|
ReplicatedAction.ActionId (FName) | OnReplicatedActionChanged delegate |
Archetype (UIntentArchetypeAsset*) | GetActionByName(ActionId) once Archetype arrives |
PlanGeneration (int32) | GetPlanGeneration() |
| Not replicated | Lives on |
|---|---|
Full FPlan | Server only — clients see one action at a time |
| Plan step index | Server only — cosmetics key on action identity, not position |
FWorldState | Server only — sensor data never crosses the wire |
| Active executor instance | Server only — clients drive cosmetics from delegate |
| Recent plan / activity history | Server only — diagnostic data |
| Goal scores, considerations | Server only — internal to the planner |
Bandwidth per replicated agent is roughly 20–80 bytes/second, peaking
briefly when a multi-step plan transitions. The anti-flap stack keeps
ReplicatedAction stable, so most of the time nothing is going over the
wire.
Where to write facts
The recommended pattern is server-side only. Your gameplay code on the
server detects events ("player entered patrol zone"), calls
SetFactBool(NearPlayer, true) on the component, and the component
replans + replicates the new ReplicatedAction automatically.
A client that calls SetFactBool is no-oped by default. The console
variable IntentForge.bAllowClientFactWrites (default false) is the
gate for a future client→server fact-write RPC. In v1.0-alpha, flipping
it to true does not yet route through an RPC — it logs and drops with
an explanatory message. Use it for development / debugging only.
Don't ship production multiplayer with it enabled; write facts from
server-side gameplay systems where the authority and replication
contracts are clean.
Driving a replan from a client gameplay event
When a client-side event (UI interaction, predicted hit confirmation,
ability cast) needs the server's agent to re-evaluate immediately rather
than wait for the safety-net timer, call RequestServerReplan from the
client:
// On the owning client (or any client with relevance to the actor)
void AYourPlayerController::OnQuestItemPickedUp()
{
if (AReplicatedPatrolGuard* Nearby = GetNearbyGuard())
{
Nearby->IntentForge->RequestServerReplan();
}
}RequestServerReplan is the Blueprint-friendly wrapper. On the authority
it forwards to RequestReplan() directly. On a client it verifies the
owning actor has a network connection (i.e. a pawn possessed by the local
PlayerController) before invoking the underlying ServerRequestReplan
RPC. If no owning connection exists, it logs a warning and returns — UE
silently drops Server RPCs from connectionless actors, so the helper
surfaces the failure mode.
The RPC itself is Server, Unreliable, WithValidation. Server-side, the
handler applies a cooldown via LastReplanTime to drop redundant
requests, so spamming the call is safe.
Authority gating model
Every mutating entry point on UIntentForgeComponent checks
HasAuthority() (via an internal ComponentIsAuthoritative helper) and
no-ops on clients. This includes:
SetArchetypeSetFactBool/SetFactScalar/SetFactObject/ClearFactObjectBatchMutateRequestReplan(and the internalEvaluateReplanit schedules)NotifyActionCompleted- Sensor sampling and the safety-net replan timer
Clients are passive observers. They receive ReplicatedAction,
Archetype, and PlanGeneration via standard UE replication; they do
not run sensors, the planner, or the dispatcher.
Authority-dependent initialization is deferred to BeginPlay rather than
InitializeComponent. HasAuthority() is unreliable during
InitializeComponent on replicated actors — the net role hasn't been
assigned yet on placed actors, so initializing there could either
double-init or skip-init depending on the load path. The component also
guards both initialization helpers with idempotency checks so a
pre-BeginPlay SetArchetype call doesn't double-up when BeginPlay runs.
Anti-flap is server-only
The five-family Anti-Flap Toolkit runs on the
server. Clients see the result — a stable CurrentAction that only
changes when the server's stack has decided the change is real. You don't
need to implement any flap correction on the client; the animation state
will be pre-stabilised by the time you see it.
For animation Blueprints: bind state transitions to
OnReplicatedActionChanged and trust that the input is clean.
Dispatcher mode for replicated agents
For replicated agents, use DispatchMode = Auto (the default) on the
server. The Auto dispatcher only registers components when
Owner->HasAuthority(), so clients automatically skip registration and
never spawn executors.
| Dispatch mode | Server-side behaviour | Client-side behaviour |
|---|---|---|
Auto (default) | Plans + executes via the dispatcher subsystem | No registration; no executor |
StateTree | Plans inside the StateTree; the IntentForge task drives executors | Wrap the task in a Server Authority decorator |
BehaviorTree | Plans inside the BT; standard AIController already runs server-only | No special handling needed |
External | Your code owns the lifecycle | Your code owns the lifecycle — gate on HasAuthority |
Testing locally
AReplicatedPatrolGuard actors in the level.CurrentAction populated but nothing else — the client never planned and has no local state to inspect.OnReplicatedActionChanged — the server's plan adoption shows up after the network's RTT.What this example does not cover (yet)
- Client-side prediction with rollback — the v2.0 candidate for high-latency / fast-paced shooter shapes. The current model trades ~100ms perceived latency for simplicity and cheat-resistance.
- Per-fact replication — projects that want clients to observe
specific facts (e.g. a guard's alert level for HUD display) will need
to mirror those facts onto the actor as separate replicated UPROPERTYs
in v1.0-alpha. A
FactSchemaAsset-level replication policy is a v1.x candidate. - Headless dedicated server tuning — works by default; profiling notes for high-agent-count dedicated servers are a roadmap item.