XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
using XFEEDToolBox.Core.Data;
using XFEEDToolBox.Core.Models;
using XFEEDToolBox.Core.Session;
using XFEExtension.NetCore.XUnit.Attributes;

namespace XFEEDToolBox.Test;

[TestFixture]
public class ShipProtectionTests
{
    private static readonly DateTimeOffset Now=DateTimeOffset.Parse("2026-10-22T00:10:00Z");
    private static readonly GameSessionTarget Target=new(12,Now.AddMinutes(-2).UtcTicks,"game","launcher",true);
    private static PlayerState Player(double health=1,bool shield=true)=>new(){CommanderId="F1",CurrentShipId=7,InGame=true,LoadGameAt=Now.AddMinutes(-1),StatusAt=Now,
        Status=new((1L<<24)|(shield?8:0),0,1,1,0,null,null,null,null,[]),Ships=[new(){ShipId=7,HullHealth=health,HullHealthAt=Now.AddSeconds(-2),ConfigurationAt=Now.AddSeconds(-2),Modules=[new(){Item="int_shieldgenerator_size6_class5",Enabled=true}]}]};
    private static ShieldRestartGuard Guard(PlayerState? player=null)
    {var guard=new ShieldRestartGuard();guard.SetEnabled(true,Now.AddSeconds(-1));guard.Prime(player??Player(),Target);return guard;}
    private static ObservedJournalEvent Hull(long sequence,double health,bool pilot=true,bool fighter=false,DateTimeOffset? at=null)
        =>new(sequence,Now,Check.Event(new{timestamp=at??Now,@event="HullDamage",Health=health,PlayerPilot=pilot,Fighter=fighter}));
    private static ShipHealthSample Sample(long sequence,double hull,double? shield,bool damage=false)=>new(sequence,Now,Target.ProcessId,Target.StartedUtcTicks,100,7,hull,shield,damage);

