OneMoreRoundWeapon.ViewModelAlignment.cs
using Sandbox;
using System;
using System.Collections.Generic;

/// <summary>
/// Final owner-local viewmodel placement extensions.
///
/// Frame ordering is deliberate:
/// 1) capture every bone helper ROOT-RELATIVE while current animation bones are settled;
/// 2) let BaseCombatWeapon pin the viewmodel root to the composed CameraView;
/// 3) center the authored optic using the captured root-relative lens pose;
/// 4) reapply every helper from the final root before PreRender.
///
/// This removes both sources of one-frame lag from the previous implementation:
/// the alignment no longer chases a cached world-space bone after moving the root, and
/// WorldPanel / PiP helpers no longer wait until an unordered OnPreRender callback to
/// learn where this frame's scope actually is.
/// </summary>
public partial class OneMoreRoundWeapon
{
	private readonly List<OMRViewModelBoneAnchor> _cachedViewModelBoneAnchors = new();
	private readonly List<CapturedViewModelBoneAnchor> _capturedViewModelBoneAnchors = new();
	private GameObject _boneAnchorCacheViewModel;

	private readonly struct CapturedViewModelBoneAnchor
	{
		public readonly OMRViewModelBoneAnchor Anchor;
		public readonly OMRViewModelBoneAnchor.RootRelativePose Pose;
		public readonly bool IsValid;

		public CapturedViewModelBoneAnchor(
			OMRViewModelBoneAnchor anchor,
			in OMRViewModelBoneAnchor.RootRelativePose pose,
			bool isValid
		)
		{
			Anchor = anchor;
			Pose = pose;
			IsValid = isValid;
		}
	}

	protected override void PlaceViewModel(
		CameraComponent camera,
		in CameraView view
	)
	{
		GameObject viewModel = ViewModel;

		if ( viewModel is null || !viewModel.IsValid )
		{
			base.PlaceViewModel( camera, view );
			ClearViewModelBoneAnchorCache();
			return;
		}

		EnsureViewModelBoneAnchorCache( viewModel );
		CaptureViewModelBoneAnchors( viewModel );

		// Canonical engine placement first.
		base.PlaceViewModel( camera, view );

		OMRViewModelAimAlignment alignment =
			viewModel.Components.Get<OMRViewModelAimAlignment>();

		if ( alignment is not null && alignment.IsValid )
		{
			OMROpticProfile profile = ActiveOpticProfile;

			if (
				profile is not null &&
				profile.IsValid &&
				profile.EnableViewModelAimAlignment &&
				TryGetCapturedAnchor(
					profile.ViewModelAimAnchorName,
					out OMRViewModelBoneAnchor.RootRelativePose alignmentPose
				)
			)
			{
				alignment.ApplyAlignment(
					this,
					camera,
					view,
					alignmentPose
				);
			}
		}

		// The root is now final for this frame. Publish every helper immediately so
		// the PiP camera and WorldPanel see the same transform before PreRender.
		ApplyCapturedViewModelBoneAnchors( viewModel );
	}

	private void EnsureViewModelBoneAnchorCache( GameObject viewModel )
	{
		if ( _boneAnchorCacheViewModel == viewModel )
			return;

		_boneAnchorCacheViewModel = viewModel;
		_cachedViewModelBoneAnchors.Clear();
		_capturedViewModelBoneAnchors.Clear();

		foreach ( GameObject obj in viewModel.GetAllObjects( true ) )
		{
			if ( obj is null || !obj.IsValid )
				continue;

			OMRViewModelBoneAnchor anchor =
				obj.GetComponent<OMRViewModelBoneAnchor>( true );

			if ( anchor is null || !anchor.IsValid )
				continue;

			_cachedViewModelBoneAnchors.Add( anchor );
		}
	}

	private void CaptureViewModelBoneAnchors( GameObject viewModel )
	{
		_capturedViewModelBoneAnchors.Clear();

		for ( int i = 0; i < _cachedViewModelBoneAnchors.Count; i++ )
		{
			OMRViewModelBoneAnchor anchor = _cachedViewModelBoneAnchors[i];

			if ( anchor is null || !anchor.IsValid )
				continue;

			bool captured = anchor.TryCaptureRootRelative(
				viewModel,
				out OMRViewModelBoneAnchor.RootRelativePose pose
			);

			_capturedViewModelBoneAnchors.Add(
				new CapturedViewModelBoneAnchor(
					anchor,
					pose,
					captured
				)
			);
		}
	}

	private bool TryGetCapturedAnchor(
		string objectName,
		out OMRViewModelBoneAnchor.RootRelativePose pose
	)
	{
		pose = default;

		if ( string.IsNullOrWhiteSpace( objectName ) )
			return false;

		for ( int i = 0; i < _capturedViewModelBoneAnchors.Count; i++ )
		{
			CapturedViewModelBoneAnchor captured =
				_capturedViewModelBoneAnchors[i];

			if (
				!captured.IsValid ||
				captured.Anchor is null ||
				!captured.Anchor.IsValid ||
				!string.Equals(
					captured.Anchor.GameObject.Name,
					objectName,
					StringComparison.OrdinalIgnoreCase
				)
			)
			{
				continue;
			}

			pose = captured.Pose;
			return true;
		}

		return false;
	}

	private void ApplyCapturedViewModelBoneAnchors( GameObject viewModel )
	{
		for ( int i = 0; i < _capturedViewModelBoneAnchors.Count; i++ )
		{
			CapturedViewModelBoneAnchor captured =
				_capturedViewModelBoneAnchors[i];

			if (
				!captured.IsValid ||
				captured.Anchor is null ||
				!captured.Anchor.IsValid
			)
			{
				continue;
			}

			captured.Anchor.ApplyRootRelative(
				viewModel,
				captured.Pose
			);
		}
	}

	private void ClearViewModelBoneAnchorCache()
	{
		_boneAnchorCacheViewModel = null;
		_cachedViewModelBoneAnchors.Clear();
		_capturedViewModelBoneAnchors.Clear();
	}
}