OMRScopePiPCamera.cs
using Sandbox;
using Sandbox.Rendering;
using System;
using System.Linq;

/// <summary>
/// Local-only picture-in-picture scope camera.
///
/// Supports both the screen-space debug/fallback lens and the final model-space
/// lens path. In model-space mode the secondary camera may take its position from
/// an authored helper inside the active viewmodel while keeping rotation tied to
/// the authoritative gameplay aim. This gives us a physically located scope
/// camera without allowing cosmetic viewmodel sway to make the reticle lie.
/// </summary>
[Title( "OMR Scope PiP Camera" )]
public sealed class OMRScopePiPCamera : Component
{
	private const string DefaultViewModelTag = "viewmodel";

	[Property, Group( "References" )]
	public CameraComponent SourceCamera { get; set; }

	[Property, Group( "Filtering" )]
	public string ViewModelTag { get; set; } = DefaultViewModelTag;

	[Property, Group( "Filtering" )]
	[Description( "Tag used by the local first-person body. Excluded so the scope does not render viewer-only local geometry." )]
	public string ViewerTag { get; set; } = "viewer";

	[Property, Group( "Rendering" ), Range( 0, 8 )]
	[Description( "Render-order offset relative to the gameplay camera. Clamped to S&box's supported camera priority range." )]
	public int PriorityOffset { get; set; } = 2;

	[Property, Group( "Diagnostics" )]
	public bool LogSetup { get; set; } = false;

	[Property, Group( "Diagnostics" )]
	public bool LogStateChanges { get; set; } = false;

	[Property, Group( "Diagnostics" )]
	public bool LogMissingAnchor { get; set; } = false;

	public CameraComponent ScopeCamera => _scopeCamera;
	public Texture OutputTexture => _outputTexture;
	public int OutputResolution => _outputResolution;
	/// <summary>Optical target FOV implied by the optic profile.</summary>
	public float CurrentScopeFieldOfView { get; private set; }

	/// <summary>FOV actually submitted to RenderToTexture this frame.</summary>
	public float CurrentRenderFieldOfView { get; private set; }

	/// <summary>Gameplay FOV before PiP peripheral presentation scaling, used as the fixed optical reference.</summary>
	public float CurrentOpticalReferenceFieldOfView { get; private set; }

	/// <summary>FOV of the camera that actually renders the physical viewmodel/lens mesh.</summary>
	public float CurrentLensProjectionFieldOfView { get; private set; }

	/// <summary>Requested/effective angular magnification of the optic.</summary>
	public float CurrentScopeMagnification { get; private set; } = 1f;

	/// <summary>
	/// Legacy presentation scale. Phase 5E15 performs deterministic magnification
	/// inside RenderToTexture via ViewSetup.ClipSpaceBounds, so model-lens optics
	/// normally leave this at 1.
	/// </summary>
	public float PresentationZoomScale { get; private set; } = 1f;

	/// <summary>Centered clip-space sub-rectangle submitted to the manual scope render.</summary>
	public Vector4? CurrentClipSpaceBounds { get; private set; }

	/// <summary>World-space separation between the gameplay eye and the final PiP render-camera position.</summary>
	public float CurrentEyeToScopeCameraDistance { get; private set; }

	/// <summary>World-space separation between the gameplay eye and the physical authored optic anchor before any ADS camera-position blend.</summary>
	public float CurrentEyeToPhysicalOpticDistance { get; private set; }

	/// <summary>0 = PiP camera remains at the physical optic anchor, 1 = PiP camera has blended to the gameplay eye.</summary>
	public float CurrentPiPPositionBlend { get; private set; }

	/// <summary>Angular eye-to-optic-axis error in degrees, measured from the gameplay view basis.</summary>
	public float CurrentEyeBoxAngularError { get; private set; }

	/// <summary>1 = eye centered in the optic's authored eye box, 0 = at or beyond the configured full-shadow angle.</summary>
	public float CurrentEyeBoxAlignment { get; private set; } = 1f;

	/// <summary>Normalized right/up displacement of the optic relative to the eye at the configured full-shadow angle.</summary>
	public Vector2 CurrentEyeBoxOffset { get; private set; } = Vector2.Zero;

	/// <summary>Estimated inner-lens diameter as a fraction of screen width for the current ADS pose/FOV.</summary>
	public float CurrentLensScreenFraction { get; private set; } = 1f;

	/// <summary>True when CurrentLensScreenFraction came from live model-space geometry rather than the authored fallback values.</summary>
	public bool IsLensScreenFractionMeasured { get; private set; }

	/// <summary>Identifies which geometry source produced the current automatic lens-size measurement.</summary>
	public string CurrentLensMeasurementSource { get; private set; } = "AuthoredFallback";

	/// <summary>Unsmoothed raw screen fraction from the current model-space lens projection. Useful for diagnostics/tuning.</summary>
	public float CurrentRawMeasuredLensScreenFraction { get; private set; }

	/// <summary>Magnification we want the player to perceive inside the on-screen lens.</summary>
	public float CurrentTargetApparentMagnification { get; private set; } = 1f;

	/// <summary>Clip-space magnification actually submitted to RenderToTexture.</summary>
	public float CurrentRenderCropMagnification { get; private set; } = 1f;

	/// <summary>Expected visible magnification after accounting for the lens's on-screen size.</summary>
	public float CurrentExpectedApparentMagnification { get; private set; } = 1f;

	public bool LastManualRenderSucceeded { get; private set; }
	public GameObject ResolvedCameraAnchor => _resolvedCameraAnchor;

	public bool IsRendering =>
		_renderRequested &&
		_scopeCamera is not null &&
		_scopeCamera.IsValid &&
		_outputTexture is not null &&
		_outputTexture.IsValid;

	public BaseInventoryComponent LocalInventory => ResolveLocalInventory();
	public OneMoreRoundWeapon ActiveWeapon => LocalInventory?.ActiveItem as OneMoreRoundWeapon;

