Cook non-determinism in USplineComponent

There’s a new code added in 5.7 in USplineComponent::Serialize:

	if (Ar.IsSaving())
	{
		LastAuthority = UE::SplineComponent::ShouldUseSplineCurves() ? ELastAuthority::SplineCurves : ELastAuthority::Spline;
	}

This causes non-determinism in the results of the unversioned serialization: the value of LastAuthority for the object is always non-zero (depends on cvars etc), and depending on the order in which the object and it’s archetype are serialized, the value of LastAuthority on the archetype can be arbitrary (eg if archetype was serialized before, it will also be initialized to same value, and then object serialization will mark the property as having default value; otherwise archetype could still have Unset value, and then the object will be serialized as having non-default value).

In any case, modifying the object in Serialize (except when loading of course) is very strange and non-intuitive; eg this code path (Serialize with Ar.IsSaving == true) happens when calculating hashes etc - surely it’s not expected to modify anything there?..

There’s UObject::PreSave which looks like a better place, but this doesn’t work either (eg if the archetype is in another package - say, the component belongs to the instance of the blueprint placed on the level - there’s no guarantee whether level or blueprint will be cooked first).

[Attachment Removed]

Hi there,

Thanks for the report.

I’ve reviewed the code but wasn’t able to reproduce the issue you described.

In this case, LastAuthority is saved by Super::Serialize(Ar) after its value is assigned, and it is loaded by Super::Serialize(Ar) before being used during deserialization. Based on this execution flow, it’s unlikely that any non-deterministic behavior is involved.

If possible, we would appreciate additional details regarding your use case. Providing a minimal reproduction project or a clear set of reproduction steps would greatly assist us in further investigating the issue.

Best regards,

Henry Liu

[Attachment Removed]

The problem is not that value of LastAuthority is non-deterministic. The problem is that, when doing unversioned serialization, the value of LastAuthority of the *archetype* could be different (depending on whether save for archetype happened earlier or not). And unversioned serialization serializes property differently in these two cases (either skipping or writing it out explicitly).

I can not provide the minimal repro, since it depends on the order in which packages are cooked (which are undefined). But the idea is to have a blueprint and an instance of the blueprint, saved in older UE version before 5.7, and cook after 5.7.

[Attachment Removed]

FWIW the way I’ve fixed it locally is by removing the LastAuthority property, and instead serializing it manually in USplineComponent::Serialize.

[Attachment Removed]

Hi,

Thank you for the update.

As you described, I attempted to reproduce the issue on my end. I created multiple Blueprint actors with a Spline Component in both UE 5.6 and UE 5.7, then migrated them from 5.6 to 5.7. After opening the project in 5.7, I first saved the spline component created in 5.7, and then placed the one migrated from 5.6 into the level.

All serialization and deserialization logs show no issues, and the spline data behaves as expected. For spline components with no LastAuthority , the property is serialized using its default value.

Please let me know if this matches the test scenario you had in mind.

If possible, could you provide more detailed reproduction steps? That would greatly help us investigate the issue further.

Best regards,

Henry Liu

[Attachment Removed]

Reproduction steps is - cook the entire project, then cook it again in `-diffonly` mode and observe the differences. The differences are intermittent (i.e. if the given object and its archetype are cooked in same order in both runs, there would be no diffs, otherwise there would be a diff reported).

You can try hacking the high level cook code to hardcode the order two packages (blueprint and instance) are cooked in and then swapping them around for a diffonly run.

To clarify the problem once again - there is no problem with the *value* of LastAuthority, there’s a problem with serialized representation - it will either be skipped as matching default (if archetype is saved before instance), or otherwise will be serialized explicitly.

[Attachment Removed]

Thank you for the update and the additional explanation.

I tested packaging with the SplineComponent, and the process completed without any issues. All archetypes were loaded before their instances, and the archetypes were saved after the instances as expected. The value of LastAuthority also appears correct.

Based on the packaging logs:

[Image Removed]During loading (0):

BP_SplineActor created in 5.6 does not contain LastAuthority, and its value is 0.

BP_TestActor created in 5.7 includes LastAuthority, and its value is 2.

The blueprints are required to be loaded before their instances.

During saving (1):

All LastAuthority values on both blueprints and instances become 2.

The blueprints are required to be saved before their instances.

If the issue is not related to the value of LastAuthority, could you please clarify the specific problem you are encountering on your end?

Rather than hard-coding the cooking order, it would be helpful to understand the exact scenario in which the incorrect order occurs. If an instance can deserialize its data before the archetype is loaded, or serialize its data before the archetype is saved, that would indicate a potential bug. If you could provide clear repro steps or a minimal repro project demonstrating the incorrect cooking order would greatly assist us in investigating the issue further.

Looking forward to your response.

Cheers,

Henry Liu

[Attachment Removed]

> The blueprints are required to be saved before their instances.

That’s not what I observe. Here’s an example of a sequence of actions - baseline cook:

