How to get rid of desync from crouching (sync timeline in C++)?

Posting here as well as on the Unreal Engine subreddit (not sure which one is more popular)? So I am creating my own Character and CharacterMovementComponent Classes based off this wonderful video, and wanted to add in smooth crouching, so I override Crouch and UnCrouch with my own implementation that calls Play and Reverse on a Timeline (in C++) that then calls a custom CrouchUpdate event (in BP) which handles lerping the half height and z offset.

The issue I’m running into is that while it looks great, it isn’t synced, even with no latency or packet loss. And then when I turn on 1000 ping and 15% packet loss, movement is slightly off, but when I crouch it goes wild, flip flopping between crouched and uncrouched and getting stuck in the wrong mode.

How do I sync the timeline with no latency or packet loss? And how do I protect against packet loss?

Is it possible that I need include some client prediction code?

Relevant Code (please ask for more if necessary):

void UFPSCharacterMovementComponent::Crouch(bool bClientSimulation)
{
	if (!HasValidData())
	{
		return;
	}

	if (!bClientSimulation && !CanCrouchInCurrentState())
	{
		return;
	}

	if (CharacterOwner->GetCapsuleComponent()->GetUnscaledCapsuleHalfHeight() == CrouchedHalfHeight)
	{
		if (!bClientSimulation)
		{
			CharacterOwner->bIsCrouched = true;
		}
		return;
	}
        
        // Not sure if necessary
	//if (bClientSimulation && CharacterOwner->GetLocalRole() == ROLE_SimulatedProxy)
	//{
	//	// restore collision size before crouching
	//	ACharacter* DefaultCharacter = CharacterOwner->GetClass()->GetDefaultObject<ACharacter>();
	//	CharacterOwner->GetCapsuleComponent()->SetCapsuleSize(DefaultCharacter->GetCapsuleComponent()->GetUnscaledCapsuleRadius(), DefaultCharacter->GetCapsuleComponent()->GetUnscaledCapsuleHalfHeight());
	//}

	if (!bClientSimulation)
	{
		CharacterOwner->bIsCrouched = true;
	}

	bForceNextFloorCheck = true;

	CrouchTimelineComp->Play();

}

void UFPSCharacterMovementComponent::UnCrouch(bool bClientSimulation)
{
	if (!HasValidData())
	{
		return;
	}

	ACharacter* DefaultCharacter = CharacterOwner->GetClass()->GetDefaultObject<ACharacter>();

	if (CharacterOwner->GetCapsuleComponent()->GetUnscaledCapsuleHalfHeight() == DefaultCharacter->GetCapsuleComponent()->GetUnscaledCapsuleHalfHeight())
	{
		if (!bClientSimulation)
		{
			CharacterOwner->bIsCrouched = false;
		}
		return;
	}

	if (!bClientSimulation)
	{
		// Test for uncrouch

		CharacterOwner->bIsCrouched = false;
	}

	CrouchTimelineComp->Reverse();

}

Changed it so the capsule is not smoothed, but the camera is smoothed. Essentially, just called the Super implementation and added a call to BlueprintImplementable event that smoothed the camera position. Probably will move that node to be C++ as I reimplement everything with GAS.