	public IWeaponOpticState ActiveOpticState =>
		LocalInventory?.ActiveItem as IWeaponOpticState;

	public OMROpticProfile ActiveProfile =>
		ActiveOpticState?.ActiveOpticProfile;

	private GameObject _scopeCameraObject;
	private CameraComponent _scopeCamera;
	private Texture _outputTexture;
	private int _outputResolution;

	private PlayerState _localPlayer;
	private BaseInventoryComponent _localInventory;

	private bool _renderRequested;
	private bool _lastRenderingState;
	private float _nextHipfirePreviewAt;

	private GameObject _anchorViewModel;
	private string _anchorName;
	private GameObject _resolvedCameraAnchor;
	private bool _loggedMissingAnchor;

	private GameObject _lensMeasureViewModel;
	private string _lensMeasureObjectName;
	private GameObject _resolvedLensMeasureObject;
	private ModelRenderer _resolvedLensImageRenderer;
	private Sandbox.WorldPanel _resolvedLensWorldPanel;
	private float _smoothedMeasuredLensScreenFraction = -1f;

	protected override void OnStart()
	{
		ResolveSourceCamera();

		if ( SourceCamera is null || !SourceCamera.IsValid )
		{
			Log.Warning( "[OMR SCOPE PIP] No valid source CameraComponent was found." );
			Enabled = false;
			return;
		}

		CreateScopeCamera();
		SetRenderingState( false );

		if ( LogSetup )
		{
			Log.Info(
				$"[OMR SCOPE PIP] Ready | Source:{SourceCamera.GameObject.Name} | " +
				$"WorldPriority:{SourceCamera.Priority}"
			);
		}
	}

	protected override void OnUpdate()
	{
		ResolveSourceCamera();

		if ( SourceCamera is null || !SourceCamera.IsValid )
		{
			SetRenderingState( false );
			return;
		}

		if ( _scopeCamera is null || !_scopeCamera.IsValid )
			CreateScopeCamera();

		if ( _scopeCamera is null || !_scopeCamera.IsValid )
		{
			SetRenderingState( false );
			return;
		}

		IWeaponOpticState optic = ActiveOpticState;
		OMROpticProfile profile = optic?.ActiveOpticProfile;

		bool usesPiP =
			profile is not null &&
			(
				profile.PresentationMode == OMROpticPresentationMode.PictureInPicture ||
				profile.PresentationMode == OMROpticPresentationMode.ModelLensPictureInPicture
			);

		bool shouldRender =
			optic is not null &&
			profile is not null &&
			profile.IsValid &&
			profile.Mode == OMROpticMode.MagnifiedScope &&
			usesPiP &&
			(
				profile.KeepPiPWarmWhileEquipped ||
				optic.IsScopePresentationActive
			);

		if ( shouldRender )
		{
			EnsureRenderTarget( profile );
			ConfigureScopeCameraStatic( profile );
		}
		else
		{
			ClearResolvedAnchor();
		}

		SetRenderingState(
			shouldRender &&
			_outputTexture is not null &&
			_outputTexture.IsValid
		);
	}

