OpponentVisionChecks.cs
using Sandbox;

namespace PaintballBaddies;

/// <summary>Opt-in native perception check in the annex's clear west lane.</summary>
public sealed class OpponentVisionChecks : Component
{
    private PlayerController player;
    private GameObject opponent, blocker;
    private ArenaOpponent bot;
    private float elapsed;
    private int stage;
    private Vector3 original;
    private bool input;
    private bool impactSent;
    protected override void OnStart()
    {
        player = Scene.GetAllComponents<PlayerController>().First();
        original = player.WorldPosition; input = player.UseInputControls;
        player.UseInputControls = false;
        opponent = new GameObject { Name = "Vision check opponent" };
        opponent.WorldPosition = new Vector3(-900,1250,1);
        bot = opponent.Components.Create<ArenaOpponent>();
        bot.Character = "imani";
        bot.CombatEnabled = true;
    }
    private void Check(string name,bool pass) => Log.Info($"VISION_CHECK {(pass ? "PASS" : "FAIL")} {name}");
    protected override void OnUpdate()
    {
        elapsed += Time.Delta;
        opponent.WorldPosition = new Vector3(-900,1250,1);
        if(stage<4)opponent.WorldRotation = Rotation.Identity;
        var nav = opponent.Components.Get<NavMeshAgent>();
        if(nav.IsValid()) { nav.MaxSpeed=0; nav.Stop(); }
        player.WorldPosition = stage == 0 || stage == 4 ? new Vector3(-1050,1250,4) : stage == 2 ? new Vector3(-650,1250,4) : new Vector3(-700,1250,4);
        player.Body.Velocity=Vector3.Zero;player.WishVelocity=Vector3.Zero;
        if(stage==0 && elapsed>1.5f)
        {
            Check("rear target is not acquired",!bot.SeesPlayer);
            stage=1;elapsed=0;
        }
        else if(stage==1 && elapsed>1.5f)
        {
            var probe=Scene.Trace.Ray(opponent.WorldPosition+Vector3.Up*60,player.EyePosition).IgnoreGameObjectHierarchy(opponent).WithoutTags("paintball_debris").Run();
            Log.Info($"VISION_TRACE hit={probe.Hit} object={probe.GameObject?.Name} actor={PaintballCombatant.Find(probe.GameObject)?.GameObject.Name} eye={player.EyePosition} end={probe.EndPosition}");
            Check("front target is acquired",bot.SeesPlayer);
            blocker=new GameObject { Name="Vision check occluder" };
            // Within the old 24-inch endpoint tolerance: a wall here must still occlude.
            blocker.WorldPosition=new Vector3(-665,1250,50);
            var shape=blocker.Components.Create<BoxCollider>();shape.Scale=new Vector3(10,150,100);shape.Static=true;
            stage=2;elapsed=0;
        }
        else if(stage==2 && elapsed>1.5f)
        {
            Check("solid cover close to target interrupts sight",!bot.SeesPlayer);
            Check("unseen movement does not update memory",(bot.LastKnownPlayerPosition-new Vector3(-700,1250,4)).Length<10);
            blocker.Destroy();blocker=null;stage=3;elapsed=0;
        }
        else if(stage==3 && elapsed>1.5f)
        {
            Check("removed obstruction allows reacquisition",bot.SeesPlayer);
            stage=4;elapsed=0;
        }
        else if(stage==4 && elapsed>.5f && !impactSent)
        {
            impactSent=true;
            var state=opponent.Components.Get<PaintballCombatant>();
            if(state.PaintHits==0)state.RegisterHit(0,new Vector3(-1050,1250,4));
        }
        else if(stage==4 && elapsed>2f)
        {
            Check("rear impact turns opponent to reacquire visible attacker",bot.SeesPlayer && Vector3.Dot(opponent.WorldRotation.Forward,Vector3.Backward)>.8f);
            Log.Info("VISION_CHECK COMPLETE");Destroy();
        }
    }
    protected override void OnDestroy()
    {
        opponent?.Destroy();blocker?.Destroy();
        if(player.IsValid()) { player.WorldPosition=original;player.Body.Velocity=Vector3.Zero;player.UseInputControls=input; }
    }
}