[2026.01.28-09.30.42:030][  0]LogCook: Display: Cooking /Game/Gameplay/Blueprints/Mechanics/BalanceBeam/BP_BalanceBeam_10M
[2026.01.28-10.07.26:728][  0]LogCook: Display: Cooking /Game/Levels/Overland/TestLevel/LI_TestLevel

And diff-only cook:

[2026.01.28-12.30.10:234][  0]LogCook: Display: Cooking /Game/Levels/Overland/TestLevel/LI_TestLevel
[2026.01.28-12.30.12:044][  0]LogDiff: Display: Comparing: D:/sun/ProjectName/Saved/Cooked/Windows/ProjectName/Content/Levels/Overland/TestLevel/LI_TestLevel.umap
[2026.01.28-12.30.12:045][  0]LogDiff: Warning: Asset class: World
[2026.01.28-12.30.12:160][  0]LogDiff: Warning: D:/sun/ProjectName/Saved/Cooked/Windows/ProjectName/Content/Levels/Overland/TestLevel/LI_TestLevel.umap: ExportMap is different (2634 Exports in source package vs 2634 Exports in dest package):
                                                -[1227] /script/engine/splinecomponent LI_TestLevel/PersistentLevel/BP_BalanceBeam_10M_C_UAID_10E7C6432E84438902_2056441966/BeamSpline Super: NULL, Template: /Game/Gameplay/Blueprints/Mechanics/BalanceBeam/BP_BalanceBeam_10M.default__bp_balancebeam_10m_c/beamspline, Flags: 262152, Size: 20, FilterFlags: 0
                                                +[1227] /script/engine/splinecomponent LI_TestLevel/PersistentLevel/BP_BalanceBeam_10M_C_UAID_10E7C6432E84438902_2056441966/BeamSpline Super: NULL, Template: /Game/Gameplay/Blueprints/Mechanics/BalanceBeam/BP_BalanceBeam_10M.default__bp_balancebeam_10m_c/beamspline, Flags: 262152, Size: 23, FilterFlags: 0
[2026.01.28-12.34.12:425][  0]LogCook: Display: Cooking /Game/Gameplay/Blueprints/Mechanics/BalanceBeam/BP_BalanceBeam_10M

In the baseline run, when the umap was cooked and the BP instance actor serialized, it would set LastAuthority to 2; the archetype’s (/Game/Gameplay/Blueprints/Mechanics/BalanceBeam/BP_BalanceBeam_10M.default__bp_balancebeam_10m_c/beamspline) last-authority was set to 2 when it was serialized earlier - so as a result, unversioned serialization path would consider LastAuthority as ‘matching archetype’.

In diff-only run, the order was different - when umap was cooked, the BP instance would have LastAuthority set to 2, but the archetype still had 0 (serialize with IsSaving not happened yet) - so the unversioned serialization path would write out this 2 explicitly. The serialized object is 3 bytes bigger, because of an extra word in header serialiation + an extra byte for actual value.

[Attachment Removed]

Sorry I can not provide a self contained repro scenario - this is all reproduced locally as I’m trying to get rid of the last sources of non-determinism when cooking the large game project (few hundreds of thousands of packages in a cook). We have tons of instances of spline components, and which one exhibits the diff in a given run is basically random.

For now, what I did to remove this source of non-determinism is this change at the very beginnging of USplineComponent::Serialize:

ELastAuthority LastAuthority = Ar.IsLoading() ? ELastAuthority::Unset : UE::SplineComponent::ShouldUseSplineCurves() ? ELastAuthority::SplineCurves : ELastAuthority::Spline;
	Ar.UsingCustomVersion(FCustomDevEngineObjectVersion::GUID);
	if (Ar.CustomVer(FCustomDevEngineObjectVersion::GUID) >= FCustomDevEngineObjectVersion::SplineComponentLastAuthorityNondeterminism)
	{
		Ar << LastAuthority;
	}

… and removing the LastAuthority property completely.

[Attachment Removed]

By the way, stumbled across _another_ source of non-determinism in USplineComponent (also seems to be a regression in 5.7). USplineComponent::GetUsedMaterials adds the debug materials LineMaterial/PointMaterial into the output depending whether relevant soft-object-ptrs are loaded. As a result, UPrimitiveComponent::PreSave can initialize bHasNoStreamableTextures flag differently (to false if by the time PreSave happens none of the debug materials have been loaded, otherwise likely to true if they don’t contain streamable textures).

[Attachment Removed]

Hi there,

Thank you for the detailed information. It’s very helpful.

I have submitted a new bug report for this issue, and once it is reflected publicly, you will be able to view it here:

https://issues.unrealengine.com/issue/UE-367486

An engine developer will review the issue at a later date. While we’re unable to provide direct updates on EPS, you can monitor any progress through the public issue tracker linked above. I will go ahead and close this case now, but feel free to respond here if you have any follow up questions.

Thanks,

Henry Liu

[Attachment Removed]