	/// <summary>
	/// CameraComponent.View is finalized after Update and before PreRender. This
	/// lets us mirror the fully composed gameplay aim and projection here.
	///
	/// For a physical model-space scope we can replace only the camera position
	/// with an authored viewmodel anchor. Rotation remains gameplay-authoritative
	/// by default so cosmetic gun sway cannot desynchronize the reticle from the
	/// actual shot direction.
	/// </summary>
	protected override void OnPreRender()
	{
		if ( !IsRendering )
			return;

		IWeaponOpticState optic = ActiveOpticState;
		OMROpticProfile profile = optic?.ActiveOpticProfile;

		if (
			optic is null ||
			profile is null ||
			!profile.IsValid ||
			SourceCamera is null ||
			!SourceCamera.IsValid ||
			_scopeCamera is null ||
			!_scopeCamera.IsValid ||
			_scopeCameraObject is null ||
			!_scopeCameraObject.IsValid
		)
		{
			return;
		}

		int previewRate = Math.Clamp( OMRClientPerformance.ScopePreviewFrameRate, 0, 240 );
		bool hipfireOnly = !optic.IsAiming && optic.AimFraction <= 0.001f;
		if ( hipfireOnly && previewRate > 0 && LastManualRenderSucceeded && RealTime.Now < _nextHipfirePreviewAt ) return;
		_nextHipfirePreviewAt = hipfireOnly && previewRate > 0 ? RealTime.Now + 1f / previewRate : 0f;
		CameraView source = SourceCamera.View;

		Vector3 sourcePosition = source.FieldOfView > 1f
			? source.Position
			: SourceCamera.WorldPosition;

		Rotation sourceRotation = source.FieldOfView > 1f
			? source.Rotation
			: SourceCamera.WorldRotation;

		float sourceFov = source.FieldOfView > 1f
			? source.FieldOfView
			: SourceCamera.FieldOfView;

		// PeripheralAimFovScale is a presentation-only zoom on the ordinary world
		// camera. Recover the pre-peripheral FOV before deriving the physical optic
		// projection so a "4x" scope remains authored as the same 4x optic even if
		// the surrounding world receives a subtle focus zoom.
		float peripheralScale = MathX.Clamp(
			optic.CurrentPeripheralAimFovScale,
			0.5f,
			1.5f
		);

		float opticalReferenceFov = MathX.Clamp(
			sourceFov / MathF.Max( peripheralScale, 0.0001f ),
			1f,
			179f
		);

		OMRViewModelCamera viewModelCamera =
			SourceCamera.GameObject.Components.Get<OMRViewModelCamera>();

		float lensProjectionFov =
			viewModelCamera is not null &&
			viewModelCamera.IsValid
				? viewModelCamera.ResolveViewModelFieldOfView( sourceFov, optic )
				: sourceFov;

		CurrentOpticalReferenceFieldOfView = opticalReferenceFov;
		CurrentLensProjectionFieldOfView = lensProjectionFov;

		float sourceZNear = source.ZNear > 0f
			? source.ZNear
			: SourceCamera.ZNear;

		float sourceZFar = source.ZFar > sourceZNear
			? source.ZFar
			: SourceCamera.ZFar;

		ResolveCameraPose(
			profile,
			optic.AimFraction,
			sourcePosition,
			sourceRotation,
			out Vector3 scopePosition,
			out Rotation scopeRotation,
			out Vector3 physicalOpticPosition
		);

		UpdateEyeBoxState(
			profile,
			sourcePosition,
			sourceRotation,
			physicalOpticPosition
		);

		// Keep the proven Phase 5E8 / Facepunch CameraWeapon lifecycle: the
		// render-target camera receives its final raw transform and projection in
		// PreRender, after the gameplay camera has composed and after the M700's
		// viewmodel anchors have been published for this frame. CameraComponent's
		// current contract explicitly allows a camera changed after composition to
		// render from that raw state. This avoids letting unrelated camera modifiers
		// participate in the off-screen optic camera.
		CurrentScopeFieldOfView = OMROpticMath.ResolveRenderedScopeFieldOfView(
			opticalReferenceFov,
			profile,
			optic.AimFraction
		);

		CurrentScopeMagnification = OMROpticMath.CalculateAngularMagnification(
			opticalReferenceFov,
			CurrentScopeFieldOfView
		);

		// Phase 5E14 proved that scaling the custom Painter-backed panel is not a
		// reliable way to magnify the live RT. S&box exposes ClipSpaceBounds on
		// ViewSetup specifically to render a smaller centered sub-rectangle of the
		// frustum (i.e. zoom into clip space). Do the crop inside RenderToTexture so
		// the physical lens consumer receives pixels that are already magnified.
		bool renderClipCrop = profile.UsePresentationCropForMagnification;
		CurrentRenderFieldOfView = renderClipCrop
			? opticalReferenceFov
			: CurrentScopeFieldOfView;

		PresentationZoomScale = 1f;
		CurrentClipSpaceBounds = null;
		CurrentLensScreenFraction = 1f;
		IsLensScreenFractionMeasured = false;
		CurrentLensMeasurementSource = "AuthoredFallback";
		CurrentRawMeasuredLensScreenFraction = 0f;
		CurrentTargetApparentMagnification = CurrentScopeMagnification;
		CurrentRenderCropMagnification = CurrentScopeMagnification;
		CurrentExpectedApparentMagnification = CurrentScopeMagnification;

		if ( renderClipCrop )
		{
			float cropMagnification = MathF.Max( CurrentScopeMagnification, 1f );

			if ( profile.CompensateForModelLensScreenSize )
			{
				float apparentBlend = ResolveApparentMagnificationBlend(
					optic.AimFraction,
					profile.ApparentMagnificationBlendStart,
					profile.ApparentMagnificationBlendFull
				);

				float currentHorizontalFov = ResolveHorizontalFieldOfView(
					lensProjectionFov,
					SourceCamera.FovAxis
				);

				// Always calculate the authored fallback first. Besides giving us a
				// safe fallback, it provides a sanity envelope for live measurement so
				// a malformed panel transform cannot suddenly turn a working 4x optic
				// into a 40x microscope.
				float referenceLensFraction = MathX.Lerp(
					MathX.Clamp( profile.HipLensScreenFraction, 0.02f, 0.8f ),
					MathX.Clamp( profile.AdsLensScreenFraction, 0.02f, 0.8f ),
					apparentBlend
				);

				float referenceHorizontalFov = MathX.Clamp(
					profile.LensFractionReferenceFov,
					30f,
					140f
				);

				float referenceTan = MathF.Tan( referenceHorizontalFov * (MathF.PI / 360f) );
				float currentTan = MathF.Tan( currentHorizontalFov * (MathF.PI / 360f) );
				float fovSizeScale = MathF.Abs( currentTan ) > 0.00001f
					? referenceTan / currentTan
					: 1f;

				float authoredCurrentLensFraction = MathX.Clamp(
					referenceLensFraction * fovSizeScale,
					0.02f,
					0.95f
				);

				float measuredFraction = 0f;
				bool measuredLensFraction =
					profile.AutoMeasureModelLensScreenFraction &&
					TryMeasureModelLensScreenFraction(
						profile,
						sourcePosition,
						sourceRotation,
						currentHorizontalFov,
						out measuredFraction
					);

				// The live value should stay in the same rough order of magnitude as
				// the authored fallback. This guard makes the feature fail-safe while
				// still allowing large intentional lens-size edits in the prefab.
				if ( measuredLensFraction )
				{
					float minPlausible = authoredCurrentLensFraction * 0.4f;
					float maxPlausible = authoredCurrentLensFraction * 2.5f;
					measuredLensFraction =
						measuredFraction >= minPlausible &&
						measuredFraction <= maxPlausible;
				}

				if ( measuredLensFraction )
				{
					CurrentRawMeasuredLensScreenFraction = measuredFraction;
					IsLensScreenFractionMeasured = true;

					float targetMeasured = MathX.Clamp(
						measuredFraction * MathX.Clamp( profile.AutoMeasuredLensFractionScale, 0.5f, 1.5f ),
						0.02f,
						0.95f
					);

					float smoothingSpeed = MathX.Clamp(
						profile.AutoMeasuredLensFractionSmoothingSpeed,
						0f,
						40f
					);

					if ( _smoothedMeasuredLensScreenFraction < 0f || smoothingSpeed <= 0f )
					{
						_smoothedMeasuredLensScreenFraction = targetMeasured;
					}
					else
					{
						float alpha = 1f - MathF.Exp( -smoothingSpeed * MathF.Max( Time.Delta, 0f ) );
						_smoothedMeasuredLensScreenFraction = MathX.Lerp(
							_smoothedMeasuredLensScreenFraction,
							targetMeasured,
							MathX.Clamp( alpha, 0f, 1f )
						);
					}

					CurrentLensScreenFraction = MathX.Clamp(
						_smoothedMeasuredLensScreenFraction,
						0.02f,
						0.95f
					);
				}
				else
				{
					CurrentLensMeasurementSource = "AuthoredFallback";
					CurrentLensScreenFraction = authoredCurrentLensFraction;
				}

				CurrentTargetApparentMagnification = MathX.Lerp(
					MathF.Max( profile.HipApparentMagnification, 0.5f ),
					MathF.Max( profile.NominalMagnification, 1f ),
					apparentBlend
				);

				// A PiP image that occupies only a fraction F of the monitor needs an
				// additional 1/F projection crop to produce the requested apparent
				// magnification on the player's screen. Example: a lens that is 20%
				// of screen width needs about a 20x render crop to look 4x relative
				// to the surrounding 1x world: 0.20 * 20 = 4.
				cropMagnification = CurrentTargetApparentMagnification /
					MathF.Max( CurrentLensScreenFraction, 0.02f );

				CurrentExpectedApparentMagnification =
					CurrentLensScreenFraction * cropMagnification;
			}

			CurrentRenderCropMagnification = MathX.Clamp( cropMagnification, 1f, 64f );

			if ( profile.CompensateForModelLensScreenSize )
			{
				CurrentExpectedApparentMagnification =
					CurrentLensScreenFraction * CurrentRenderCropMagnification;
			}

			if ( CurrentRenderCropMagnification > 1.0001f )
			{
				CurrentClipSpaceBounds = BuildCenteredClipSpaceBounds(
					CurrentRenderCropMagnification
				);
			}
		}

		CurrentEyeToScopeCameraDistance = Vector3.DistanceBetween(
			sourcePosition,
			scopePosition
		);

		CurrentEyeToPhysicalOpticDistance = Vector3.DistanceBetween(
			sourcePosition,
			physicalOpticPosition
		);

		// Keep the component's raw values in sync for diagnostics, but do not let the
		// scene scheduler render this camera automatically. The actual optic render
		// below uses CameraComponent.RenderToTexture with an explicit ViewSetup.
		_scopeCameraObject.WorldPosition = scopePosition;
		_scopeCameraObject.WorldRotation = scopeRotation;
		_scopeCamera.FieldOfView = CurrentRenderFieldOfView;
		_scopeCamera.ZNear = sourceZNear;
		_scopeCamera.ZFar = sourceZFar;
		_scopeCamera.FovAxis = SourceCamera.FovAxis;
		_scopeCamera.BackgroundColor = SourceCamera.BackgroundColor;

		Transform renderTransform = new Transform( scopePosition )
			.WithRotation( scopeRotation );

		ViewSetup renderSetup = new()
		{
			Transform = renderTransform,
			FieldOfView = CurrentRenderFieldOfView,
			ZNear = sourceZNear,
			ZFar = sourceZFar,
			EnablePostprocessing = profile.PiPEnablePostProcessing,
			ClipSpaceBounds = CurrentClipSpaceBounds,
			FlipX = false,
			FlipY = false
		};

		LastManualRenderSucceeded = _scopeCamera.RenderToTexture(
			_outputTexture,
			renderSetup
		);
	}

