InitialOnly replicated arrays can fail to capture values when removed and re-added between replay checkpoints

We’re observing that an InitialOnly array can contain incorrect values for a replay checkpoint, particularly if:

  1. the array had values for the prior checkpoint
  2. some array entry was removed, with the same value being readded later
  3. some other property replicated between the value removal and addition

My current understanding is that the LifetimeChangelist will contain the correct array properties when first captured in a checkpoint, but those values can be pruned when the other property replicates while the array is smaller. The catch is that the shadow data still contains the data from the previous checkpoint (the comparison will be skipped as the property is InitialOnly), so if the same values are added back before the next checkpoint, then the comparison against the shadow data at that point in time will appear as though nothing has changed - and the properties won’t be added back to the LifetimeChangelist for checkpoint capture. Note that this can apply to individual properties on a struct as well - i.e. if a mostly different struct is added in place of another in an InitialOnly array, but one of the struct’s properties is the same as the struct removed before it.

Something we’ve tried to mitigate this behavior is to avoid updating the shadow data after comparison for InitialOnly properties - this means that *any* non-default data will appear changed and can be added to the changelist, but this regresses some behavior around the SharedSerialization for live game reconnects where a property that is changed back to default will not appear as changed (and so the SharedSerialization may persist a stale value). Some things we’ve considered from here are:

  • avoid updating the shadow data specifically for InitialOnly properties in replay connection
  • avoid updating the shadow data for InitialOnly properties and prospectively reset the SharedSerialization when an InitialOnly properties appear unchanged
  • prune changelists to the shadow data rather than the live object data

All of these options feel somewhat flawed, so we would appreciate any thoughts on if there’s a good way to resolve this in line with the intent for the system. I’d be happy to propose the fix for UE if we can get it clean enough, but it would be good to least understand if we can resolve this on our current version without digging too much of a hole.

Steps to Reproduce

  1. Create a replicated actor
  2. On that actor, have a replicated array with the InitialOnly condition and some other non-InitialOnly replicated property
  3. Begin replay capture
  4. Add a value to the replicated array during gameplay
  5. Capture a checkpoint after the property is changed
  6. Remove one or more values from the array
  7. Change the non-InitialOnly replicated property
  8. Add the removed values back to the array
  9. Capture another checkpoint
  10. Playback the replay and go to a time shortly after the 2nd checkpoint
    1. Observe that the values in the array are defaulted instead of having the correct value

The attached repro project attempts to perform these steps automatically and log the results. When the test checkpoint is 1, I do not observe the OnRep printout from a separate bug - for checkpoints 2 and 3, I see the replicated bools in the array as all false (even though 2 of the 3 were true during recording).

Hi,

Thank you for bringing this to our attention and for the repro project! I’ve opened a new issue for this, UE-347051, which should be visible in the public tracker in a day or so.

After discussing your proposed fixes with a colleague, it does seem as though the first approach, “avoid updating the shadow data specifically for InitialOnly properties in replay connection,” would make the most sense. It is worth noting that this issue might also occur for other replication conditions as well though, such as COND_Dynamic properties currently set to COND_Never (this will also skip comparisons in CompareParentProperty).

Thanks,

Alex