ForwardAutoRunReview.cs
using Sandbox;
using System;
namespace PaintballBaddies;

/// <summary>Feeds mapped actions before the production auto-run/native movement hooks.</summary>
public sealed class ForwardAutoRunReview : Component
{
    private PlayerController player;
    private ForwardAutoRun runner;
    private PaintballMarker weapon;
    private int initialShots;
    private GameObject runway;
    private IDisposable updateHook,fixedHook;
    private float elapsed;
    private int phase;
    private bool initialized,originalAutomatic;
    protected override void OnUpdate()
    {
        if(initialized)return;
        player=Scene.GetAllComponents<PlayerController>().FirstOrDefault();
        runner=player?.Components.Get<ForwardAutoRun>();
        if(!runner.IsValid())return;
        initialized=true;originalAutomatic=runner.Automatic;
        weapon=player.Components.Get<PaintballMarker>();initialShots=weapon.Shots;
        if(!runner.Automatic)runner.ToggleAutomatic();
        // Keep native physics, but isolate the movement test from new arena cover props.
        runway=TrainingRange.Box(GameObject,"Focus run review floor",new Vector3(0,1250,1500),new Vector3(3200,600,20),Color.Gray,true);
        player.WorldPosition=new Vector3(-900,1250,1512);player.EyeAngles=Angles.Zero;player.UseLookControls=false;
        weapon.AcceptInput=true;
        updateHook=Scene.AddHook(GameObjectSystem.Stage.StartUpdate,-95,Inputs,nameof(ForwardAutoRunReview),"Review actions");
        fixedHook=Scene.AddHook(GameObjectSystem.Stage.StartFixedUpdate,-95,()=>{elapsed+=Time.Delta;Inputs();},nameof(ForwardAutoRunReview),"Review actions");
    }
    private void Inputs()
    {
        var forward=elapsed<2.2f || (elapsed>=2.7f && elapsed<7.1f);
        Input.SetAction("forward",forward);Input.SetAction("backward",false);
        Input.SetAction("run",elapsed>=5.8f && elapsed<6.6f);
        Input.SetAction("attack2",elapsed>=2 && elapsed<7.1f);
        Input.SetAction("attack1",elapsed>=6.6f && elapsed<7f);
        Input.SetAction("duck",elapsed>=4.4f && elapsed<4.7f);
        Input.SetAction("walk",elapsed>=4.9f && elapsed<5.2f);
        Input.AnalogMove=forward ? Vector3.Forward : Vector3.Zero;
    }
    private void Check(string name,bool pass)=>Log.Info($"AUTO_RUN_REVIEW {(pass ? "PASS" : "FAIL")} {name} speed={player.Body.Velocity.WithZ(0).Length:0.0} held={runner.HeldSeconds:0.00}");
    protected override void OnFixedUpdate()
    {
        if(!initialized)return;
        if(phase==0 && elapsed>.8f){Check("starts walking",!runner.Running && player.Body.Velocity.WithZ(0).Length>60 && player.Body.Velocity.WithZ(0).Length<105);phase++;}
        if(phase==1 && elapsed>1.8f){Check("held forward reaches native run speed",runner.Running && Input.Down("run") && player.Body.Velocity.WithZ(0).Length>150);phase++;}
        if(phase==2 && elapsed>2.1f){Check("entering focus preserves automatic run",runner.Running && Input.Down("run") && weapon.Aiming && player.Body.Velocity.WithZ(0).Length>150);phase++;}
        if(phase==3 && elapsed>2.5f){Check("release clears timer",runner.HeldSeconds==0 && !Input.Down("run"));phase++;}
        if(phase==4 && elapsed>3.2f){Check("holding forward while focused starts with walk delay",!runner.Running && !Input.Down("run") && weapon.Aiming);phase++;}
        if(phase==5 && elapsed>4.2f){Check("sustained forward enters run while focused",runner.Running && Input.Down("run") && weapon.Aiming && player.Body.Velocity.WithZ(0).Length>150);phase++;}
        if(phase==6 && elapsed>4.5f){Check("crouch resets timer",runner.HeldSeconds==0 && !Input.Down("run"));phase++;}
        if(phase==7 && elapsed>5){Check("walk override resets timer",runner.HeldSeconds==0 && !Input.Down("run"));phase++;}
        if(phase==8 && elapsed>5.9f){Check("old manual run cannot bypass forward delay",!Input.Down("run") && !runner.Running && weapon.Aiming && player.Body.Velocity.WithZ(0).Length<105);phase++;}
        if(phase==9 && elapsed>6.9f){Check("forward-held focused running permits native marker fire",weapon.Shots>initialShots && weapon.Aiming && Input.Down("run") && player.Body.Velocity.WithZ(0).Length>150);phase++;}
    }
    protected override void OnDestroy()
    {
        updateHook?.Dispose();fixedHook?.Dispose();
        if(runway.IsValid())runway.Destroy();
        if(runner.IsValid() && runner.Automatic!=originalAutomatic)runner.ToggleAutomatic();
        if(player.IsValid()){player.UseLookControls=true;player.Components.Get<PaintballMarker>().AcceptInput=true;}
    }
}