	/// <summary>
	/// Builds a centered ViewSetup clip-space crop for the requested optical
	/// magnification. S&box stores ClipSpaceBounds as
	/// (left, down/bottom, right, up/top). The engine's full-frustum default is
	/// (-1, -1, +1, +1). Keeping min values below max values is essential; a
	/// vertically reversed rectangle can leave the RT showing only its clear
	/// color, which is exactly what the E15 regression looked like.
	/// </summary>
	private static Vector4 BuildCenteredClipSpaceBounds( float magnification )
	{
		float safeMagnification = MathX.Clamp( magnification, 1f, 64f );
		float halfExtent = 1f / safeMagnification;

		return new Vector4(
			-halfExtent, // left
			-halfExtent, // down / bottom
			 halfExtent, // right
			 halfExtent  // up / top
		);
	}

	private static float ResolveApparentMagnificationBlend(
		float aimFraction,
		float startFraction,
		float fullFraction
	)
	{
		float start = MathX.Clamp( startFraction, 0f, 1f );
		float full = MathX.Clamp( fullFraction, start, 1f );
		float aim = MathX.Clamp( aimFraction, 0f, 1f );

		if ( full <= start + 0.0001f )
			return aim >= full ? 1f : 0f;

		float t = MathX.Clamp( (aim - start) / (full - start), 0f, 1f );
		return t * t * (3f - 2f * t);
	}

	private static float ResolveHorizontalFieldOfView(
		float authoredFov,
		CameraComponent.Axis axis
	)
	{
		float fov = MathX.Clamp( authoredFov, 1f, 179f );

		if ( axis != CameraComponent.Axis.Vertical )
			return fov;

		return Screen.CreateVerticalFieldOfView(
			fov,
			MathF.Max( Screen.Aspect, 0.01f )
		);
	}

	protected override void OnDisabled()
	{
		SetRenderingState( false );
		ClearResolvedAnchor();
		DestroyScopeCamera();
		DisposeRenderTarget();
		base.OnDisabled();
	}

	protected override void OnDestroy()
	{
		SetRenderingState( false );
		ClearResolvedAnchor();
		DestroyScopeCamera();
		DisposeRenderTarget();
		base.OnDestroy();
	}

	private void ResolveSourceCamera()
	{
		if ( SourceCamera is not null && SourceCamera.IsValid )
			return;

		SourceCamera = Components.Get<CameraComponent>();
		SourceCamera ??= Scene?.Camera;
	}

