FActorInstanceHandle Lazy Resolution Breaks FHitResult Under IRIS Native Serialization

Hello

We ran into this issue on our 5.7.1 project, but have confirmed it is still happening on a clean 5.7.3 project.

I don’t think it needs Lyra to reproduce it, but it matches our setup closest and was easy to add

Please download our reproduction project and read the README.md file in it with more details and code snippets.

It is 100% repro for us

---

More technical details:

When physics traces fill FHitResult, HitObjectHandle (FActorInstanceHandle) stores the hit UPrimitiveComponent in ReferenceObject with ResolutionStatus=NeedsResolving.

The owning actor is resolved lazily on first GetActor() call. FActorInstanceHandle::ResolutionStatus is NOT a UPROPERTY — it is a plain C++ member.

When IRIS uses FStructNetSerializer for structs in SupportsStructNetSerializerList, it only serializes UPROPERTYs. ResolutionStatus is lost, defaulting to Invalid on the server.

ResolveHandle() then skips resolution (status is Invalid, not NeedsResolving), and Cast<AActor>(UStaticMeshComponent*) returns NULL.

The legacy operator<< path (ActorInstanceHandle.cpp:494-534) avoids this by calling ResolveHandle() before saving and explicitly setting ResolutionStatus=Resolved on load.

No custom FActorInstanceHandleNetSerializer exists in the engine to replicate this behavior under IRIS.

---

Please confirm if this is expected behaviour. This really surprised us and the only workaround for now is to manually call

HitResult.GetActor()

Before its set in the struct for serialization

Thanks

Karl

[Attachment Removed]

Steps to Reproduce
ENVIRONMENT:

- Engine: UE 5.7 (stock, no engine modifications)

- Project: Lyra Sample Game (from Fab/Marketplace)

- Platform: Windows (Win64), Development Editor

- Net Mode: Play As Client with dedicated server

REQUIRED SETTINGS:

- IRIS replication enabled (net.Iris.UseIrisReplication=1 in DefaultEngine.ini [ConsoleVariables])

- FGameplayAbilityTargetData_SingleTargetHit registered in SupportsStructNetSerializerList (this is already the case in BaseEngine.ini by default)

- PIE: Run Under One Process = Unchecked, Run Dedicated Server as Separate Process = Checked

RELEVANT ENGINE FEATURE:

- IRIS native struct serialization (FStructNetSerializer) for structs in SupportsStructNetSerializerList

- FActorInstanceHandle lazy resolution in FHitResult.HitObjectHandle

REPRO PROJECT:

The attached Lyra project contains a minimal repro ability (GA_IrisHitResultRepro) and two cheat commands. Only 4 files were added/modified from stock Lyra:

- Source/LyraGame/Tests/GA_IrisHitResultRepro.h/.cpp (new — repro ability)

- Source/LyraGame/Player/LyraCheatManager.h/.cpp (modified — added 2 cheat commands)

- Config/DefaultEngine.ini (modified — added net.Iris.UseIrisReplication=1 and base struct to SupportsStructNetSerializerList)

STEPS TO REPRODUCE:

1. Open the attached Lyra project (Lyra.uproject) in UE 5.7 Editor.

2. Build the project (Development Editor, Win64).

3. Configure PIE settings (Edit > Editor Preferences > Level Editor > Play):

- Number of Players: 1

- Net Mode: Play As Client

- Run Under One Process: Unchecked

- Run Dedicated Server as Separate Process: Checked

4. Hit Play. Wait for the dedicated server log window and client window to appear. Wait for the client to fully spawn into the map.

5. On the client viewport, open the console (~ key) and type:

Cheat IrisRepro

This sends a command to the server which grants the repro ability to the player’s AbilitySystemComponent.

6. Aim the camera at any static mesh in the level (a wall, the floor, a prop — anything with physics collision).

7. Open the console again and type:

IrisReproFire

This activates the repro ability on the client, which:

a) Performs a LineTraceSingleByChannel (ECC_Visibility) from the camera

b) Packages the resulting FHitResult into FGameplayAbilityTargetData_SingleTargetHit

c) Sends it to the server via ServerSetReplicatedTargetData (GAS target data RPC)

8. Check the dedicated server log window for the result.

EXPECTED RESULT:

Server log shows the hit actor name:

LogIrisRepro: Display: SERVER: bBlockingHit=true GetActor()=SomeActor_0 GetComponent()=StaticMeshComponent0

ACTUAL RESULT:

Server log shows NULL actor:

LogIrisRepro: Error: SERVER: bBlockingHit=true GetActor()=NULL GetComponent()=StaticMeshComponent0 <-- BUG

Note: GetComponent() returns a valid component, confirming the FHitResult data arrived correctly. Only GetActor() fails.

WORKAROUND VERIFICATION:

To confirm the root cause, open Source/LyraGame/Tests/GA_IrisHitResultRepro.cpp, find the commented line (around line 105):

// HitResult.GetActor(); // <-- UNCOMMENT THIS LINE TO FIX THE BUG

Uncomment it, recompile, and repeat steps 4-8. GetActor() now returns the correct actor because calling it before serialization forces FActorInstanceHandle lazy resolution, updating ReferenceObject from the hit component to the owning actor.

[Attachment Removed]

Hi,

Thank you for the detailed report and repro project!

Given that this is causing a difference in behavior between the generic replication system and Iris, it’s likely that this is unintended behavior. I’ve opened a new issue, UE-365455, which should be visible in the public tracker in a day or so.

Thanks,

Alex

[Attachment Removed]