Summary
When new elements are added to a class-level array literal initializer in a plain Verse class, most device instances that instantiate that class still see the old, shorter array at runtime. Only 1-2 instances see the correct updated length. The array data is genuinely truncated, not just the .Length value. Failable array access fails for indices beyond the old array size. This causes silent logic failures with no compile-time or runtime error.
Please select what you are reporting on:
Verse
What Type of Bug are you experiencing?
Verse
Steps to Reproduce
- Create a simple data class and a helper class with an array literal containing 32 elements:
my_item := class<concrete>()
{
ID : int = 0
Name : string = ""
}
my_helper := class()
{
Items : []my_item = array{
my_item{ ID := 1, Name := "Item1" },
my_item{ ID := 2, Name := "Item2" },
my_item{ ID := 3, Name := "Item3" },
my_item{ ID := 4, Name := "Item4" },
my_item{ ID := 5, Name := "Item5" },
my_item{ ID := 6, Name := "Item6" },
my_item{ ID := 7, Name := "Item7" },
my_item{ ID := 8, Name := "Item8" },
my_item{ ID := 9, Name := "Item9" },
my_item{ ID := 10, Name := "Item10" },
my_item{ ID := 11, Name := "Item11" },
my_item{ ID := 12, Name := "Item12" },
my_item{ ID := 13, Name := "Item13" },
my_item{ ID := 14, Name := "Item14" },
my_item{ ID := 15, Name := "Item15" },
my_item{ ID := 16, Name := "Item16" },
my_item{ ID := 17, Name := "Item17" },
my_item{ ID := 18, Name := "Item18" },
my_item{ ID := 19, Name := "Item19" },
my_item{ ID := 20, Name := "Item20" },
my_item{ ID := 21, Name := "Item21" },
my_item{ ID := 22, Name := "Item22" },
my_item{ ID := 23, Name := "Item23" },
my_item{ ID := 24, Name := "Item24" },
my_item{ ID := 25, Name := "Item25" },
my_item{ ID := 26, Name := "Item26" },
my_item{ ID := 27, Name := "Item27" },
my_item{ ID := 28, Name := "Item28" },
my_item{ ID := 29, Name := "Item29" },
my_item{ ID := 30, Name := "Item30" },
my_item{ ID := 31, Name := "Item31" },
my_item{ ID := 32, Name := "Item32" }
}
GetItem(Index:int):?my_item=
{
if (Item := Items[Index])
{
return option{Item}
}
return false
}
}
- Create a creative_device subclass that instantiates this helper and logs diagnostics:
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /Verse.org/Simulation/Tags }
my_test_device_tag := class(tag){}
my_test_device := class(creative_device)
{
@editable DeviceID : int = 1
Helper : my_helper = my_helper{}
OnBegin<override>()<suspends>:void=
{
Print("DEBUG - DeviceID={DeviceID}, ArrayLength={Helper.Items.Length}")
# Try to access this device's own item
ItemIndex := DeviceID - 1
MaybeItem := Helper.GetItem(ItemIndex)
if (Item := MaybeItem?)
{
Print("DEBUG - DeviceID={DeviceID}, Found: ID={Item.ID}, Name={Item.Name}")
}
else
{
Print("BUG - DeviceID={DeviceID}, FAILED to access index {ItemIndex}")
}
# Also try to access the last item to test full array availability
LastIndex := Helper.Items.Length - 1
MaybeLastItem := Helper.GetItem(LastIndex)
if (LastItem := MaybeLastItem?)
{
Print("DEBUG - DeviceID={DeviceID}, Last item: ID={LastItem.ID}, Name={LastItem.Name}")
}
else
{
Print("BUG - DeviceID={DeviceID}, FAILED to access last index {LastIndex}")
}
}
}
-
Place 32 instances of my_test_device in the world, setting DeviceID on each to values 1 through 32. Tag each with my_test_device_tag.
-
Build Verse code, launch a session, and confirm all 32 devices report ArrayLength=32 and successfully access their items. This establishes the baseline.
-
Now add 12 new elements to the array in my_helper (Items 33-44):
# ... existing items 1-32 above ...
my_item{ ID := 33, Name := "Item33" },
my_item{ ID := 34, Name := "Item34" },
my_item{ ID := 35, Name := "Item35" },
my_item{ ID := 36, Name := "Item36" },
my_item{ ID := 37, Name := "Item37" },
my_item{ ID := 38, Name := "Item38" },
my_item{ ID := 39, Name := "Item39" },
my_item{ ID := 40, Name := "Item40" },
my_item{ ID := 41, Name := "Item41" },
my_item{ ID := 42, Name := "Item42" },
my_item{ ID := 43, Name := "Item43" },
my_item{ ID := 44, Name := "Item44" }
-
Place 12 new my_test_device instances for DeviceIDs 33-44.
-
Build Verse code and launch a new session.
-
Observe log output.
Expected Result
All 44 device instances should report ArrayLength=44 and successfully access both their own item and the last item in the array:
LogVerse: : DEBUG - DeviceID=1, ArrayLength=44
LogVerse: : DEBUG - DeviceID=1, Found: ID=1, Name=Item1
LogVerse: : DEBUG - DeviceID=1, Last item: ID=44, Name=Item44
LogVerse: : DEBUG - DeviceID=2, ArrayLength=44
LogVerse: : DEBUG - DeviceID=2, Found: ID=2, Name=Item2
LogVerse: : DEBUG - DeviceID=2, Last item: ID=44, Name=Item44
...
LogVerse: : DEBUG - DeviceID=33, ArrayLength=44
LogVerse: : DEBUG - DeviceID=33, Found: ID=33, Name=Item33
LogVerse: : DEBUG - DeviceID=33, Last item: ID=44, Name=Item44
...
LogVerse: : DEBUG - DeviceID=44, ArrayLength=44
LogVerse: : DEBUG - DeviceID=44, Found: ID=44, Name=Item44
LogVerse: : DEBUG - DeviceID=44, Last item: ID=44, Name=Item44
Observed Result
After adding elements 33-44 and rebuilding, most device instances report the old stale array length of 32. Only 1-2 instances report the correct length of 44. This is not just a .Length reporting issue. The array data is genuinely truncated at runtime. Failable array access (if (Item := Items[Index])) fails for indices 32+ on affected instances, meaning the newly added elements do not exist in memory for those instances despite being present in the source code.
This causes downstream logic to silently fall through to default values (zeros, empty strings, false) with no compile-time or runtime error, leading to incorrect game behavior that is extremely difficult to diagnose.
LogVerse: : DEBUG - DeviceID=1, ArrayLength=44
LogVerse: : DEBUG - DeviceID=1, Found: ID=1, Name=Item1
LogVerse: : DEBUG - DeviceID=1, Last item: ID=44, Name=Item44
LogVerse: : DEBUG - DeviceID=2, ArrayLength=32
LogVerse: : DEBUG - DeviceID=2, Found: ID=2, Name=Item2
LogVerse: : DEBUG - DeviceID=2, Last item: ID=32, Name=Item32
LogVerse: : DEBUG - DeviceID=3, ArrayLength=32
LogVerse: : DEBUG - DeviceID=3, Found: ID=3, Name=Item3
LogVerse: : DEBUG - DeviceID=3, Last item: ID=32, Name=Item32
...
LogVerse: : DEBUG - DeviceID=32, ArrayLength=32
LogVerse: : DEBUG - DeviceID=32, Found: ID=32, Name=Item32
LogVerse: : DEBUG - DeviceID=32, Last item: ID=32, Name=Item32
LogVerse: : DEBUG - DeviceID=33, ArrayLength=32
LogVerse: : BUG - DeviceID=33, FAILED to access index 32
LogVerse: : DEBUG - DeviceID=33, Last item: ID=32, Name=Item32
LogVerse: : DEBUG - DeviceID=34, ArrayLength=32
LogVerse: : BUG - DeviceID=34, FAILED to access index 33
LogVerse: : DEBUG - DeviceID=34, Last item: ID=32, Name=Item32
...
LogVerse: : DEBUG - DeviceID=44, ArrayLength=32
LogVerse: : BUG - DeviceID=44, FAILED to access index 43
LogVerse: : DEBUG - DeviceID=44, Last item: ID=32, Name=Item32
Platform(s)
Windows (UEFN Editor)
Island Code
9881-8842-2718
Additional Notes
- The class containing the array (my_helper) is a plain Verse class, not a creative_device. Each creative_device instance creates its own copy via default construction (my_helper{}).
- The array was originally 32 elements. After adding 12 new elements to bring it to 44, most runtime instances still contain only the original 32 elements.
- At least one instance does see the correct 44 elements, confirming the source file compiles correctly.
- Full Verse rebuilds and UEFN restarts have not resolved the issue.
- In our production project, 46 creative_device instances reference this helper class across multiple streaming levels. The silent failure causes every newly added area (13 areas) to unlock for free with no cost check, since the failable lookups returned false and all requirement variables stayed at their default zero values.
- There is no compile-time warning, no runtime error, and no log output indicating data staleness. The only way to detect the problem is to explicitly log .Length and compare against the source code.