	private BaseInventoryComponent ResolveLocalInventory()
	{
		if (
			_localPlayer is not null &&
			_localPlayer.IsValid &&
			!_localPlayer.IsProxy &&
			!_localPlayer.IsBot &&
			_localInventory is not null &&
			_localInventory.IsValid
		)
		{
			return _localInventory;
		}

		_localPlayer = Scene?
			.GetAllComponents<PlayerState>()
			.FirstOrDefault(
				x =>
					x is not null &&
					x.IsValid &&
					!x.IsProxy &&
				!x.IsBot
			);

		_localInventory = _localPlayer?
			.Components
			.Get<BaseInventoryComponent>();

		return _localInventory;
	}

	private void CreateScopeCamera()
	{
		if ( _scopeCamera is not null && _scopeCamera.IsValid )
			return;

		if (
			Scene is null ||
			SourceCamera is null ||
			!SourceCamera.IsValid
		)
		{
			return;
		}

		_scopeCameraObject = Scene.CreateObject( true );
		_scopeCameraObject.Name = "OMR Scope PiP Camera";
		_scopeCameraObject.NetworkMode = NetworkMode.Never;
		_scopeCameraObject.Flags |= GameObjectFlags.NotSaved;

		_scopeCamera = _scopeCameraObject.Components.Create<CameraComponent>();
		_scopeCamera.IsMainCamera = false;
		_scopeCamera.Orthographic = false;
		_scopeCamera.BackgroundColor = SourceCamera.BackgroundColor;
		_scopeCamera.ClearFlags = ClearFlags.Color | ClearFlags.Depth | ClearFlags.Stencil;
		_scopeCamera.Enabled = false;
	}

	private void ConfigureScopeCameraStatic( OMROpticProfile profile )
	{
		if (
			_scopeCamera is null ||
			!_scopeCamera.IsValid ||
			SourceCamera is null ||
			!SourceCamera.IsValid
		)
		{
			return;
		}

		_scopeCamera.IsMainCamera = false;
		_scopeCamera.Orthographic = false;
		_scopeCamera.ClearFlags = ClearFlags.Color | ClearFlags.Depth | ClearFlags.Stencil;
		_scopeCamera.BackgroundColor = SourceCamera.BackgroundColor;
		_scopeCamera.EnablePostProcessing = profile.PiPEnablePostProcessing;
		_scopeCamera.FovAxis = SourceCamera.FovAxis;
		_scopeCamera.TargetEye = SourceCamera.TargetEye;
		_scopeCamera.ZNear = SourceCamera.ZNear;
		_scopeCamera.ZFar = SourceCamera.ZFar;
		_scopeCamera.Priority = Math.Clamp(
			SourceCamera.Priority + PriorityOffset,
			1,
			16
		);
		_scopeCamera.Viewport = new Vector4( 0f, 0f, 1f, 1f );
		_scopeCamera.CustomSize = new Vector2( _outputResolution, _outputResolution );
		_scopeCamera.RenderTarget = null;
		_scopeCamera.Enabled = false;

		AddExcludeTag( ViewModelTag );
		AddExcludeTag( ViewerTag );
	}

	private void AddExcludeTag( string tag )
	{
		if (
			_scopeCamera is null ||
			!_scopeCamera.IsValid ||
			string.IsNullOrWhiteSpace( tag ) ||
			_scopeCamera.RenderExcludeTags.Has( tag )
		)
		{
			return;
		}

		_scopeCamera.RenderExcludeTags.Add( tag );
	}

	private void EnsureRenderTarget( OMROpticProfile profile )
	{
		int resolution = ResolveDesiredResolution( profile );

		if (
			_outputTexture is not null &&
			_outputTexture.IsValid &&
			_outputResolution == resolution
		)
		{
			return;
		}

		DisposeRenderTarget();

		_outputTexture = Texture.CreateRenderTarget()
			.WithSize( resolution, resolution )
			.WithFormat( ImageFormat.RGBA8888 )
			.Create();

		_outputResolution = resolution;
		_nextHipfirePreviewAt = 0f;
		LastManualRenderSucceeded = false;

		if ( _scopeCamera is not null && _scopeCamera.IsValid )
		{
			_scopeCamera.RenderTarget = null;
			_scopeCamera.CustomSize = new Vector2( resolution, resolution );
		}
	}

	private int ResolveDesiredResolution( OMROpticProfile profile )
	{
		IWeaponOpticState optic = ActiveOpticState;

		if (
			profile.KeepPiPWarmWhileEquipped &&
			optic is not null &&
			optic.AimFraction < MathX.Clamp( profile.PiPFullResolutionAimFraction, 0f, 1f )
		)
		{
			return Math.Clamp(
				profile.PiPPrewarmResolution,
				128,
				1024
			);
		}

		if ( profile.PiPResolutionMode == OMRPiPResolutionMode.Fixed )
		{
			return Math.Clamp(
				profile.PiPRenderResolution,
				256,
				2048
			);
		}

		int minimum = Math.Clamp( profile.PiPMinimumResolution, 256, 4096 );
		int maximum = Math.Clamp( profile.PiPMaximumResolution, minimum, 4096 );
		int quantum = Math.Clamp( profile.PiPResolutionQuantum, 16, 256 );

		float desired = MathF.Max( Screen.Height, 1 ) * MathX.Clamp(
			profile.PiPScreenHeightScale,
			0.15f,
			1.5f
		);

		int quantized = (int)MathF.Ceiling( desired / quantum ) * quantum;
		return Math.Clamp( quantized, minimum, maximum );
	}

