Summary
When a Verse device that contains an array of device references is duplicated, the resulting copy does not reference the devices correctly.
It seems to result in hybrid references that refer to the correct transform, but the rest of the Actor from the wrong device, associated with one of the other array elements.
This occurs even if the array indirectly contains the device references, such as in an array of structs or classes that themselves contain the device reference.
We think it may have started with v37.00 due to some issues we’ve observed, but the repro steps below were only tested in v37.30.
I’ve noted some issues that may be related have been reported but I thought detailed repro steps could help get this fixed.
Please select what you are reporting on:
Unreal Editor for Fortnite
What Type of Bug are you experiencing?
Verse
Steps to Reproduce
From a blank map, create a Verse file ArrayReferencer.verse
with the following code:
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
Msg<localizes>(s:string) : message = "{s}"
ArrayReferencer := class(creative_device) :
@editable var Buttons <private> : []button_device = array{}
OnBegin<override>()<suspends> : void =
for (ButtonIndex->Button : Buttons):
Init(ButtonIndex, Button)
Init <public> (ButtonIndex: int, Button: button_device) : void =
ButtonVal := Round[Pow(2.0, (ButtonIndex+1)*1.0)] or 0
OldVal := Button.GetMaxTriggerCount()
NewVal := OldVal + ButtonVal
Button.SetMaxTriggerCount(NewVal)
Print("Init Button {ButtonIndex+1}. OldVal: {OldVal} + Val: {ButtonVal} => NewVal: {NewVal}. PosY: {Button.GetTransform().Translation.Y}")
Button.SetInteractionText(Msg("Button {ButtonIndex+1}\nValue: {NewVal}\nPosY: {Button.GetTransform().Translation.Y}"))
Button.InteractedWithEvent.Subscribe(ButtonCallback{ButtonIndex:=ButtonIndex, Button:=Button}.OnInteracted)
ButtonCallback := class:
ButtonIndex : int;
Button : button_device;
OnInteracted(Agent : agent) : void =
Print("OnInteracted Button {ButtonIndex+1}: PosY: {Button.GetTransform().Translation.Y}")
Place 3 button devices with distinct Y (or Left) positions, for instance:
Button1: (X=0.0,Y=-100.0,Z=100.0)
Button2: (X=0.0,Y=-200.0,Z=100.0)
Button3: (X=0.0,Y=-300.0,Z=100.0)
This will help see which transform we’re referencing.
In the verse, we’re reading and writing a value to each Button device’s MaxTriggerCount
, just as a marker. The value 2<<(ButtonIndex+1)
is being added in order to provide for a simple bitfield to observe which Button references overlap.
Logs are provided to observe if the Buttons match as expected during startup and interacting, and details are written to their interaction text.
Place our ArrayReferencer
device and add 3 array elements, and assign the 3 Button devices in order.
If we test at this point, the results described in the Expected result section are seen.
Now, duplicate the ArrayReferencer
device in the outliner and delete the original.
Start a session and observe the startup logs, the Buttons’ interact messages, and the logs when interacting with the buttons.
Expected Result
Startup logs:
Init Button 1. OldVal: 0 + Val: 2 => NewVal: 2. PosY: -100.000
Init Button 2. OldVal: 0 + Val: 4 => NewVal: 4. PosY: -200.000
Init Button 3. OldVal: 0 + Val: 8 => NewVal: 8. PosY: -300.000
Observe that Buttons 1, 2, 3, values 2, 4, 8, and positions -100, -200, -300 match.
Button InteractedWithEvent
output when pressing each button:
Button1: Button 1: Value: 2 PosY: -100.000
Button2: Button 2: Value: 4 PosY: -200.000
Button3: Button 3: Value: 8 PosY: -300.000
Observe that Buttons 1, 2, 3, values 2, 4, 8, and positions -100, -200, -300 match.
Button OnInteract output for each button:
Button1: OnInteracted Button 1: PosY: -100.000
Button2: OnInteracted Button 2: PosY: -200.000
Button3: OnInteracted Button 3: PosY: -300.000
Observe that Buttons 1, 2, 3 trigger the corresponding registered callback.
Observed Result
Startup logs:
Init Button 1. OldVal: 0 + Val: 2 => NewVal: 2. PosY: -100.000
Init Button 2. OldVal: 2 + Val: 4 => NewVal: 6. PosY: -200.000
Init Button 3. OldVal: 0 + Val: 8 => NewVal: 8. PosY: -300.000
Observe that the value for Button 2 is carried over from Button 1 and added up, despite the transform dereferenced from the same variable being that of a different Button device.
Button interaction text:
Button 1: Button 2: Value: 6 PosY: -200.000
Button 2: Button 3: Value: 8 PosY: -300.000
Button 3: INTERACT
Observe that the button text does not match the intended button, and the last button text has not been set.
Button InteractedWithEvent
output when pressing each button:
Buttons 1 and 2 produce the same output - all 3 callbacks are triggered from either button (in a random order).
OnInteracted Button 2: PosY: -200.000
OnInteracted Button 1: PosY: -100.000
OnInteracted Button 3: PosY: -300.000
Button 3 does nothing, no callback seems registered.
Platform(s)
Windows
Additional Notes
One tentative explanation I have is that there exists an internal reference system that supports both Scene Graph components like the transform and the legacy Actor components, and it’s somehow getting out of sync between the two in serialized arrays during deserialization, resulting in these strange chimera references being created, mixing multiple target objects depending on the component being accessed.
But that’s just a theory, I haven’t been able to think of an other explanation.
I’ve not created test cases around other devices than Button devices, but we’ve seen issues in practice with Mutator Zones and other things.
Testing using an array of classes or structs that contain the reference behave the same. The class/struct references are correct, only the device reference is problematic.
I haven’t been able to reproduce any such issue if the device references are not in an array but are distinct variables in the Verse device.
The bug does not seem user-detectable from the editor, everything in the affected actor looks fine, and the issue only becomes apparent at runtime. Nothing obvious stands out in the serialized text when copying the device, but it’s hard to be sure.
I’ve seen some similar-ish reports in recent days but none included detailed enough repro steps, so I thought it was worth making sure a good repro was made available to help track this down.