    [Test] public void HullDamageBelowHalfTriggersOnceAndFiftyPercentDoesNot()
    {
        var guard=Guard();Check.That(!guard.Observe(Hull(1,.5),Player(),Target));
        Check.That(guard.Observe(Hull(2,.499),Player(),Target));Check.Equal(ShipProtectionReason.HullBelowHalf,guard.LastTrigger!.Reason);
        Check.That(guard.TestingBlocked);Check.That(!guard.Observe(Hull(3,.4),Player(),Target));
    }
    [Test] public void FirstLoadBelowHalfIsAHistoricalBaselineRatherThanAPermanentTestingBlock()
    {
        var player=Player(.4,false);var guard=Guard(player);Check.That(!guard.TestingBlocked);
        Check.That(!guard.Observe(new(1,Now,Check.Event(new{timestamp=Now,@event="Status",Flags=1L<<24})),player,Target));
        Check.That(!guard.ObserveHealth(Sample(1,.4,0),player,Target,Now));
        Check.That(!guard.ObserveHealth(Sample(2,.4,0),player,Target,Now));
        guard.SetEnabled(true,Now.AddSeconds(-1));guard.Prime(player,Target);
        Check.That(!guard.TestingBlocked);Check.That(!guard.ObserveHealth(Sample(3,.4,0),player,Target,Now));
    }
    [Test] public void NativeDamageCrossingThresholdUsesContinuousHealthAndTriggersWithoutJournalStep()
    {
        var guard=Guard();Check.That(!guard.ObserveHealth(Sample(1,.501,1),Player(),Target,Now));
        Check.That(guard.ObserveHealth(Sample(2,.499,.9),Player(),Target,Now));Check.That(guard.TestingBlocked);
    }
    [Test] public void DockedBaselineStillProtectsTheFirstDamageFrameAfterLaunch()
    {
        var flying=Player();var docked=flying with{Docked=true,Status=flying.Status! with{Flags=flying.Status.Flags|1}};var guard=Guard(docked);
        Check.That(!guard.ObserveHealth(Sample(1,1,1),docked,Target,Now));
        Check.That(guard.ObserveHealth(Sample(2,1,0),flying,Target,Now));Check.Equal(ShipProtectionReason.ShieldsLost,guard.LastTrigger!.Reason);
        var noShield=Player(1,false) with{Docked=true,Status=docked.Status! with{Flags=docked.Status.Flags&~8L}};guard=Guard(noShield);
        Check.That(!guard.ObserveHealth(Sample(1,1,0),noShield,Target,Now));Check.That(!guard.ObserveHealth(Sample(2,1,0),Player(1,false),Target,Now));
    }
    [Test] public void InitialZeroShieldDoesNotTriggerButFurtherHullDamageDoes()
    {
        var guard=Guard(Player(1,false));Check.That(!guard.ObserveHealth(Sample(1,1,0),Player(1,false),Target,Now));
        Check.That(!guard.ObserveHealth(Sample(2,1,0),Player(1,false),Target,Now));
        Check.That(guard.ObserveHealth(Sample(3,.99,0),Player(1,false),Target,Now));Check.Equal(ShipProtectionReason.ShieldsLost,guard.LastTrigger!.Reason);
    }
    [Test] public void LostShieldUsesDamageEdgeAndIgnoresRepeatedZeroSamples()
    {
        var guard=Guard();Check.That(!guard.ObserveHealth(Sample(1,1,.01),Player(),Target,Now));
        Check.That(guard.ObserveHealth(Sample(2,1,0),Player(1,false),Target,Now));
        Check.That(!guard.ObserveHealth(Sample(3,1,0),Player(1,false),Target,Now));
    }
    [Test] public void ShipOrProcessChangeCannotTurnBaselineIntoDamage()
    {
        var guard=Guard();Check.That(!guard.ObserveHealth(Sample(1,1,1),Player(),Target,Now));
        Check.That(!guard.ObserveHealth(Sample(2,.1,0) with{EntityId=200},Player(),Target,Now));
        Check.That(!guard.ObserveHealth(Sample(3,.09,0) with{ProcessId=99},Player(),Target,Now));
        Check.That(!guard.ObserveHealth(Sample(3,.09,0) with{ProcessStartedUtcTicks=Target.StartedUtcTicks+1},Player(),Target,Now));
        Check.That(!guard.ObserveHealth(Sample(3,.09,0) with{ShipId=99},Player(),Target,Now));
    }
    [Test] public void DuplicateHistoricalInvalidAndFighterDamageAreRejected()
    {
        foreach(var e in new[]{Hull(1,.1,false),Hull(1,.1,true,true),Hull(1,.1,at:Now.AddMinutes(-1)),Hull(1,-1),Hull(1,2)})
            Check.That(!Guard().Observe(e,Player(),Target));
        var guard=Guard();Check.That(!guard.ObserveHealth(Sample(1,1,1),Player(),Target,Now));
        foreach(var s in new[]{Sample(1,.1,0),Sample(2,.1,0) with{At=Now.AddSeconds(-1)},Sample(2,.1,0) with{At=Now.AddMilliseconds(1)},Sample(2,double.NaN,0),Sample(2,.1,double.NaN)})
            Check.That(!guard.ObserveHealth(s,Player(),Target,Now));
    }
    [Test] public void HistoricalLowHullAndReprimingDoNotCreateADamageEpisode()
    {
        var guard=Guard(Player(.4,false));Check.That(!guard.TestingBlocked);
        guard.Prime(Player(.4,false),Target);Check.That(!guard.TestingBlocked);
        guard.Prime(Player(.9) with{Ships=[Player(.9).Ships[0] with{HullHealthAt=Now}]},Target);Check.That(!guard.TestingBlocked);
    }
    [Test]public void FreshStableDamageObservationsReleaseTheBlockAfterThreeSecondsWithoutRepairingOrRestarting()
    {
        var player=Player(.4,false);var guard=Guard(player);
        Check.That(!guard.ObserveHealth(Sample(1,.4,0),player,Target,Now));
        var damaged=Now.AddMilliseconds(100);
        Check.That(guard.ObserveHealth(Sample(2,.39,0) with{At=damaged},player,Target,damaged));Check.That(guard.TestingBlocked);
        for(var tick=1;tick<=30;tick++)
        {
            var at=damaged.AddMilliseconds(tick*100);
            Check.That(!guard.ObserveHealth(Sample(tick+2,.39,0) with{At=at},player,Target,at));
            Check.Equal(tick<30,guard.TestingBlocked);
        }
        var later=damaged.AddSeconds(3.1);
        Check.That(guard.ObserveHealth(Sample(33,.38,0) with{At=later},player,Target,later));Check.That(guard.TestingBlocked);
    }
    [Test]public void DuplicateStaleAndInterruptedHealthCannotCountAsThreeStableSeconds()
    {
        var player=Player(.4,false);var guard=Guard(player);guard.ObserveHealth(Sample(1,.4,0),player,Target,Now);
        guard.ObserveHealth(Sample(2,.39,0),player,Target,Now);Check.That(guard.TestingBlocked);
        var later=Now.AddSeconds(4);
        Check.That(!guard.ObserveHealth(Sample(2,.39,0),player,Target,later));Check.That(guard.TestingBlocked);
        guard.ObserveHealth(Sample(3,.39,0) with{At=later},player,Target,later);Check.That(guard.TestingBlocked);
        for(var tick=1;tick<=29;tick++)
        {var at=later.AddMilliseconds(tick*100);guard.ObserveHealth(Sample(tick+3,.39,0) with{At=at},player,Target,at);Check.That(guard.TestingBlocked);}
        var finished=later.AddSeconds(3);guard.ObserveHealth(Sample(33,.39,0) with{At=finished},player,Target,finished);Check.That(!guard.TestingBlocked);
        guard.ObserveHealth(Sample(34,0,0) with{At=finished.AddMilliseconds(100)},player,Target,finished.AddMilliseconds(100));Check.That(guard.TestingBlocked);
    }
    [Test]public void ANewEntityOrSessionStartsANewBaselineWithoutRetainingThePreviousDamageLatch()
    {
        var player=Player(.4,false);var guard=Guard(player);guard.ObserveHealth(Sample(1,.4,0),player,Target,Now);
        guard.ObserveHealth(Sample(2,.39,0),player,Target,Now);Check.That(guard.TestingBlocked);
        Check.That(!guard.ObserveHealth(Sample(3,.39,0) with{EntityId=200},player,Target,Now));Check.That(!guard.TestingBlocked);
        guard.ObserveHealth(Sample(4,.38,0) with{EntityId=200},player,Target,Now);Check.That(guard.TestingBlocked);
        guard.Prime(player with{LoadGameAt=Now.AddSeconds(-2)},Target);Check.That(!guard.TestingBlocked);
    }
    [Test] public void LiveDamageProjectionTracksMainShipWithoutChangingEquipmentSnapshot()
    {
        var projector=new PlayerProjector();projector.Apply(Check.Event(new{timestamp=Now,@event="Loadout",ShipID=7,Ship="anaconda",HullHealth=.9,Modules=new object[0]}));
        var config=projector.State.Ships[0].ConfigurationAt;
        projector.Apply(Hull(1,.4).Data);Check.Equal(.4,projector.State.Ships[0].HullHealth);Check.Equal(config,projector.State.Ships[0].ConfigurationAt);
        projector.Apply(Hull(2,.1,true,true).Data);Check.Equal(.4,projector.State.Ships[0].HullHealth);
    }
}