	private void ResolveCameraPose(
		OMROpticProfile profile,
		float aimFraction,
		Vector3 sourcePosition,
		Rotation sourceRotation,
		out Vector3 position,
		out Rotation rotation,
		out Vector3 physicalOpticPosition
	)
	{
		position = sourcePosition;
		rotation = sourceRotation;
		physicalOpticPosition = sourcePosition;
		CurrentPiPPositionBlend = 1f;

		if ( profile.PiPCameraPoseMode == OMRPiPCameraPoseMode.MainCamera )
			return;

		GameObject anchor = ResolveViewModelCameraAnchor( profile );
		if ( anchor is null || !anchor.IsValid )
			return;

		// The active weapon publishes bone helpers from its final PlaceViewModel root
		// before PreRender. Do not force another world-space bone read here: the
		// renderer's bone cache was produced before BaseCombatWeapon moved the root
		// for this camera frame, so re-reading it can reintroduce a one-frame offset.
		physicalOpticPosition = anchor.WorldPosition;
		position = physicalOpticPosition;
		CurrentPiPPositionBlend = 0f;

		if ( profile.BlendPiPPositionToGameplayEye )
		{
			CurrentPiPPositionBlend = ResolveSmoothBlend(
				aimFraction,
				profile.PiPPositionBlendStart,
				profile.PiPPositionBlendFull
			);

			position = Vector3.Lerp(
				physicalOpticPosition,
				sourcePosition,
				CurrentPiPPositionBlend
			);
		}

		if ( profile.PiPCameraPoseMode == OMRPiPCameraPoseMode.ViewModelAnchorFullTransform )
			rotation = anchor.WorldRotation;
	}

	private void UpdateEyeBoxState(
		OMROpticProfile profile,
		Vector3 eyePosition,
		Rotation eyeRotation,
		Vector3 physicalOpticPosition
	)
	{
		CurrentEyeBoxAngularError = 0f;
		CurrentEyeBoxAlignment = 1f;
		CurrentEyeBoxOffset = Vector2.Zero;

		if ( !profile.EnableEyeBoxSimulation )
			return;

		Vector3 eyeToOptic = physicalOpticPosition - eyePosition;
		float forward = Vector3.Dot( eyeToOptic, eyeRotation.Forward );
		float right = Vector3.Dot( eyeToOptic, eyeRotation.Right );
		float up = Vector3.Dot( eyeToOptic, eyeRotation.Up );

		float depth = MathF.Max( MathF.Abs( forward ), 0.001f );
		float rightAngle = MathF.Atan2( right, depth ) * (180f / MathF.PI);
		float upAngle = MathF.Atan2( up, depth ) * (180f / MathF.PI);

		CurrentEyeBoxAngularError = MathF.Sqrt(
			rightAngle * rightAngle +
			upAngle * upAngle
		);

		float perfect = MathX.Clamp(
			profile.EyeBoxPerfectAngleDegrees,
			0f,
			45f
		);

		float fullShadow = MathX.Clamp(
			profile.EyeBoxFullShadowAngleDegrees,
			MathF.Max( perfect + 0.001f, 0.001f ),
			89f
		);

		float shadowBlend = ResolveSmoothBlend(
			CurrentEyeBoxAngularError,
			perfect,
			fullShadow
		);

		CurrentEyeBoxAlignment = 1f - shadowBlend;

		float normalizedRight = MathX.Clamp( rightAngle / fullShadow, -1f, 1f );
		float normalizedUp = MathX.Clamp( upAngle / fullShadow, -1f, 1f );
		float length = MathF.Sqrt(
			normalizedRight * normalizedRight +
			normalizedUp * normalizedUp
		);

		if ( length > 1f )
		{
			normalizedRight /= length;
			normalizedUp /= length;
		}

		CurrentEyeBoxOffset = new Vector2(
			normalizedRight,
			normalizedUp
		);
	}

	/// <summary>
	/// Measure the current physical lens diameter from the ScopeImagePlane renderer.
	/// Phase 6C4 prefers the real mesh that actually presents the PiP image, so the
	/// legacy WorldPanel can be removed from the prefab without changing apparent
	/// magnification. A WorldPanel path remains as a compatibility fallback for any
	/// older optic prefab that has not migrated yet.
	///
	/// Projection stays manual instead of using CameraComponent.PointToScreenPixels:
	/// the viewmodel is positioned against the fully composed gameplay view after
	/// camera feel/recoil modifiers, and these inputs are that settled view.
	/// </summary>
	private bool TryMeasureModelLensScreenFraction(
		OMROpticProfile profile,
		Vector3 eyePosition,
		Rotation eyeRotation,
		float horizontalFov,
		out float fraction
	)
	{
		fraction = 0f;
		CurrentLensMeasurementSource = "AuthoredFallback";

		ResolveLensMeasurementReferences( profile );

		if (
			_resolvedLensImageRenderer is not null &&
			_resolvedLensImageRenderer.IsValid &&
			TryMeasureRendererScreenFraction(
				_resolvedLensImageRenderer,
				eyePosition,
				eyeRotation,
				horizontalFov,
				out fraction
			)
		)
		{
			CurrentLensMeasurementSource = "PhysicalMesh";
			return true;
		}

		if (
			_resolvedLensMeasureObject is not null &&
			_resolvedLensMeasureObject.IsValid &&
			_resolvedLensWorldPanel is not null &&
			_resolvedLensWorldPanel.IsValid &&
			TryMeasureLegacyWorldPanelScreenFraction(
				_resolvedLensMeasureObject,
				_resolvedLensWorldPanel,
				eyePosition,
				eyeRotation,
				horizontalFov,
				out fraction
			)
		)
		{
			CurrentLensMeasurementSource = "LegacyWorldPanel";
			return true;
		}

		return false;
	}

