using XFEExtension.NetCore.XUnit.Attributes; using XFEEDToolBox.Core.Data; using XFEEDToolBox.Core.Engineering; using XFEEDToolBox.Core.Models; using XFEEDToolBox.Core.Outfitting; using XFEEDToolBox.Core.Planning; using XFEEDToolBox.Core.Rendering; using XFEEDToolBox.Core.Session; namespace XFEEDToolBox.Test; [TestFixture] public class RefinementTests { private static readonly EngineerAccess Engineer=new(){Id=300260,Name="Tod 'The Blaster' McQuinn",Progress="Unlocked",Rank=4,ReputationMin=94.6,ReputationMax=94.6}; private static readonly FittedModule Module=new(){Slot="LargeHardpoint1",Item="hpt_multicannon_gimbal_large",Enabled=true,Engineering=new(){Blueprint="Weapon_Overcharged",Level=3,Quality=.65,ExperimentalEffectId="special_incendiary_rounds"}}; private static readonly RefitTarget Target=new("Overcharged Weapon",5,1,"special_incendiary_rounds",300260,false,true); private static PlayerState Player(FittedModule? module=null)=>new(){CommanderId="F1",CurrentShipId=7,Ships=[new(){ShipId=7,Type="anaconda",ConfigurationAt=DateTimeOffset.UtcNow,Modules=[module??Module]}],Engineers=[Engineer]}; [Test] public void GradeThreeSixtyFivePercentStartsAtActualProgressAndPromotesEngineer() { var effect=EngineeringCatalog.Default.Recipes.Single(r=>r.Type=="Multi-cannon"&&r.JournalName=="special_incendiary_rounds"); var plan=EngineeringPlanner.Calculate(EngineeringCatalog.Default,Module,Target.Blueprint,5,1,effect,Engineer,[]); Check.Equal(3,plan.Rolls[0].Grade);Check.Equal(.65,plan.Rolls[0].BeforeQuality); Check.That(plan.Rolls.All(r=>r.Grade>=3));Check.Equal(5,plan.Rolls[0].AfterRank); Check.Equal(10,plan.Rolls.Count);Check.That(plan.Complete); Check.That(!plan.Notes.Any(n=>n.Contains("更换蓝图"))); Check.That(!plan.Materials.Any(m=>m.Key=="phosphorus")); } [Test] public void LiveProgressRebasesStaleShipWhilePreservingTargetsAndOriginalSnapshot() { var stale=Module with{Engineering=Module.Engineering! with{Level=1,Quality=0}}; var doc=ShipRefit.Create(Player(stale).Ships.Single(),OutfittingCatalog.Default); doc=ShipRefit.SetTarget(doc,Module.Slot,Target); var current=ShipRefit.RefreshOwnedProgress(doc,Player()); Check.Equal(1,doc.Slots.Single(s=>s.Slot==Module.Slot).Module!.Engineering!.Level); var slot=current.Slots.Single(s=>s.Slot==Module.Slot); Check.Equal(3,slot.Module!.Engineering!.Level);Check.Equal(.65,slot.Module.Engineering.Quality!.Value);Check.Equal(Target,slot.Target!); Check.That(ReferenceEquals(current,ShipRefit.RefreshOwnedProgress(current,Player()))); var plan=ShipRefitPlanner.Calculate(current,EngineeringCatalog.Default,[Engineer],[]); Check.Equal(10,plan.Modules.Single().Rolls); } [Test] public void AddWholeShipUpdatesOwnedEntriesWithoutDuplicatingThemAndUsesLiveProgress() { var p=Player();var doc=ShipRefit.SetTarget(ShipRefit.Create(p.Ships.Single(),OutfittingCatalog.Default),Module.Slot,Target); var first=UpgradeListPlanner.AddShip([],doc);var second=UpgradeListPlanner.AddShip(first,doc); Check.Equal(1,second.Count);Check.Equal(first[0].Id,second[0].Id);Check.Equal("ship:7:LargeHardpoint1",second[0].SourceKey); var stale=second[0] with{Module=Module with{Engineering=null}}; var plan=UpgradeListPlanner.Calculate([stale],p,EngineeringCatalog.Default); Check.Equal(10,plan.Engineering.Modules.Single().Rolls); Check.Throws(()=>UpgradeListPlanner.AddShip([],ShipRefit.Create(p.Ships.Single(),OutfittingCatalog.Default))); } [Test] public void ModuleIconIndexUsesRealBoundedTexturesAndActualGrades() { Check.That(EliteModuleIcons.References.Count>100); Check.Equal("module_category_multicannon_icon",EliteModuleIcons.IconName(Module.Item)); Check.Equal("module_category_shieldgenerator_icon",EliteModuleIcons.IconName("int_shieldgenerator_size6_class5")); Check.That(EliteModuleIcons.References.All(r=>r.ResourceHash.Length==40&&r.SourceSha256.Length==64&&r.Width<=512&&r.Height<=512&&r.RowPitch>=r.Width/4*16)); var slot=new RefitSlot{Module=Module,Capacity=3,Target=Target};Check.Equal("3",slot.SlotSize);Check.Equal("G3",slot.CurrentGrade); Check.That(!slot.CardName.Contains("武器挂点")); } [Test] public void CommanderProfileUsesOwnRecordedVisitsDeduplicatesScansAndResetsOnAccountChange() { var p=new PlayerProjector(); p.Apply(Check.Json("""{"event":"LoadGame","timestamp":"2026-10-22T00:00:00Z","FID":"F1","Commander":"Test","Credits":42}""")); var scan=Check.Json("""{"event":"Scan","timestamp":"2026-10-22T00:00:03Z","SystemAddress":7,"BodyName":"Sol A","StarType":"G","WasDiscovered":false}"""); p.Apply(Check.Json("""{"event":"FSDJump","timestamp":"2026-10-22T00:00:02Z","SystemAddress":7,"StarSystem":"Sol","StarPos":[0,0,0],"JumpDist":4.5}"""));p.Apply(scan);p.Apply(scan); Check.Equal(1,p.State.Profile.VisitedSystems.Count);Check.Equal(1,p.State.Profile.ScannedBodies);Check.Equal(1,p.State.Profile.FirstDiscoveredBodies);Check.Equal("G",p.State.Profile.VisitedSystems[0].StarClass); p.Apply(Check.Json("""{"event":"Status","timestamp":"2026-10-22T00:00:04Z","Flags":16777224,"Balance":125}"""));Check.Equal(125m,p.State.Credits!.Value);Check.That(p.State.CreditsReliable&&p.State.Profile.Arx is null); p.Apply(Check.Json("""{"event":"LoadGame","timestamp":"2026-10-22T00:00:05Z","FID":"F2","Commander":"Other"}"""));Check.Equal(0,p.State.Profile.VisitedSystems.Count);Check.Equal(0,p.State.Profile.ScannedBodies); } } [TestFixture] public class ShieldRestartTests { private static readonly DateTimeOffset Now=DateTimeOffset.Parse("2026-10-22T00:01:00Z"); private static readonly GameSessionTarget Game=new(7,Now.AddMinutes(-1).UtcTicks,"game","launcher",true); private static PlayerState Player(bool up=true)=>new(){CommanderId="F1",CurrentShipId=7,InGame=true,LoadGameAt=Now.AddSeconds(-55),StatusAt=Now,Status=new((1L<<24)|(up?8:0),0,1,1,0,null,null,null,null,[]),Ships=[new(){ShipId=7,ConfigurationAt=Now,Modules=[new(){Item="int_shieldgenerator_size6_class5",Enabled=true}]}]}; private static ObservedJournalEvent Event(long sequence,bool up,DateTimeOffset? time=null)=>new(sequence,Now,Check.Event(new{timestamp=time??Now,@event="ShieldState",ShieldsUp=up})); private static ShieldRestartGuard Guard(bool prime=true){var guard=new ShieldRestartGuard();guard.SetEnabled(true,Now.AddSeconds(-1));if(prime)guard.Prime(Player(),Game);return guard;} [Test] public void LiveShieldFailureTriggersExactlyOnceAndNeedsRecoveryToRearm() { var g=Guard();Check.That(g.Armed);Check.That(g.Observe(Event(1,false),Player(false),Game));Check.That(!g.Observe(Event(2,false),Player(false),Game)); Check.That(!g.Observe(Event(3,true),Player(),Game));Check.That(g.Observe(Event(4,false),Player(false),Game)); } [Test] public void DisabledAlreadyDownHistoricalAndDuplicateEventsCannotTrigger() { var g=new ShieldRestartGuard();Check.That(!g.Observe(Event(1,false),Player(false),Game)); g=Guard(false);Check.That(!g.Observe(Event(1,false),Player(false),Game)); g=Guard();Check.That(!g.Observe(Event(1,false,Now.AddMinutes(-5)),Player(false),Game));Check.That(g.Armed); Check.That(g.Observe(Event(2,false),Player(false),Game));Check.That(!g.Observe(Event(2,false),Player(false),Game)); } [Test] public void DockedOnFootFighterShieldlessOtherProcessAndNewSessionCannotTrigger() { foreach(var p in new[]{Player(false) with{Docked=true},Player(false) with{Status=Player(false).Status! with{Flags2=1}},Player(false) with{Status=Player(false).Status! with{Flags=1L<<25}},Player(false) with{Ships=[]},Player(false) with{LoadGameAt=Now.AddSeconds(1)},Player(false) with{CommanderId="F2"},Player(false) with{CurrentShipId=8}}) Check.That(!Guard().Observe(Event(1,false),p,Game)); Check.That(!Guard().Observe(Event(1,false),Player(false),Game with{ProcessId=8})); Check.That(!new ToolboxSettings().ShieldZeroRestartEnabled); } }