PlayerSprint.cs
using Sandbox;

public enum SprintInputBehavior
{
	Hold,
	Toggle,
	Hybrid
}

public sealed class PlayerSprint : Component
{
	[Property]
	public PlayerController Controller { get; set; }

	[Property]
	public SprintInputBehavior Behavior { get; set; } =
		SprintInputBehavior.Hybrid;

	[Property]
	public float HybridHoldThreshold { get; set; } = 0.22f;

	[Property]
	public float BaseMoveSpeed { get; set; } = 250f;

	[Property]
	public float SprintSpeed { get; set; } = 340f;

	[Property]
	public float ForwardInputThreshold { get; set; } = 0.25f;

	[Property]
	public float StopForwardInputThreshold { get; set; } = 0.05f;

	[Property]
	public bool RequireGrounded { get; set; } = true;

	[Property]
	public bool CancelToggleWhenForwardReleased { get; set; } = true;

	// =========================================================
	// INTENT
	// =========================================================

	/// <summary>
	/// The player currently wants to sprint.
	///
	/// This is INTENT, not physical locomotion state.
	///
	/// Jumping, sliding and crouching do not clear it.
	/// </summary>
	public bool WantsSprint { get; private set; }

	public bool IsSprintButtonHeld { get; private set; }

	public bool PersistentSprintToggle =>
		_persistentSprintToggle;

	// =========================================================
	// PHYSICAL RESULT
	// =========================================================

	/// <summary>
	/// The locomotion resolver sets this.
	///
	/// WantsSprint may be true while IsSprinting is false,
	/// for example:
	///
	/// - airborne
	/// - sliding
	/// - blocked under a ceiling
	/// </summary>
	public bool IsSprinting { get; private set; }

	// =========================================================
	// INTERNAL HYBRID STATE
	// =========================================================

	private bool _persistentSprintToggle;

	private bool _pressInProgress;

	private float _heldTime;

	private bool _holdCommitted;

	private bool _pressStartedWithToggle;

	protected override void OnStart()
	{
		Controller ??=
			Components.Get<PlayerController>();

		ResetSprint();

		Log.Info(
			$"PlayerSprint active | Proxy: {IsProxy} | Owner: {Network.OwnerId}"
		);
	}

	/// <summary>
	/// Called by PlayerLocomotion.
	///
	/// Nothing except raw player input changes sprint intent.
	/// </summary>
	public void UpdateIntent(
		bool buttonDown,
		bool buttonPressed,
		bool buttonReleased,
		float forwardInput
	)
	{
		if ( IsProxy )
			return;

		IsSprintButtonHeld =
			buttonDown;

		switch ( Behavior )
		{
			case SprintInputBehavior.Hold:
				{
					_persistentSprintToggle =
						false;

					break;
				}

			case SprintInputBehavior.Toggle:
				{
					if ( buttonPressed )
					{
						_persistentSprintToggle =
							!_persistentSprintToggle;
					}

					break;
				}

			case SprintInputBehavior.Hybrid:
				{
					UpdateHybridInput(
						buttonDown,
						buttonPressed,
						buttonReleased
					);

					break;
				}
		}

		//
		// Releasing forward movement is itself player input,
		// so it is allowed to clear a persistent sprint toggle.
		//
		if (
			CancelToggleWhenForwardReleased &&
			forwardInput <=
				StopForwardInputThreshold
		)
		{
			_persistentSprintToggle =
				false;
		}

		bool sprintInputActive =
			Behavior switch
			{
				SprintInputBehavior.Hold =>
					buttonDown,

				SprintInputBehavior.Toggle =>
					_persistentSprintToggle,

				SprintInputBehavior.Hybrid =>
					buttonDown ||
					_persistentSprintToggle,

				_ =>
					false
			};

		bool movingForward =
			forwardInput >=
			ForwardInputThreshold;

		WantsSprint =
			sprintInputActive &&
			movingForward;
	}

	private void UpdateHybridInput(
		bool buttonDown,
		bool buttonPressed,
		bool buttonReleased
	)
	{
		if ( buttonPressed )
		{
			_pressInProgress =
				true;

			_heldTime =
				0f;

			_holdCommitted =
				false;

			_pressStartedWithToggle =
				_persistentSprintToggle;
		}

		if (
			buttonDown &&
			_pressInProgress
		)
		{
			_heldTime +=
				Time.Delta;

			if (
				_heldTime >=
					HybridHoldThreshold
			)
			{
				_holdCommitted =
					true;
			}
		}

		if ( buttonReleased )
		{
			if (
				_pressInProgress &&
				!_holdCommitted
			)
			{
				//
				// Short tap toggles persistent sprint.
				//
				_persistentSprintToggle =
					!_pressStartedWithToggle;
			}

			//
			// Long holds do not modify the existing latch.
			//
			ResetPressTracking();
		}
	}

	public void SetPhysicalSprint(
		bool active
	)
	{
		IsSprinting =
			active;
	}

	/// <summary>
	/// Hard reset for menu / round resets.
	///
	/// This is deliberately NOT used by movement transitions.
	/// </summary>
	public void ResetSprint()
	{
		WantsSprint =
			false;

		IsSprinting =
			false;

		IsSprintButtonHeld =
			false;

		_persistentSprintToggle =
			false;

		ResetPressTracking();
	}

	private void ResetPressTracking()
	{
		_pressInProgress =
			false;

		_heldTime =
			0f;

		_holdCommitted =
			false;

		_pressStartedWithToggle =
			false;
	}

	// =========================================================
	// LEGACY COMPATIBILITY
	// =========================================================
	//
	// These remain temporarily so another old component does
	// not fail compilation while we migrate.
	//
	// They no longer alter player intent.
	//

	public void CancelForSlide()
	{
		IsSprinting =
			false;
	}

	public void CancelForCrouch()
	{
		IsSprinting =
			false;
	}

	public void RequestSprintAfterSlide()
	{
		//
		// Intentionally empty.
		//
		// There is nothing to request anymore.
		// WantsSprint already represents current intent.
		//
	}

	public void CancelSprintRequest()
	{
		ResetSprint();
	}

	public bool ResumeSprintAfterSlideRequested =>
		false;
}