	private bool TryMeasureRendererScreenFraction(
		ModelRenderer renderer,
		Vector3 eyePosition,
		Rotation eyeRotation,
		float horizontalFov,
		out float fraction
	)
	{
		fraction = 0f;

		if ( renderer is null || !renderer.IsValid || renderer.GameObject is null || !renderer.GameObject.IsValid )
			return false;

		BBox localBounds = renderer.LocalBounds;
		Vector3 localSize = localBounds.Size;
		float maxDimension = MathF.Max(
			MathF.Abs( localSize.x ),
			MathF.Max( MathF.Abs( localSize.y ), MathF.Abs( localSize.z ) )
		);

		if ( maxDimension <= 0.0001f )
			return false;

		Transform worldTransform = renderer.GameObject.WorldTransform;
		float minX = float.MaxValue;
		float maxX = float.MinValue;

		foreach ( Vector3 localCorner in localBounds.Corners )
		{
			Vector3 worldCorner = worldTransform.PointToWorld( localCorner );

			if ( !TryProjectHorizontalNormalized(
				worldCorner,
				eyePosition,
				eyeRotation,
				horizontalFov,
				out float x
			) )
			{
				return false;
			}

			minX = MathF.Min( minX, x );
			maxX = MathF.Max( maxX, x );
		}

		fraction = MathX.Clamp( maxX - minX, 0f, 1f );
		return fraction > 0.001f;
	}

	private bool TryMeasureLegacyWorldPanelScreenFraction(
		GameObject lensObject,
		Sandbox.WorldPanel worldPanel,
		Vector3 eyePosition,
		Rotation eyeRotation,
		float horizontalFov,
		out float fraction
	)
	{
		fraction = 0f;

		float panelScale = Sandbox.UI.WorldPanel.ScreenToWorldScale;
		if ( panelScale <= 0f )
			return false;

		Vector3 worldScale = lensObject.WorldScale;
		float halfWidth = worldPanel.PanelSize.x * 0.5f * panelScale * MathF.Max( MathF.Abs( worldScale.y ), 0.0001f );
		float halfHeight = worldPanel.PanelSize.y * 0.5f * panelScale * MathF.Max( MathF.Abs( worldScale.z ), 0.0001f );

		Vector3 right = lensObject.WorldRotation.Right * halfWidth;
		Vector3 up = lensObject.WorldRotation.Up * halfHeight;
		Vector3 center = lensObject.WorldPosition;

		Vector3[] corners =
		{
			center - right - up,
			center + right - up,
			center - right + up,
			center + right + up
		};

		float minX = float.MaxValue;
		float maxX = float.MinValue;

		for ( int i = 0; i < corners.Length; i++ )
		{
			if ( !TryProjectHorizontalNormalized(
				corners[i],
				eyePosition,
				eyeRotation,
				horizontalFov,
				out float x
			) )
			{
				return false;
			}

			minX = MathF.Min( minX, x );
			maxX = MathF.Max( maxX, x );
		}

		fraction = MathX.Clamp( maxX - minX, 0f, 1f );
		return fraction > 0.001f;
	}

	private void ResolveLensMeasurementReferences( OMROpticProfile profile )
	{
		OneMoreRoundWeapon weapon = ActiveWeapon;
		GameObject viewModel = weapon?.ViewModel;
		string objectName = profile?.ViewModelAimAnchorName;

		if ( viewModel is null || !viewModel.IsValid || string.IsNullOrWhiteSpace( objectName ) )
		{
			ClearLensMeasurementReferences();
			return;
		}

		bool cacheStillValid =
			_lensMeasureViewModel == viewModel &&
			string.Equals( _lensMeasureObjectName, objectName, StringComparison.Ordinal ) &&
			_resolvedLensMeasureObject is not null &&
			_resolvedLensMeasureObject.IsValid;

		if ( cacheStillValid )
		{
			// Renderer/component references may legitimately be removed while editing a
			// prefab, so refresh those lightweight lookups even when the anchor cache is valid.
			RefreshLensMeasurementComponents();
			return;
		}

		_lensMeasureViewModel = viewModel;
		_lensMeasureObjectName = objectName;
		_resolvedLensMeasureObject = viewModel
			.GetAllObjects( true )
			.FirstOrDefault(
				x => x is not null && x.IsValid && string.Equals( x.Name, objectName, StringComparison.OrdinalIgnoreCase )
			);

		_resolvedLensImageRenderer = null;
		_resolvedLensWorldPanel = null;
		_smoothedMeasuredLensScreenFraction = -1f;

		RefreshLensMeasurementComponents();
	}

	private void RefreshLensMeasurementComponents()
	{
		if ( _resolvedLensMeasureObject is null || !_resolvedLensMeasureObject.IsValid )
		{
			_resolvedLensImageRenderer = null;
			_resolvedLensWorldPanel = null;
			return;
		}

		OMRScopePhysicalLens physicalLens = _resolvedLensMeasureObject
			.GetComponent<OMRScopePhysicalLens>( true );

		ModelRenderer physicalRenderer = physicalLens?.ImageRenderer;

		if ( physicalRenderer is null || !physicalRenderer.IsValid )
		{
			GameObject imageObject = _resolvedLensMeasureObject
				.GetAllObjects( true )
				.FirstOrDefault(
					x => x is not null && x.IsValid && string.Equals( x.Name, "ScopeImagePlane", StringComparison.OrdinalIgnoreCase )
				);

			physicalRenderer = imageObject?.Components.Get<ModelRenderer>();
		}

		_resolvedLensImageRenderer =
			physicalRenderer is not null && physicalRenderer.IsValid
				? physicalRenderer
				: null;

		_resolvedLensWorldPanel = _resolvedLensMeasureObject
			.GetComponent<Sandbox.WorldPanel>( true );
	}

	private void ClearLensMeasurementReferences()
	{
		_lensMeasureViewModel = null;
		_lensMeasureObjectName = null;
		_resolvedLensMeasureObject = null;
		_resolvedLensImageRenderer = null;
		_resolvedLensWorldPanel = null;
		_smoothedMeasuredLensScreenFraction = -1f;
	}

