MouseTargeting.cs
using System;
using Sandbox;
using Sandbox.Citizen;

/// <summary>
/// Two-axis hand positioning (vertical/depth) with an elliptical reach
/// limit, plus PC interference noise that RoundManager drives.
/// </summary>
public sealed class MouseTargeting : Component
{
	[Property] public GameObject HandTarget { get; set; }
	[Property] public GameObject ChestAnchor { get; set; }

	[Property] public float VerticalMin { get; set; } = -30.0f;
	[Property] public float VerticalMax { get; set; } = 10.0f;
	[Property] public float DepthMin { get; set; } = -20.0f;
	[Property] public float DepthMax { get; set; } = 50.0f;

	[Property] public float Sensitivity { get; set; } = 0.1f;
	[Property] public float ShoulderHeightOffset { get; set; } = 55.0f;
	[Property] public float SideOffset { get; set; } = 0.0f;

	// Freeze is now driven by RoundManager (between-round reset), not a
	// keybind - set this from code, not the player.
	[Property] public bool FreezePosition { get; set; } = false;

	// PC interference, set by RoundManager each round.
	public float InterferenceIntensity { get; set; } = 0.0f;

	// STRENGTH = raw magnitude of the drift.
	[Property] public float NoiseStrength { get; set; } = 40.0f;

	// AGGRESSIVENESS = how often/fast the drift changes direction.
	// Low = slow, sweeping drift. High = twitchy, jittery drift.
	[Property] public float NoiseAggressiveness { get; set; } = 2.0f;

	private CitizenAnimationHelper _anim;
	private float _verticalOffset;
	private float _depthOffset;

	// Smoothed noise state
	private float _noiseV, _noiseD;
	private float _noiseTargetV, _noiseTargetD;
	private float _noiseTimer;

	protected override void OnStart()
	{
		_anim = Components.Get<CitizenAnimationHelper>();

		if ( _anim is null )
		{
			Log.Warning( "MouseTargeting: no CitizenAnimationHelper found on this GameObject." );
			return;
		}

		_anim.IsSitting = true;
		_anim.IkRightHand = HandTarget;

		ResetToHome();
	}

	// Called by RoundManager between rounds to snap back to a neutral pose.
	public void ResetToHome()
	{
		_verticalOffset = VerticalMin;
		_depthOffset = DepthMax;
		_noiseV = 0;
		_noiseD = 0;
	}

	protected override void OnUpdate()
	{
		if ( HandTarget is null || ChestAnchor is null || FreezePosition )
			return;

		_verticalOffset -= Input.MouseDelta.y * Sensitivity;
		_depthOffset += Input.MouseDelta.x * Sensitivity;

		if ( InterferenceIntensity > 0 )
		{
			_noiseTimer += Time.Delta;

			var retargetInterval = 1f / MathF.Max( 0.01f, NoiseAggressiveness );

			if ( _noiseTimer >= retargetInterval )
			{
				_noiseTimer = 0;
				_noiseTargetV = Game.Random.Float( -1f, 1f );
				_noiseTargetD = Game.Random.Float( -1f, 1f );
			}

			_noiseV = MathX.Lerp( _noiseV, _noiseTargetV, Time.Delta * NoiseAggressiveness );
			_noiseD = MathX.Lerp( _noiseD, _noiseTargetD, Time.Delta * NoiseAggressiveness );

			_verticalOffset += _noiseV * InterferenceIntensity * NoiseStrength * Time.Delta;
			_depthOffset += _noiseD * InterferenceIntensity * NoiseStrength * Time.Delta;
		}

		var centerV = (VerticalMin + VerticalMax) * 0.5f;
		var halfV = (VerticalMax - VerticalMin) * 0.5f;
		var centerD = (DepthMin + DepthMax) * 0.5f;
		var halfD = (DepthMax - DepthMin) * 0.5f;

		var normV = (_verticalOffset - centerV) / halfV;
		var normD = (_depthOffset - centerD) / halfD;
		var dist = MathF.Sqrt( normV * normV + normD * normD );

		if ( dist > 1.0f )
		{
			normV /= dist;
			normD /= dist;
		}

		_verticalOffset = centerV + normV * halfV;
		_depthOffset = centerD + normD * halfD;

		var rot = ChestAnchor.Transform.Rotation;
		var shoulderPos = ChestAnchor.Transform.Position + rot.Up * ShoulderHeightOffset;

		var worldPos = shoulderPos
			+ rot.Forward * _depthOffset
			+ rot.Up * _verticalOffset
			+ rot.Right * SideOffset;

		HandTarget.Transform.Position = worldPos;
	}
}