VaultInterruptionReview.cs
using Sandbox;

namespace PaintballBaddies;

/// <summary>Opt-in native lifecycle checks; only install after the user's playtest.</summary>
public sealed class VaultInterruptionReview : Component
{
    [Property] public string CaptureId { get; set; } = "";
    private PlayerController player;
    private CoverController cover;
    private VaultController vault;
    private GameObject obstacle;
    private bool input, look, motion;
    private Vector3 position;
    private Angles angles;
    private float elapsed;
    private int stage;

    protected override void OnStart()
    {
        player=Scene.GetAllComponents<PlayerController>().First();
        cover=player.Components.Get<CoverController>();
        vault=player.Components.Get<VaultController>();
        input=player.UseInputControls;look=player.UseLookControls;motion=player.Body.MotionEnabled;
        position=player.WorldPosition;angles=player.EyeAngles;
        player.UseLookControls=false;
        cover.TestInput=true;cover.TestAim=false;cover.TestMovement=Vector3.Zero;
        obstacle=TrainingRange.Box(null,"Vault interruption review",new Vector3(-550,70,24),new Vector3(20,180,48),Color.Gray,true);
        obstacle.Components.Create<CoverSurface>();
        Place(true);
    }

    private void Place(bool acceptsInput)
    {
        if(vault.IsValid())vault.Cancel();
        cover.Leave();
        player.WorldPosition=new Vector3(-595,70,2);
        player.EyeAngles=Angles.Zero;
        player.Body.MotionEnabled=true;player.Body.Velocity=Vector3.Zero;
        player.WishVelocity=Vector3.Zero;player.UseInputControls=acceptsInput;
    }

    private void Check(string name,bool passed) => Log.Info("VAULT_INTERRUPTION "+Json.Serialize(new {capture_id=CaptureId,name,passed}));

    protected override void OnUpdate()
    {
        elapsed+=Time.Delta;
        // Allow the teleported body to settle, but interrupt well inside the .8s vault.
        var delay=stage is 0 or 3 or 6 ? .6f : .15f;
        if(elapsed<delay)return;
        elapsed=0;
        switch(stage++)
        {
            case 0:
                Check("explicit vault action starts traversal",!cover.Attached && vault.TryBegin() && vault.IsVaulting);
                Check("traversal owns physics and input",vault.IsVaulting && !player.UseInputControls && !player.Body.MotionEnabled);
                break;
            case 1:
                Check("disable happens during traversal",vault.IsVaulting && vault.Progress>0 && vault.Progress<1);
                vault.Enabled=false;
                break;
            case 2:
                Check("disable restores physics and input",!vault.IsVaulting && player.UseInputControls && player.Body.MotionEnabled);
                Check("disable clears stale movement",player.WishVelocity.Length<.01f);
                Check("disabled vault rejects activation",!vault.Available && !vault.TryBegin());
                vault.Enabled=true;Place(false);
                break;
            case 3:
                Check("starts with external input owner",cover.TryEnter() && vault.TryBegin());
                break;
            case 4:
                vault.Enabled=false;
                break;
            case 5:
                Check("preserves external input owner",!vault.IsVaulting && !player.UseInputControls && player.Body.MotionEnabled);
                vault.Enabled=true;Place(true);
                break;
            case 6:
                Check("starts before removal",cover.TryEnter() && vault.TryBegin());
                break;
            case 7:
                vault.Destroy();
                break;
            case 8:
                Check("removal restores physics and input",player.UseInputControls && player.Body.MotionEnabled && player.WishVelocity.Length<.01f);
                break;
        }
    }

    protected override void OnDestroy()
    {
        if(vault.IsValid()){vault.Cancel();vault.Enabled=true;}
        if(cover.IsValid()){cover.Leave();cover.TestInput=false;cover.TestMovement=Vector3.Zero;cover.TestAim=false;}
        if(obstacle.IsValid())obstacle.Destroy();
        if(!player.IsValid())return;
        player.UseInputControls=input;player.UseLookControls=look;
        player.WorldPosition=position;player.EyeAngles=angles;player.WishVelocity=Vector3.Zero;
        if(player.Body.IsValid()){player.Body.MotionEnabled=motion;player.Body.Velocity=Vector3.Zero;}
    }
}