	private static bool TryProjectHorizontalNormalized(
		Vector3 worldPoint,
		Vector3 eyePosition,
		Rotation eyeRotation,
		float horizontalFov,
		out float normalizedX
	)
	{
		normalizedX = 0f;

		Vector3 delta = worldPoint - eyePosition;
		float depth = Vector3.Dot( delta, eyeRotation.Forward );
		if ( depth <= 0.001f )
			return false;

		float lateral = Vector3.Dot( delta, eyeRotation.Right );
		float tanHalfFov = MathF.Tan( MathX.Clamp( horizontalFov, 1f, 179f ) * (MathF.PI / 360f) );
		if ( MathF.Abs( tanHalfFov ) <= 0.00001f )
			return false;

		float ndcX = lateral / (depth * tanHalfFov);
		normalizedX = 0.5f + ndcX * 0.5f;
		return true;
	}

	private static float ResolveSmoothBlend(
		float value,
		float startValue,
		float fullValue
	)
	{
		float start = MathF.Min( startValue, fullValue );
		float full = MathF.Max( startValue, fullValue );

		if ( full <= start + 0.0001f )
			return value >= full ? 1f : 0f;

		float t = MathX.Clamp(
			(value - start) / (full - start),
			0f,
			1f
		);

		return t * t * (3f - 2f * t);
	}

	private GameObject ResolveViewModelCameraAnchor( OMROpticProfile profile )
	{
		OneMoreRoundWeapon weapon = ActiveWeapon;
		GameObject viewModel = weapon?.ViewModel;
		string anchorName = profile.PiPCameraAnchorName;

		if (
			viewModel is null ||
			!viewModel.IsValid ||
			string.IsNullOrWhiteSpace( anchorName )
		)
		{
			ClearResolvedAnchor();
			return null;
		}

		if (
			_resolvedCameraAnchor is not null &&
			_resolvedCameraAnchor.IsValid &&
			_anchorViewModel == viewModel &&
			string.Equals( _anchorName, anchorName, StringComparison.Ordinal )
		)
		{
			return _resolvedCameraAnchor;
		}

		_anchorViewModel = viewModel;
		_anchorName = anchorName;
		_resolvedCameraAnchor = viewModel
			.GetAllObjects( true )
			.FirstOrDefault(
				x =>
					x is not null &&
					x.IsValid &&
					string.Equals( x.Name, anchorName, StringComparison.OrdinalIgnoreCase )
			);

		if ( _resolvedCameraAnchor is null || !_resolvedCameraAnchor.IsValid )
		{
			if ( LogMissingAnchor && !_loggedMissingAnchor )
			{
				_loggedMissingAnchor = true;
				Log.Warning(
					$"[OMR SCOPE PIP] Viewmodel '{viewModel.Name}' has no camera anchor named '{anchorName}'. Falling back to the main gameplay camera position."
				);
			}

			return null;
		}

		_loggedMissingAnchor = false;
		return _resolvedCameraAnchor;
	}

	private void ClearResolvedAnchor()
	{
		_anchorViewModel = null;
		_anchorName = null;
		_resolvedCameraAnchor = null;
		_loggedMissingAnchor = false;

		ClearLensMeasurementReferences();
		IsLensScreenFractionMeasured = false;
		CurrentLensMeasurementSource = "AuthoredFallback";
		CurrentRawMeasuredLensScreenFraction = 0f;
	}

	private void DisposeRenderTarget()
	{
		if ( _scopeCamera is not null && _scopeCamera.IsValid )
			_scopeCamera.RenderTarget = null;

		if ( _outputTexture is not null && _outputTexture.IsValid )
			_outputTexture.Dispose();

		_outputTexture = null;
		_outputResolution = 0;
		CurrentScopeFieldOfView = 0f;
		CurrentRenderFieldOfView = 0f;
		CurrentOpticalReferenceFieldOfView = 0f;
		CurrentLensProjectionFieldOfView = 0f;
		CurrentScopeMagnification = 1f;
		PresentationZoomScale = 1f;
		CurrentClipSpaceBounds = null;
		CurrentEyeToScopeCameraDistance = 0f;
		CurrentEyeToPhysicalOpticDistance = 0f;
		CurrentPiPPositionBlend = 0f;
		CurrentEyeBoxAngularError = 0f;
		CurrentEyeBoxAlignment = 1f;
		CurrentEyeBoxOffset = Vector2.Zero;
		CurrentLensScreenFraction = 1f;
		CurrentLensMeasurementSource = "AuthoredFallback";
		CurrentTargetApparentMagnification = 1f;
		CurrentRenderCropMagnification = 1f;
		CurrentExpectedApparentMagnification = 1f;
		LastManualRenderSucceeded = false;
	}

	private void SetRenderingState( bool rendering )
	{
		_renderRequested = rendering;

		if ( _scopeCamera is not null && _scopeCamera.IsValid )
		{
			// Manual RT cameras remain disabled in the scene scheduler. This matches
			// current S&box RenderToTexture usage: a disabled CameraComponent can still
			// be rendered explicitly without participating in normal camera composition.
			_scopeCamera.Enabled = false;
			_scopeCamera.RenderTarget = null;
		}

		if ( !rendering )
			LastManualRenderSucceeded = false;

		if ( rendering == _lastRenderingState )
			return;

		_lastRenderingState = rendering;

		if ( LogStateChanges )
		{
			Log.Info(
				$"[OMR SCOPE PIP] Rendering:{rendering} | " +
				$"Profile:{ActiveProfile?.ResourceName ?? "NONE"} | " +
				$"Resolution:{_outputResolution} | " +
				$"Anchor:{ResolvedCameraAnchor?.Name ?? "MAIN_CAMERA"}"
			);
		}
	}

	private void DestroyScopeCamera()
	{
		if ( _scopeCameraObject is not null && _scopeCameraObject.IsValid )
			_scopeCameraObject.Destroy();

		_scopeCameraObject = null;
		_scopeCamera = null;
	}
}