TrainingChecks.cs
using Sandbox;

namespace PaintballBaddies;

/// <summary>Opt-in native integration run, disabled in the saved player scene.</summary>
public sealed class TrainingChecks : Component
{
    private PlayerController player;
    private PaintballMarker weapon;
    private float elapsed;
    private int phase;
    private Vector3 start;
    private GameObject ceiling;
    private Vector3? previousFoot;
    private float footTravel;
    private float maxSequenceTime;
    private float maxRunTime;
    private Vector3? secondaryStart;
    private float secondaryTravel;
    protected override void OnStart()
    {
        player = Components.Get<PlayerController>();
        weapon = Components.Get<PaintballMarker>();
        weapon.AcceptInput = false;
        player.UseInputControls = false;
        player.UseLookControls = false;
        player.EyeAngles = new Angles( 0, 0, 0 );
    }

    private void Check( string name, bool pass, string detail ) => Log.Info( $"PB_CHECK {(pass ? "PASS" : "FAIL")} {name}: {detail}" );

    protected override void OnFixedUpdate()
    {
        elapsed += Time.Delta;
        if ( phase == 0 && elapsed > 1 )
        {
            var edge = Scene.Trace.Ray( new Vector3(-440,16,55), new Vector3(-410,16,55) ).Run();
            Check( "shaped target edge is solid", edge.Hit && edge.GameObject.Components.Get<PaintballTarget>() is not null, $"hit={edge.Hit}" );
            var corner = Scene.Trace.Ray( new Vector3(-440,16,78), new Vector3(-410,16,78) ).Run();
            Check( "chamfered target corner stays clear", !corner.Hit, $"hit={corner.Hit}" );
            weapon.FireAt( new Vector3( -420, 0, 55 ) );
            phase++;
        }
        else if ( phase == 1 && elapsed > 1.7f )
        {
            Check( "target hit", weapon.Ammo == 39 && weapon.Shots == 1 && weapon.Hits == 1, $"ammo={weapon.Ammo} shots={weapon.Shots} hits={weapon.Hits}" );
            Check( "scored shot leaves paint decal", PaintImpactSystem.Find(Scene)?.ActiveCount == 1, $"marks={PaintImpactSystem.Find(Scene)?.ActiveCount}" );
            weapon.Reload();
            Check( "reload blocks fire", !weapon.FireAt( new Vector3( -420, 0, 55 ) ), $"remaining={weapon.ReloadRemaining}" );
            phase++;
        }
        else if ( phase == 2 && elapsed > 3.6f && weapon.ReloadRemaining == 0 )
        {
            Check( "reload conservation", weapon.Ammo == 40 && weapon.Reserve == 159, $"ammo={weapon.Ammo} reserve={weapon.Reserve}" );
            elapsed = 3.6f;
            GameObject.WorldPosition = new Vector3( -630, 65, 4 );
            player.Body.Velocity = Vector3.Zero;
            start = GameObject.WorldPosition;
            phase++;
        }
        else if ( phase == 3 )
        {
            player.WishVelocity = Vector3.Forward * player.WalkSpeed;
            if ( player.Renderer.TryGetBoneTransform( "LeftFoot", out var foot ) )
            {
                var relative = foot.Position - player.WorldPosition;
                if ( previousFoot.HasValue ) footTravel += (relative - previousFoot.Value).Length;
                previousFoot = relative;
            }
            maxSequenceTime = System.MathF.Max( maxSequenceTime, player.Renderer.Sequence.Time );
            if ( elapsed > 3.9f && player.Renderer.TryGetBoneTransform( "SuitSecondary_L", out var secondary )
                && player.Renderer.TryGetBoneTransform( "Spine01", out var chest ) )
            {
                var local = chest.Rotation.Inverse * (secondary.Position - chest.Position);
                secondaryStart ??= local;
                secondaryTravel = System.MathF.Max( secondaryTravel, (local - secondaryStart.Value).Length );
            }
            else if ( elapsed > 3.9f && player.Renderer.Model.Morphs.GetIndex( "SuitFollowThroughLUp" ) >= 0 )
            {
                var value = player.Renderer.Morphs.Get( "SuitFollowThroughLUp" ) - player.Renderer.Morphs.Get( "SuitFollowThroughLDown" );
                var local = Vector3.Up * value * (.0025f / .0254f);
                secondaryStart ??= local;
                secondaryTravel = System.MathF.Max( secondaryTravel, (local - secondaryStart.Value).Length );
            }
            if ( elapsed > 4.6f )
            {
                Check( "walk movement", GameObject.WorldPosition.x > start.x + 55, $"start={start} current={GameObject.WorldPosition}" );
                Check( "walk animation", player.Renderer.Sequence.Name == "walk", player.Renderer.Sequence.Name );
                Check( "walk animation advances", maxSequenceTime > 0.15f, $"max sequence time={maxSequenceTime}" );
                Check( "walk feet move relative to body", footTravel > 8, $"foot travel={footTravel}" );
                var footsteps=Components.Get<MovementFootsteps>();
                Check("walking emits bounded footsteps",footsteps?.StepsPlayed is >= 1 and <= 5,$"steps={footsteps?.StepsPlayed}");
                Check( "minute secondary motion", secondaryStart.HasValue && secondaryTravel > 0.002f && secondaryTravel < 0.25f,
                    $"secondary excursion={secondaryTravel} inches (bone or authored morph range)" );
                phase++;
            }
        }
        else if ( phase == 4 )
        {
            player.WishVelocity = Vector3.Forward * player.RunSpeed;
            if ( player.Renderer.Sequence.Name == "run" )
                maxRunTime = System.MathF.Max( maxRunTime, player.Renderer.Sequence.Time );
            if ( elapsed > 6.3f )
            {
                Check( "run animation advances", maxRunTime > 0.15f, $"max run time={maxRunTime}" );
                Check( "cover collision", GameObject.WorldPosition.x < -295 && GameObject.WorldPosition.x > -360, $"position={GameObject.WorldPosition}" );
                player.WishVelocity = Vector3.Zero;
                GameObject.WorldPosition = new Vector3( -630, 65, 4 );
                player.Body.Velocity = Vector3.Zero;
                player.UpdateDucking( true );
                phase++;
            }
        }
        else if ( phase == 5 && elapsed > 7 )
        {
            Check( "crouch collider", player.CurrentHeight < 50, $"height={player.CurrentHeight}" );
            Check( "crouch animation", player.Renderer.Sequence.Name == "crouch", player.Renderer.Sequence.Name );
            ceiling = TrainingRange.Box( null, "Temporary clearance test", new Vector3( -630, 65, 55 ), new Vector3( 100, 100, 10 ), Color.White, true );
            phase++;
        }
        else if ( phase == 6 && elapsed > 7.6f )
        {
            player.UpdateDucking( false );
            phase++;
        }
        else if ( phase == 7 && elapsed > 8.1f )
        {
            Check( "standing clearance", player.CurrentHeight < 50, $"height under ceiling={player.CurrentHeight}" );
            ceiling.Destroy();
            phase++;
        }
        else if ( phase == 8 && elapsed > 8.5f )
        {
            player.UpdateDucking( false );
            phase++;
        }
        else if ( phase == 9 && elapsed > 9.2f )
        {
            Check( "stand after clearance", player.CurrentHeight > 60, $"height={player.CurrentHeight}" );
            weapon.ResetTraining();
            GameObject.WorldPosition = new Vector3( -630, 0, 4 );
            phase++;
        }
        else if ( phase == 10 && elapsed > 9.7f )
        {
            ceiling = TrainingRange.Box( null, "Temporary barrel obstruction", (player.EyePosition + weapon.Muzzle) * 0.5f,
                new Vector3( 8, 40, 80 ), Color.White, true );
            phase++;
        }
        else if ( phase == 11 && elapsed > 10 )
        {
            weapon.FireAt( new Vector3( -420, 0, 55 ) );
            phase++;
        }
        else if ( phase == 12 && elapsed > 10.5f )
        {
            Check( "barrel obstruction", weapon.Shots == 1 && weapon.Hits == 0, $"shots={weapon.Shots} hits={weapon.Hits}" );
            ceiling.Destroy();
            ceiling = TrainingRange.Box( null, "Temporary downrange obstruction", new Vector3( -500, 0, 55 ),
                new Vector3( 4, 100, 100 ), Color.White, true );
            phase++;
        }
        else if ( phase == 13 && elapsed > 11 )
        {
            weapon.FireAt( new Vector3( -420, 0, 55 ) );
            phase++;
        }
        else if ( phase == 14 && elapsed > 11.5f )
        {
            Check( "projectile cover obstruction", weapon.Shots == 2 && weapon.Hits == 0, $"shots={weapon.Shots} hits={weapon.Hits}" );
            ceiling.Destroy();
            weapon.ResetTraining();
            phase++;
        }
        else if ( phase == 15 )
        {
            weapon.FireAt( new Vector3( -420, 0, 55 ) );
            if ( elapsed > 17 )
            {
                Check( "empty magazine", weapon.Ammo == 0 && weapon.Shots == 40 && !weapon.FireAt( Vector3.Zero ), $"ammo={weapon.Ammo} shots={weapon.Shots}" );
                weapon.Reload();
                phase++;
            }
        }
        else if ( phase == 16 && elapsed > 19 && weapon.ReloadRemaining == 0 )
        {
            Check( "full reload", weapon.Ammo == 40 && weapon.Reserve == 120, $"ammo={weapon.Ammo} reserve={weapon.Reserve}" );
            weapon.ResetTraining();
            weapon.AcceptInput = true;
            player.UseInputControls = true;
            player.UseLookControls = true;
            GameObject.WorldPosition = new Vector3( -630, 0, 4 );
            Log.Info( "PB_CHECK COMPLETE" );
            Enabled = false;
        }
    }
}