using System.Collections.Concurrent; using XFEEDToolBox.Core.Execution; using XFEEDToolBox.Core.Models; using XFEEDToolBox.Core.Planning; using XFEEDToolBox.Views; namespace XFEEDToolBox; public partial class App { private static async Task RunTradePlanningFixtureProbeAsync(PlannerPage page,string directory,Func capture,Action check) { var isolated=Path.GetFullPath(Path.Combine(directory,"isolated-data")); if(!ViewModel.OfflinePreview||!Path.GetFullPath(Runtime.DataDirectory).Equals(isolated,StringComparison.OrdinalIgnoreCase)) return new{skipped=true,reason="Synthetic planning visuals are restricted to the explicit isolated offline profile."}; var player=ViewModel.CurrentPlayer;var advanced=ViewModel.TradeAdvancedEditing; var at=DateTimeOffset.UtcNow; Station Station(long id,string name,double x)=>new(){MarketId=id,Name=name,SystemName="Synthetic planner fixture",Position=new(x,0,0), Type="Coriolis",MaxLandingPad=LandingPad.Large,Security=SecurityLevel.High,DistanceToArrivalLs=100,UpdatedAt=at}; var origin=Station(990001,"Synthetic supply",0);var destination=Station(990002,"Synthetic CG delivery",5); CommodityQuote Quote(Station station,string key,long buy,long sell)=>new(){MarketId=station.MarketId,Commodity=key,DisplayName=key, BuyPrice=buy,SellPrice=sell,Supply=200,Demand=200,CollectedAt=at,Source=DataSource.Demonstration}; var book=new MarketBook([origin,destination],[Quote(origin,"uranium",100,100),Quote(destination,"uranium",200,200), Quote(origin,"coffee",200,200),Quote(destination,"coffee",100,100)]); var request=new PlannerRequest{StartMarketId=origin.MarketId,CurrentMarketId=origin.MarketId,CurrentPosition=new(0,0,0),Credits=100000,ReserveCredits=0,CargoCapacity=64,LadenJumpRangeLy=20, ActualShipType="Type9",ShipRequiredPad=LandingPad.Large,RequiredPad=LandingPad.Large,AllowedSourceKinds=StationSourceKinds.AllKnown, MaxLegs=2,Loop=true,AsOf=at,CostPerJump=0,CostPerMinute=0}; var samples=new ConcurrentQueue(); var result=await Task.Run(()=>new TradeRoutePlanner().CalculateCandidatesWithDiagnostics(book,request,3,progress:new PlannerFixtureProgress(samples))); if(result.Candidates.Count==0)throw new InvalidOperationException("The isolated synthetic planning fixture did not produce a feasible route."); var goal=new CommunityGoalInfo(990001,"Synthetic CG activity fixture","tradelist","trade",destination.MarketId,destination.SystemName,destination.Name, at.AddHours(1),true,false,null){AcceptedCommodities=["uranium"]}; var observed=samples.LastOrDefault(value=>value.Total is >0)??throw new InvalidOperationException("The synthetic calculation supplied no measurable progress sample."); var unknown=new TradePlanningProgress("总量未知的读库状态",0,null,"显式测试总量未知时的不定进度样式"); object? knownLayout=null;object? unknownLayout=null;object? kinds=null;object? cg=null; using(ViewModel.EnterTradePlanningFixtureForProbe(result,[goal],observed,isolated)) { try { ViewModel.TradeAdvancedEditing=false;page.UpdateLayout();await Task.Delay(80); var candidate=ViewModel.TradeCandidates.FirstOrDefault(value=>value.IsCommunityGoal); var list=(ListView)page.FindName("TradeRouteCandidates"); if(candidate is not null){list.ScrollIntoView(candidate);page.UpdateLayout();await Task.Delay(100);((FrameworkElement)list.ContainerFromItem(candidate)).StartBringIntoView(new BringIntoViewOptions{AnimationDesired=false});await Task.Delay(100);} await capture("trade-planner-synthetic-cg"); check("isolated CG badge and dedicated colour",candidate is not null&&candidate.CommunityGoalVisibility==Visibility.Visible&&candidate.CommunityGoalLabel.Contains("合成夹具") &&candidate.RouteAccentBrush is Microsoft.UI.Xaml.Media.SolidColorBrush{Color.R:195,Color.G:166,Color.B:255}, "Explicit synthetic route/CG only; the production template receives a purple badge with text. Extra CG rewards are absent from NetProfit and Cr/h."); cg=ViewModel.TradeCandidates.Select(value=>new{value.IsCommunityGoal,value.CommunityGoalLabel,value.CommunityGoalDescription,value.Plan.NetProfit,value.Plan.CreditsPerHour}).ToArray(); var panel=(FrameworkElement)page.FindName("TradePlanningProgressPanel");var bar=(ProgressBar)page.FindName("TradePlanningProgressBar"); panel.StartBringIntoView(new BringIntoViewOptions{AnimationDesired=false});page.UpdateLayout();await Task.Delay(80); knownLayout=new{observed.Stage,observed.Processed,observed.Total,bar.Value,bar.IsIndeterminate,panel.ActualWidth,panel.ActualHeight}; await capture("trade-planner-synthetic-progress-known"); check("isolated progress uses observed processed and total",!bar.IsIndeterminate&&observed.Total>0&&Math.Abs(bar.Value-Math.Clamp(observed.Processed*100d/observed.Total.Value,0,100))<.001, "Determinate visual replays an actual Core progress sample from the synthetic book, not an elapsed-time percentage."); ViewModel.ReplayTradePlanningProgressForProbe(unknown);page.UpdateLayout();await Task.Delay(80); unknownLayout=new{ViewModel.TradeProgressStage,ViewModel.TradeProgressProcessed,ViewModel.TradeProgressTotal,bar.IsIndeterminate}; await capture("trade-planner-synthetic-progress-unknown"); check("isolated unknown total is indeterminate",bar.IsIndeterminate&&ViewModel.TradeProgressTotal is null,"Unknown-total style is an explicit component-state fixture; no fabricated percentage is displayed."); ViewModel.TradeAdvancedEditing=true;page.UpdateLayout();await Task.Delay(80); var sources=(FrameworkElement)page.FindName("TradeSourceKindsPanel");sources.StartBringIntoView(new BringIntoViewOptions{AnimationDesired=false});await Task.Delay(100); kinds=ViewModel.TradeSourceKinds.Select(value=>new{value.Label,value.Kind,value.IsSelected}).ToArray(); await capture("trade-planner-synthetic-source-kinds"); check("isolated source kinds support multiple choices",ViewModel.TradeSourceKinds.Count==9&&ViewModel.TradeSourceKinds.Count(value=>value.IsSelected)==2 &&ViewModel.SelectedTradeSourceKinds==(StationSourceKinds.Starport|StationSourceKinds.FleetCarrier),"Nine explicit options; starport and carrier selected together, unknown remains separate. Destination eligibility is independent."); var distance=ViewModel.MaxDistance;var version=ViewModel.TradePlanningInputVersionForProbe; try { ViewModel.MaxDistance=distance+1; check("isolated advanced edits invalidate old candidates",ViewModel.TradeCandidates.Count==0&&ViewModel.Plan is null&&ViewModel.TradePlanningInputVersionForProbe>version, "Changing an actual advanced input clears the synthetic candidate collection and changes its input generation."); version=ViewModel.TradePlanningInputVersionForProbe;ViewModel.MaxDistance=distance+2; check("isolated in-flight input changes remain observable with no candidates",ViewModel.TradePlanningInputVersionForProbe>version, "The generation changes even after candidates are empty, so a running calculation cannot publish results for the old inputs."); } finally{ViewModel.MaxDistance=distance;} } finally{ViewModel.TradeAdvancedEditing=advanced;} } check("isolated planning progress is cleared",!ViewModel.TradePlanningBusy&&ViewModel.TradeProgressVisibility==Visibility.Collapsed&&ViewModel.TradeProgressTotal is null,"Fixture cleanup uses the same completion reset and invalidates late operation updates."); check("isolated planning leaves actual player unchanged",ViewModel.CurrentPlayer==player&&!ViewModel.RunActive,"Only an isolated UI route collection was replaced temporarily; the actual player, wallet, cargo and session were never substituted."); return new{synthetic=true,originalPlayerUnchanged=ViewModel.CurrentPlayer==player,knownLayout,unknownLayout,kinds,cg, actualSyntheticCalculationProgress=samples.ToArray(),nativeReadsSent=0,gameInputsSent=0,tradeOrdersSubmitted=0,settingWrites=0}; } private sealed class PlannerFixtureProgress(ConcurrentQueue samples):IProgress {public void Report(TradePlanningProgress value)=>samples.Enqueue(value);} }