From efd9921fde67b384655e4b2d7373ea646fe6e158 Mon Sep 17 00:00:00 2001 From: "j.foucher" Date: Thu, 25 Jun 2026 18:16:35 +0200 Subject: [PATCH] ElevenLabs: guard null gaze target on late join (replicated ref not resolved) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A client that joins while a conversation is already active can run OnRep with NetConnectedPawns/NetActiveSpeakerPawn entries whose replicated pawn references haven't resolved yet (actor channel not open). NetConnectedPawns.Num() > 0 did not guarantee a non-null element, so OnRep_ConversationState dereferenced a null pawn in its log (crash in Dev/PIE) and ApplyConversationGaze set TargetActor to null (gaze glitch). Both now skip until the reference resolves — OnRep fires again once it's patched in. More likely with 3+ clients. Co-Authored-By: Claude Opus 4.8 --- .../PS_AI_ConvAgent_ElevenLabsComponent.cpp | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent_ElevenLabsComponent.cpp b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent_ElevenLabsComponent.cpp index 9fbfe8e..10e0456 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent_ElevenLabsComponent.cpp +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent_ElevenLabsComponent.cpp @@ -2060,12 +2060,18 @@ void UPS_AI_ConvAgent_ElevenLabsComponent::OnRep_ConversationState() { Gaze->bActive = true; // Use active speaker if set, otherwise first connected pawn. + // GazeTarget can be null on a late joiner if the replicated pawn + // reference hasn't resolved yet (actor channel not open). Skip until + // it resolves — OnRep fires again once the reference is patched in. APawn* GazeTarget = NetActiveSpeakerPawn ? NetActiveSpeakerPawn : NetConnectedPawns.Last(); - Gaze->TargetActor = GazeTarget; - Gaze->ResetBodyTarget(); - Gaze->bEnableBodyTracking = true; - UE_LOG(LogPS_AI_ConvAgent_ElevenLabs, Log, - TEXT("[NET-REP] Gaze ACTIVATED, TargetActor set to %s"), *GazeTarget->GetName()); + if (GazeTarget) + { + Gaze->TargetActor = GazeTarget; + Gaze->ResetBodyTarget(); + Gaze->bEnableBodyTracking = true; + UE_LOG(LogPS_AI_ConvAgent_ElevenLabs, Log, + TEXT("[NET-REP] Gaze ACTIVATED, TargetActor set to %s"), *GazeTarget->GetName()); + } } else { @@ -2704,10 +2710,12 @@ void UPS_AI_ConvAgent_ElevenLabsComponent::ApplyConversationGaze() { Gaze->bActive = true; // Look at active speaker if set, otherwise last connected pawn. + // GazeTarget can be null if a replicated pawn ref hasn't resolved yet + // (late joiner). Don't overwrite the target with null in that case. AActor* GazeTarget = NetActiveSpeakerPawn ? static_cast(NetActiveSpeakerPawn) : static_cast(NetConnectedPawns.Last()); - if (Gaze->TargetActor != GazeTarget) + if (GazeTarget && Gaze->TargetActor != GazeTarget) { Gaze->TargetActor = GazeTarget; Gaze->ResetBodyTarget();