MovingReloadChecks.cs
using Sandbox;
using System;
namespace PaintballBaddies;
/// <summary>Opt-in walking/crouched reload review using actual controller movement.</summary>
public sealed class MovingReloadChecks : Component
{
    [Property] public int CharacterIndex {get;set;}
    [Property] public bool Crouched {get;set;}
    [Property] public bool Running {get;set;}
    [Property] public bool Stationary {get;set;}
    [Property] public string CandidateModelPath {get;set;} = "";
    private PlayerController player;
    private PaintballMarker weapon;
    private float elapsed,maxArmDifference,maxFootDifference;
    private int stage,samples,activeFrames,fullBodyFrames;
    private Vector3 start;
    private Vector3 previousHand;
    private bool hasHand;
    private float maxHandStep,maxStepPhase,maxStepElapsed;
    protected override void OnStart()
    {
        player=Scene.GetAllComponents<PlayerController>().First();weapon=player.Components.Get<PaintballMarker>();
        weapon.PoseApplied+=ObserveFinalPose;
        player.UseInputControls=player.UseLookControls=player.UseCameraControls=false;weapon.AcceptInput=false;
    }
    protected override void OnUpdate()
    {
        elapsed+=Time.Delta;
        if(stage==0)
        {
            player.Components.Get<RosterSelection>().Select(CharacterIndex);player.WorldPosition=new Vector3(-950,-500,2);player.EyeAngles=new Angles(0,90,0);start=player.WorldPosition;stage=1;
            if(!string.IsNullOrEmpty(CandidateModelPath))player.Renderer.Model=Model.Load(CandidateModelPath);
        }
        player.UpdateDucking(Crouched);player.WishVelocity=Vector3.Left*(Stationary ? 0 : Running ? 180 : 55);
        WorldPosition=player.WorldPosition+new Vector3(150,-100,90);WorldRotation=Rotation.LookAt(player.WorldPosition+Vector3.Up*38-WorldPosition);
        if(stage==1 && elapsed>.7f)
        {
            Check("shot prepares reload",weapon.FireAt(player.WorldPosition+Vector3.Left*500+Vector3.Up*70),"shot accepted");
            weapon.Reload();stage=2;
        }
        if(stage==2 && elapsed>1 && elapsed<2.9f)
        {
            if(player.Components.Get<ReloadPresentationTrack>() is {Docked:false})activeFrames++;
            var body=player.Renderer;
            if(body.Sequence.Name=="reload_standing")fullBodyFrames++;
            foreach(var name in new[]{"LeftHand","LeftFoot","RightFoot"})
            {
                var bone=body.Model.Bones.GetBone(name);
                if(!body.TryGetBoneTransformAnimation(bone,out var raw) || !body.TryGetBoneTransform(name,out var final))continue;
                var difference=(raw.Position-final.Position).Length;
                if(name=="LeftHand")maxArmDifference=MathF.Max(maxArmDifference,difference);else maxFootDifference=MathF.Max(maxFootDifference,difference);
            }
            samples++;
        }
        if(elapsed<3.6f)return;
        Check("selected character",player.Renderer.Model?.Name==(string.IsNullOrEmpty(CandidateModelPath) ? RosterSelection.GetModelPath(CharacterIndex) : CandidateModelPath),player.Renderer.Model?.Name);
        Check(Stationary ? "stationary position retained" : "movement continues",Stationary ? (player.WorldPosition-start).WithZ(0).Length<2 : Vector3.Dot(player.WorldPosition-start,Vector3.Left)>140,$"travel={player.WorldPosition-start}");
        Check("locomotion retained",player.Renderer.Sequence.Name==(Stationary ? "idle" : Crouched ? "crouch_walk" : Running ? "run" : "walk"),player.Renderer.Sequence.Name);
        Check("reload props animate",activeFrames>60,$"active_frames={activeFrames}");
        Check("upper body layered",samples>60 && maxArmDifference>2,$"hand_offset_inches={maxArmDifference}, full_body_frames={fullBodyFrames}");
        Check("legs retain animation",maxFootDifference<.1f,$"foot_override_inches={maxFootDifference}");
        Check("ammo conserved",weapon.Ammo==40 && weapon.Reserve==159 && weapon.ReloadRemaining==0,$"ammo={weapon.Ammo},reserve={weapon.Reserve}");
        Check("gear docked after reload",player.Components.Get<ReloadPresentationTrack>() is {Docked:true},"dock state");
        Log.Info($"MOVING_RELOAD OBSERVATION maximum relative hand step inches={maxHandStep} running={Running} reload_phase={maxStepPhase} elapsed={maxStepElapsed}");
        Log.Info("MOVING_RELOAD COMPLETE");Destroy();
    }
    private void Check(string n,bool pass,string detail)=>Log.Info($"MOVING_RELOAD {(pass ? "PASS" : "FAIL")} {n}: {detail}");
    private void ObserveFinalPose(SkinnedModelRenderer body)
    {
        if(elapsed<=.5f || !body.IsValid() || !body.TryGetBoneTransform("LeftHand",out var hand))return;
        var relative=hand.Position-player.WorldPosition;
        if(hasHand && (relative-previousHand).Length>maxHandStep)
        {
            maxHandStep=(relative-previousHand).Length;maxStepPhase=weapon.ReloadProgress;maxStepElapsed=elapsed;
        }
        previousHand=relative;hasHand=true;
    }
    protected override void OnDestroy()
    {
        if(weapon.IsValid())weapon.PoseApplied-=ObserveFinalPose;
        if(!player.IsValid())return;
        player.WishVelocity=Vector3.Zero;player.UpdateDucking(false);player.UseInputControls=player.UseLookControls=player.UseCameraControls=true;weapon.AcceptInput=true;
    }
}