net.PushModelValidateProperties false-positive ensure on AbilitySystemComponent::RepAnimMontageInfo (vanilla Lyra repro)

Analysis: All engine writes to RepAnimMontageInfo go through GetRepAnimMontageInfo_Mutable(), which marks dirty — there is no missing mark.

FGameplayAbilityRepAnimMontage is a WithNetSerializer struct embedding FPredictionKey, whose serialization is connection-conditional by design (GameplayPrediction.cpp — ValidKeyForConnection only for the predicting connection / server-initiated keys). The shared shadow-compare path assumes connection-independent property state, so a live client-predicted key makes the validation comparison disagree with the dirty bookkeeping. Same class of false positive as the Role/RemoteRole case already worked around in FRepLayout::CompareParentProperties (force-dirty on rep-state recreation, with explanatory comment); FPredictionKey-bearing properties appear to need equivalent treatment or exemption. Suggest either force-marking on prediction-key change, or excluding connection-conditional NetSerialize parents from the validation ensure.

Impact: No replication misbehavior observed — issue is validation noise, but it makes

net.PushModelValidateProperties unusable for projects on GAS, since montage spam drowns real findings.

Analysis courthesy of Fable 5.

Repro Steps:

  1. Vanilla Lyra project, add to DefaultEngine.ini: [SystemSettings] net.IsPushModelEnabled=1, net.PushModelValidateProperties=1
  2. PIE, Net Mode = Play As Client, 1 client
  3. Activate any client-predicted ability that plays a montage (melee/emote/dash)

Observed:

Ensure condition failed: !bDidPropertyChange || bIsPropertyDirty [RepLayout.cpp:1567] — Push Model Property changed value, but was not marked dirty! Property=/Script/GameplayAbilities.AbilitySystemComponent:RepAnimMontageInfo

, repeating while montages play.

Reproduced on Launcher UE5.7.0 with vanilla Lyra.

Reproduced on our project which runs a custom version of UE5.7.1 based on Lyra.

Hi,

Thank you for the report!

After adding some custom logging, it seems as though it’s the SectionIdToPlay that’s not identical during the compare, causing the validation to fail:

LogRepCompares: VeryVerbose: CompareProperties: Owner: Class /Script/LyraGame.LyraAbilitySystemComponent CompareIndex: 42 HistoryIndex: 9
LogRepCompares: VeryVerbose: CompareParentProperties: Property validation
...
LogRepCompares: VeryVerbose: CompareProperties_r: CmdIndex: 31 CmdType: Property Property: RepAnimMontageInfo
LogTemp: Struct Property Animation Is Identical: 1
LogTemp: Struct Property SlotName Is Identical: 1
LogTemp: Struct Property PlayRate Is Identical: 1
LogTemp: Struct Property Position Is Identical: 1
LogTemp: Struct Property BlendTime Is Identical: 1
LogTemp: Struct Property BlendOutTime Is Identical: 1
LogTemp: Struct Property PlayCount Is Identical: 1
LogTemp: Struct Property NextSectionID Is Identical: 1
LogTemp: Struct Property PlayInstanceId Is Identical: 1
LogTemp: Struct Property bRepPosition Is Identical: 1
LogTemp: Struct Property IsStopped Is Identical: 1
LogTemp: Struct Property SkipPositionCorrection Is Identical: 1
LogTemp: Struct Property bSkipPlayRate Is Identical: 1
LogTemp: Struct Property PredictionKey Is Identical: 1
LogTemp: Struct Property SectionIdToPlay Is Identical: 0
LogRepCompares: VeryVerbose: StoreProperty: RepAnimMontageInfo
LogOutputDevice: Warning: Script Stack (0 frames) :
LogOutputDevice: Error: Ensure condition failed: !bDidPropertyChange || bIsPropertyDirty [File:D:\P4V\UE5-Main\Engine\Source\Runtime\Engine\Private\RepLayout.cpp] [Line: 1572] 
Push Model Property changed value, but was not marked dirty! Property=/Script/GameplayAbilities.AbilitySystemComponent:RepAnimMontageInfo

This could be due to FGameplayAbilityRepAnimMontage::NetSerialize setting this to 0 if bRepPosition is true. To look into it more, I’ve created a new issue, UE-384357, which should be visible in the public tracker in a day or so.

Thanks,

Alex