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<object> RunTradePlanningFixtureProbeAsync(PlannerPage page,string directory,Func<string,Task> capture,Action<string,bool?,string> 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<TradePlanningProgress>();
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<TradePlanningProgress> samples):IProgress<TradePlanningProgress>
{public void Report(TradePlanningProgress value)=>samples.Enqueue(value);}
}
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<object> RunTradePlanningFixtureProbeAsync(PlannerPage page,string directory,Func<string,Task> capture,Action<string,bool?,string> 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<TradePlanningProgress>();
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<TradePlanningProgress> samples):IProgress<TradePlanningProgress>
{public void Report(TradePlanningProgress value)=>samples.Enqueue(value);}
}