using System.Diagnostics;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using XFEEDToolBox.Core.Data;
using XFEEDToolBox.Core.Execution;
using XFEEDToolBox.Core.Models;
using XFEEDToolBox.Core.Native;
using XFEEDToolBox.Core.Navigation;
internal static class CgCargoJourney
{
// Explicit current CG route. Cached prices are ceilings/floors only; the
// driver obtains original prices and stock immediately before each order.
internal static async Task RunAsync(int pid,string directory,CancellationToken externalToken=default,TradeOrder? requestedOrder=null)
{
directory=Path.GetFullPath(directory);Directory.CreateDirectory(directory);
using var runLease=new FileStream(Path.Combine(directory,"runner.lock"),FileMode.OpenOrCreate,FileAccess.ReadWrite,FileShare.None);
var settings=ToolboxSettings.Load(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),"XFEstudio","XFEEDToolBox","settings.json"));
await using var journal=new JournalMonitor(settings.JournalDirectory);await journal.StartAsync();var initial=journal.State;
if(!initial.InGame||initial.CurrentShipId!=5||string.IsNullOrWhiteSpace(initial.CommanderId)||initial.CargoCapacity!=192)
throw new InvalidOperationException("Actual Corsair/5 with the verified 192t hold is required.");
var workRoot=Directory.GetParent(directory)!.FullName;
var useNativeCruise=true;var navigationPolicy=Path.Combine(workRoot,"navigation-policy.json");
if(File.Exists(navigationPolicy))
{using var p=JsonDocument.Parse(await File.ReadAllTextAsync(navigationPolicy));useNativeCruise=p.RootElement.GetProperty("useNativeCruise").GetBoolean();}
var lossHold=Path.Combine(workRoot,"ship-loss-hold.json");
if(File.Exists(lossHold)){using var h=JsonDocument.Parse(await File.ReadAllTextAsync(lossHold));if(h.RootElement.GetProperty("active").GetBoolean())throw new InvalidOperationException("Ship loss hold is active; no CG input is permitted.");}
using(var acceptance=JsonDocument.Parse(await File.ReadAllTextAsync(Path.Combine(workRoot,"one-ton-journey","result.json"))))
if(!acceptance.RootElement.GetProperty("passed").GetBoolean())throw new InvalidOperationException("The original 1t journey must pass before a CG batch.");
var accepted=TradeJourneyCheckpoints.Load(Path.Combine(workRoot,"one-ton-journey","journey.json"));
if(accepted.Commander!=initial.CommanderId||accepted.ShipId!=5||accepted.Sold is not {QuantityDelta:-1,Commodity:"biowaste"})
throw new InvalidDataException("Original completed 1t sale receipt required before CG production.");
var membershipPath=Path.Combine(Directory.GetParent(directory)!.FullName,"cg-membership-864","membership-latest.json");
long contributionBefore;
using(var membership=JsonDocument.Parse(await File.ReadAllTextAsync(membershipPath)))
{
var root=membership.RootElement;var body=root.GetProperty("Body").GetString()!;
var confirmedGoal=CommunityGoalCatalogue.RequireTrade(CommunityGoalCatalogue.Parse(body),DateTimeOffset.UtcNow,true);
if(root.GetProperty("Proof").GetString()!=Convert.ToHexStringLower(SHA256.HashData(Encoding.UTF8.GetBytes(body)))
||confirmedGoal.Id!=864)
throw new InvalidDataException("Original active CG 864 membership evidence required before carrying cargo.");
contributionBefore=confirmedGoal.Contribution??0;
}
var planPath=Path.Combine(directory,"plan.json");
var plannedOrder=requestedOrder??new TradeOrder("uranium","Uranium",192,7241,57104);
if(File.Exists(planPath))
{
using var saved=JsonDocument.Parse(await File.ReadAllTextAsync(planPath));var plan=saved.RootElement;
if(plan.GetProperty("goalId").GetUInt64()!=864||plan.GetProperty("sourceMarket").GetInt64()!=3228792064)
throw new InvalidDataException("Persisted CG route differs from this cargo journey.");
plannedOrder=new(plan.GetProperty("commodity").GetString()!,plan.TryGetProperty("label",out var label)?label.GetString()!:"Uranium",plan.GetProperty("quantity").GetInt32(),
plan.GetProperty("buyCeiling").GetInt64(),plan.GetProperty("sellFloor").GetInt64());
contributionBefore=plan.GetProperty("contributionBefore").GetInt64();
}
else
{
await using var file=new FileStream(planPath,FileMode.CreateNew,FileAccess.Write,FileShare.Read);
await JsonSerializer.SerializeAsync(file,new{goalId=864UL,sourceMarket=3228792064L,quantity=plannedOrder.Quantity,commodity=plannedOrder.Commodity,label=plannedOrder.Name,
buyCeiling=plannedOrder.BuyPrice,sellFloor=plannedOrder.SellPrice,contributionBefore});
}
var checkpointPath=Path.Combine(directory,"journey.json");
var checkpoint=File.Exists(checkpointPath)?TradeJourneyCheckpoints.Load(checkpointPath):new(initial.CommanderId,5,DateTimeOffset.UtcNow);
if(checkpoint.Commander!=initial.CommanderId||checkpoint.ShipId!=5)throw new InvalidDataException("CG journey belongs to another ship or commander.");
if(!NativeTradeBatchPolicy.Allows(plannedOrder.Commodity)||plannedOrder.Quantity is <1 or >192||plannedOrder.BuyPrice is <=0 or >100000||plannedOrder.SellPrice<=plannedOrder.BuyPrice)
throw new InvalidDataException("Persisted CG batch exceeds the original interface limits.");
if(checkpoint.Bought is {} reserved&&(reserved.Commodity!=plannedOrder.Commodity||reserved.QuantityDelta!=plannedOrder.Quantity||reserved.MarketId!=3228792064))
throw new InvalidDataException("CG journey has a different reserved cargo; no order was submitted.");
var attempt=Guid.NewGuid().ToString("N");
var guardian=PersistentProtectionProcess.Current??throw new InvalidOperationException("CG trading requires independently owned protection.");
guardian.RequireReady(pid);
using var stop=CancellationTokenSource.CreateLinkedTokenSource(externalToken);stop.CancelAfter(TimeSpan.FromMinutes(60));
using var sentinel=new Timer(_=>{if(File.Exists(Path.Combine(directory,"stop"))||!guardian.IsAlive)stop.Cancel();},null,100,100);
using var ledger=new TradeProfitLedger(Path.Combine(directory,"ledger"));var channel=new NativeCommerceChannel(pid);
NativeWalletObservation? wallet=null;PlayerState Player()=>wallet?.Project(journal.State)??journal.State;
async Task Check(CancellationToken token)
{
token.ThrowIfCancellationRequested();var current=Player();
if(!current.InGame||current.CurrentShipId!=5||current.CommanderId!=initial.CommanderId||current.LoadGameAt!=initial.LoadGameAt
||NavigationSafety.UnhealthyHull(current)||NavigationSafety.HasExternalThreat(current)||!guardian.IsAlive)
throw new InvalidOperationException("CG session, ship or continuous protection changed.");
var hold=Path.Combine(Directory.GetParent(directory)!.FullName,"maintenance-hold.json");
if(current.Docked&&File.Exists(hold))
{
using var maintenance=JsonDocument.Parse(await File.ReadAllTextAsync(hold,token));
if(maintenance.RootElement.GetProperty("active").GetBoolean())throw new InvalidOperationException("Maintenance hold requires staying docked.");
}
JsonElement health;var readyUntil=DateTimeOffset.UtcNow.AddSeconds(15);
while(true)
{
token.ThrowIfCancellationRequested();current=Player();
if(!current.InGame||current.CurrentShipId!=5||current.CommanderId!=initial.CommanderId||current.LoadGameAt!=initial.LoadGameAt
||NavigationSafety.UnhealthyHull(current)||NavigationSafety.HasExternalThreat(current)||!guardian.IsAlive)
throw new InvalidOperationException("CG ship or protection changed while waiting for post-transition telemetry.");
try{guardian.RequireReady(pid);health=await channel.CallAsync(new{op="health.read"},token);break;}
catch(Exception error)when((NavigationRecovery.CanRetry(error,token)||error is InvalidOperationException&&error.Message=="Native main-ship health unavailable or stale")&&DateTimeOffset.UtcNow<readyUntil)
{
// Journal can confirm a real jump before the new main-ship
// sampler publishes its first verified frame. No flight or
// trade input is sent until that original frame is ready.
await Task.Delay(80,token);
}
}
if(!health.GetProperty("localPlayerShipVerified").GetBoolean()||!health.GetProperty("shipHealth").GetProperty("verified").GetBoolean()
||health.GetProperty("shipHealth").GetProperty("hullFraction").GetDouble()<.5)
throw new InvalidOperationException("Fresh independent main-ship health required.");
}
async Task Travel(Station target,Func<CancellationToken,Task> beforeInput,CancellationToken token)
{
await beforeInput(token);if(Player().Docked&&Player().MarketId==target.MarketId)return;
if(Player().Docked)
{
using var source=new NativeCurrentSystemSource(pid,NativeSystemLocationReader.GameSha256,()=>journal.State);
var catalogue=await source.ReadLocationsAsync(token);
var departureTarget=catalogue.Locations.FirstOrDefault(x=>x.Category==SystemLocationCategory.Station&&x.MarketId is >0&&x.MarketId!=Player().MarketId)
??throw new InvalidOperationException("A verified alternate local target is required for native departure.");
Console.WriteLine("原始自动起飞:"+Player().StationName);
await LocalDepartureAcceptance.RunAsync(pid,departureTarget.Name,Path.Combine(directory,"departure-"+Guid.NewGuid().ToString("N")+".json"),token,stopAfterDeparture:false);
}
await Check(token);
if(!Player().SystemName.Equals(target.SystemName,StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine("游戏原始航线:"+target.SystemName);
await InterstellarTravelAcceptance.RunAsync(pid,target.SystemName,Path.Combine(directory,"interstellar-"+Guid.NewGuid().ToString("N")+".json"),token);
}
await Check(token);Console.WriteLine("原始超巡、空管与自动降落:"+target.Name);
await LocalNavigationAcceptance.RunAsync(pid,target.Name,Path.Combine(directory,"arrival-"+Guid.NewGuid().ToString("N")+".json"),
requireCruise:true,useNativeCruise:useNativeCruise,allowObscuration:true,externalToken:token,keepHandoffScene:true);
var dockObservedUntil=DateTimeOffset.UtcNow.AddSeconds(5);
while(!Player().Docked||Player().MarketId!=target.MarketId)
{
token.ThrowIfCancellationRequested();guardian.RequireReady(pid);
if(Player().Docked||DateTimeOffset.UtcNow>=dockObservedUntil)break;
await Task.Delay(50,token);
}
if(!Player().Docked||Player().MarketId!=target.MarketId)throw new InvalidOperationException("Original docking differs from the CG route.");
}
var from=new Station{MarketId=3228792064,Name="Polyakov Station",SystemName="Duamta"};
var to=new Station{MarketId=CommunityGoalCatalogue.HomerMarket,Name="Homer Platform",SystemName="AD Leonis"};
var order=plannedOrder;var leg=new TradeLeg{Number=1,From=from,To=to,Orders=[order]};
var policy=new NativeTradeBatchPolicy(192,Math.Max(initial.Rebuy,4427098));
var driver=new NativeTradeCommandDriver(channel,Player,Path.Combine(directory,"operations"),Travel,Check,batchPolicy:policy);
driver.WalletObserved+=value=>wallet=value;
driver.ServiceConfirmed+=receipt=>ledger.Record(receipt.ToCashEntry());driver.Status+=Console.WriteLine;
string? failure=null;Task? services=null;var started=DateTimeOffset.UtcNow;long? openingCredits=null,closingCredits=null;
var continuousArrivalAccepted=false;var walletReconciled=false;var openingPath=Path.Combine(directory,"opening-wallet.json");
try
{
guardian.RequireReady(pid);await driver.PreflightAsync(stop.Token);await Check(stop.Token);
if(File.Exists(openingPath))
{
using var saved=JsonDocument.Parse(await File.ReadAllTextAsync(openingPath,stop.Token));openingCredits=saved.RootElement.GetProperty("credits").GetInt64();
}
else
{
if(checkpoint.Bought is null)openingCredits=wallet?.Credits??throw new InvalidDataException("Original opening wallet not observed.");
else
{
// Upgrade an existing in-flight checkpoint using its first
// actual confirmed debit, never a derived or assumed balance.
var entries=Directory.EnumerateFiles(Path.Combine(directory,"ledger"),"*.entry.json").Select(path=>
{
using var stored=JsonDocument.Parse(File.ReadAllText(path));var root=stored.RootElement;
return (Sequence:root.GetProperty("Sequence").GetInt64(),Key:root.GetProperty("Entry").GetProperty("OperationKey").GetString()!);
}).OrderBy(entry=>entry.Sequence).ToArray();
if(entries.Length==0)throw new InvalidDataException("Reserved CG cargo is missing its accounting history.");
using var original=JsonDocument.Parse(await File.ReadAllTextAsync(Path.Combine(directory,"operations",entries[0].Key+".confirmed.json"),stop.Token));
var receipt=original.RootElement.GetProperty("receipt");
if(!receipt.GetProperty("serverConfirmed").GetBoolean()||!receipt.GetProperty("clientApplied").GetBoolean())throw new InvalidDataException("Opening CG debit is not confirmed.");
openingCredits=receipt.GetProperty("creditsBefore").GetInt64();
}
await using var file=new FileStream(openingPath,FileMode.CreateNew,FileAccess.Write,FileShare.Read);
await JsonSerializer.SerializeAsync(file,new{credits=openingCredits,at=DateTimeOffset.UtcNow},cancellationToken:stop.Token);
}
TradeJourneyCheckpoints.Save(checkpointPath,checkpoint);
if(checkpoint.Bought is null)
{
if(!Player().Docked||Player().MarketId!=from.MarketId||Player().CargoCount!=0)
throw new InvalidOperationException("First CG batch requires the confirmed empty hold at Polyakov Station.");
Console.WriteLine($"采购 {order.Quantity}t {order.Name},并完成本站支持的维护。");
services=driver.ServiceDockedAsync(new(from.MarketId,order.Quantity),Check,stop.Token);
var action=new TradeAction(TradeActionKind.Buy,leg,order);
var receipt=await driver.ExecuteTradeAsync(action,Check,stop.Token);receipt.Validate(action,Player(),started);
ledger.RecordTrade(receipt);checkpoint=checkpoint with{Bought=receipt};TradeJourneyCheckpoints.Save(checkpointPath,checkpoint);
Console.WriteLine(JsonSerializer.Serialize(new{bought=receipt.QuantityDelta,receipt.UnitPrice,receipt.CreditsAfter}));
await services;services=null;
}
else ledger.RecordTrade(checkpoint.Bought);
if(checkpoint.Sold is null)
{
await Travel(to,Check,stop.Token);await Check(stop.Token);
Console.WriteLine("Homer 原始卖出与加油维修补给。");
services=driver.ServiceDockedAsync(new(to.MarketId,order.Quantity),Check,stop.Token);
var action=new TradeAction(TradeActionKind.Sell,leg,order);
var receipt=await driver.ExecuteTradeAsync(action,Check,stop.Token);receipt.Validate(action,Player(),started);
ledger.RecordTrade(receipt);checkpoint=checkpoint with{Sold=receipt};TradeJourneyCheckpoints.Save(checkpointPath,checkpoint);
await services;services=null;
}
else
{
ledger.RecordTrade(checkpoint.Sold);
// A previous process can have saved the sale before its
// concurrent service task finished. Reconfirm services before
// accepting the completed journey; never purchase again.
await driver.ServiceDockedAsync(new(to.MarketId,order.Quantity),Check,stop.Token);
}
if(!Player().Docked||Player().MarketId!=to.MarketId||Player().CargoCount!=0)throw new InvalidOperationException("CG final docking or empty hold not confirmed.");
var membership=await new NativeCommunityGoalClient(channel).ReadAsync(true,stop.Token);
var goal=CommunityGoalCatalogue.RequireTrade(membership.Goals,DateTimeOffset.UtcNow,true);
await File.WriteAllTextAsync(Path.Combine(directory,"membership-after.json"),JsonSerializer.Serialize(membership),stop.Token);
if(goal.Id!=864||goal.Contribution is null||goal.Contribution<contributionBefore+order.Quantity)
throw new InvalidOperationException("Original CG contribution has not confirmed this batch's delivered increment.");
var inventory=await channel.CallAsync(new{op="commerce.inventory"},stop.Token);closingCredits=inventory.GetProperty("credits").GetInt64();
if(inventory.GetProperty("count").GetInt32()!=0||openingCredits+ledger.Snapshot.CashChange!=closingCredits)
throw new InvalidDataException("Final original wallet does not reconcile with this journey's confirmed receipts.");
walletReconciled=true;
// The next batch must measure its own increment from this newly
// confirmed contribution, rather than reusing the original signup's
// zero baseline. An existing journey still keeps its saved plan.
var membershipTemporary=membershipPath+"."+Guid.NewGuid().ToString("N")+".tmp";
try
{
await File.WriteAllTextAsync(membershipTemporary,JsonSerializer.Serialize(membership),stop.Token);
File.Move(membershipTemporary,membershipPath,true);
}
finally{if(File.Exists(membershipTemporary))File.Delete(membershipTemporary);}
continuousArrivalAccepted=Directory.EnumerateFiles(directory,"arrival-*.json").Any(path=>
{
using var report=JsonDocument.Parse(File.ReadAllText(path));var value=report.RootElement;
bool True(string name)=>value.TryGetProperty(name,out var field)&&field.ValueKind==JsonValueKind.True;
// Provenance diagnostics also end in .json but are not arrival
// reports. Software cruise proves its original safe-drop call
// instead of claiming the disabled game assist engaged.
var cruise=True("cruiseEngaged")||value.TryGetProperty("useNativeCruise",out var native)&&native.ValueKind==JsonValueKind.False
&&True("cruiseObserved")&&True("safeDropRequested");
return True("passed")&&cruise&&True("dockingGranted")&&True("dockingEngaged")&&True("finalDocked");
});
}
catch(Exception error){failure=error.ToString();Console.Error.WriteLine(error.Message);}
finally
{
stop.Cancel();if(services is not null)try{await services;}catch(Exception error){failure??=error.ToString();}
await driver.ReleaseAllAsync(); // The independent protection outlives this journey.
var result=JsonSerializer.Serialize(new{passed=failure is null,pid,started=checkpoint.StartedAt,attemptStarted=started,finished=DateTimeOffset.UtcNow,checkpoint,profit=ledger.Snapshot,
finalSystem=Player().SystemName,finalStation=Player().StationName,Player().CargoCount,Player().Credits,openingCredits,closingCredits,walletReconciled,
continuousArrivalAccepted,failure,rewardsClaimed=0,screenUse=false},ToolboxSettings.JsonOptions);
await File.WriteAllTextAsync(Path.Combine(directory,"attempt-"+attempt+".json"),result);
await File.WriteAllTextAsync(Path.Combine(directory,"result.json"),result);
}
if(failure is not null)throw new InvalidOperationException("CG cargo journey paused for reconciliation; original receipts were retained.");
Console.WriteLine(JsonSerializer.Serialize(new{passed=true,quantity=order.Quantity,profit=ledger.Snapshot}));
}
}
using System.Diagnostics;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using XFEEDToolBox.Core.Data;
using XFEEDToolBox.Core.Execution;
using XFEEDToolBox.Core.Models;
using XFEEDToolBox.Core.Native;
using XFEEDToolBox.Core.Navigation;
internal static class CgCargoJourney
{
// Explicit current CG route. Cached prices are ceilings/floors only; the
// driver obtains original prices and stock immediately before each order.
internal static async Task RunAsync(int pid,string directory,CancellationToken externalToken=default,TradeOrder? requestedOrder=null)
{
directory=Path.GetFullPath(directory);Directory.CreateDirectory(directory);
using var runLease=new FileStream(Path.Combine(directory,"runner.lock"),FileMode.OpenOrCreate,FileAccess.ReadWrite,FileShare.None);
var settings=ToolboxSettings.Load(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),"XFEstudio","XFEEDToolBox","settings.json"));
await using var journal=new JournalMonitor(settings.JournalDirectory);await journal.StartAsync();var initial=journal.State;
if(!initial.InGame||initial.CurrentShipId!=5||string.IsNullOrWhiteSpace(initial.CommanderId)||initial.CargoCapacity!=192)
throw new InvalidOperationException("Actual Corsair/5 with the verified 192t hold is required.");
var workRoot=Directory.GetParent(directory)!.FullName;
var useNativeCruise=true;var navigationPolicy=Path.Combine(workRoot,"navigation-policy.json");
if(File.Exists(navigationPolicy))
{using var p=JsonDocument.Parse(await File.ReadAllTextAsync(navigationPolicy));useNativeCruise=p.RootElement.GetProperty("useNativeCruise").GetBoolean();}
var lossHold=Path.Combine(workRoot,"ship-loss-hold.json");
if(File.Exists(lossHold)){using var h=JsonDocument.Parse(await File.ReadAllTextAsync(lossHold));if(h.RootElement.GetProperty("active").GetBoolean())throw new InvalidOperationException("Ship loss hold is active; no CG input is permitted.");}
using(var acceptance=JsonDocument.Parse(await File.ReadAllTextAsync(Path.Combine(workRoot,"one-ton-journey","result.json"))))
if(!acceptance.RootElement.GetProperty("passed").GetBoolean())throw new InvalidOperationException("The original 1t journey must pass before a CG batch.");
var accepted=TradeJourneyCheckpoints.Load(Path.Combine(workRoot,"one-ton-journey","journey.json"));
if(accepted.Commander!=initial.CommanderId||accepted.ShipId!=5||accepted.Sold is not {QuantityDelta:-1,Commodity:"biowaste"})
throw new InvalidDataException("Original completed 1t sale receipt required before CG production.");
var membershipPath=Path.Combine(Directory.GetParent(directory)!.FullName,"cg-membership-864","membership-latest.json");
long contributionBefore;
using(var membership=JsonDocument.Parse(await File.ReadAllTextAsync(membershipPath)))
{
var root=membership.RootElement;var body=root.GetProperty("Body").GetString()!;
var confirmedGoal=CommunityGoalCatalogue.RequireTrade(CommunityGoalCatalogue.Parse(body),DateTimeOffset.UtcNow,true);
if(root.GetProperty("Proof").GetString()!=Convert.ToHexStringLower(SHA256.HashData(Encoding.UTF8.GetBytes(body)))
||confirmedGoal.Id!=864)
throw new InvalidDataException("Original active CG 864 membership evidence required before carrying cargo.");
contributionBefore=confirmedGoal.Contribution??0;
}
var planPath=Path.Combine(directory,"plan.json");
var plannedOrder=requestedOrder??new TradeOrder("uranium","Uranium",192,7241,57104);
if(File.Exists(planPath))
{
using var saved=JsonDocument.Parse(await File.ReadAllTextAsync(planPath));var plan=saved.RootElement;
if(plan.GetProperty("goalId").GetUInt64()!=864||plan.GetProperty("sourceMarket").GetInt64()!=3228792064)
throw new InvalidDataException("Persisted CG route differs from this cargo journey.");
plannedOrder=new(plan.GetProperty("commodity").GetString()!,plan.TryGetProperty("label",out var label)?label.GetString()!:"Uranium",plan.GetProperty("quantity").GetInt32(),
plan.GetProperty("buyCeiling").GetInt64(),plan.GetProperty("sellFloor").GetInt64());
contributionBefore=plan.GetProperty("contributionBefore").GetInt64();
}
else
{
await using var file=new FileStream(planPath,FileMode.CreateNew,FileAccess.Write,FileShare.Read);
await JsonSerializer.SerializeAsync(file,new{goalId=864UL,sourceMarket=3228792064L,quantity=plannedOrder.Quantity,commodity=plannedOrder.Commodity,label=plannedOrder.Name,
buyCeiling=plannedOrder.BuyPrice,sellFloor=plannedOrder.SellPrice,contributionBefore});
}
var checkpointPath=Path.Combine(directory,"journey.json");
var checkpoint=File.Exists(checkpointPath)?TradeJourneyCheckpoints.Load(checkpointPath):new(initial.CommanderId,5,DateTimeOffset.UtcNow);
if(checkpoint.Commander!=initial.CommanderId||checkpoint.ShipId!=5)throw new InvalidDataException("CG journey belongs to another ship or commander.");
if(!NativeTradeBatchPolicy.Allows(plannedOrder.Commodity)||plannedOrder.Quantity is <1 or >192||plannedOrder.BuyPrice is <=0 or >100000||plannedOrder.SellPrice<=plannedOrder.BuyPrice)
throw new InvalidDataException("Persisted CG batch exceeds the original interface limits.");
if(checkpoint.Bought is {} reserved&&(reserved.Commodity!=plannedOrder.Commodity||reserved.QuantityDelta!=plannedOrder.Quantity||reserved.MarketId!=3228792064))
throw new InvalidDataException("CG journey has a different reserved cargo; no order was submitted.");
var attempt=Guid.NewGuid().ToString("N");
var guardian=PersistentProtectionProcess.Current??throw new InvalidOperationException("CG trading requires independently owned protection.");
guardian.RequireReady(pid);
using var stop=CancellationTokenSource.CreateLinkedTokenSource(externalToken);stop.CancelAfter(TimeSpan.FromMinutes(60));
using var sentinel=new Timer(_=>{if(File.Exists(Path.Combine(directory,"stop"))||!guardian.IsAlive)stop.Cancel();},null,100,100);
using var ledger=new TradeProfitLedger(Path.Combine(directory,"ledger"));var channel=new NativeCommerceChannel(pid);
NativeWalletObservation? wallet=null;PlayerState Player()=>wallet?.Project(journal.State)??journal.State;
async Task Check(CancellationToken token)
{
token.ThrowIfCancellationRequested();var current=Player();
if(!current.InGame||current.CurrentShipId!=5||current.CommanderId!=initial.CommanderId||current.LoadGameAt!=initial.LoadGameAt
||NavigationSafety.UnhealthyHull(current)||NavigationSafety.HasExternalThreat(current)||!guardian.IsAlive)
throw new InvalidOperationException("CG session, ship or continuous protection changed.");
var hold=Path.Combine(Directory.GetParent(directory)!.FullName,"maintenance-hold.json");
if(current.Docked&&File.Exists(hold))
{
using var maintenance=JsonDocument.Parse(await File.ReadAllTextAsync(hold,token));
if(maintenance.RootElement.GetProperty("active").GetBoolean())throw new InvalidOperationException("Maintenance hold requires staying docked.");
}
JsonElement health;var readyUntil=DateTimeOffset.UtcNow.AddSeconds(15);
while(true)
{
token.ThrowIfCancellationRequested();current=Player();
if(!current.InGame||current.CurrentShipId!=5||current.CommanderId!=initial.CommanderId||current.LoadGameAt!=initial.LoadGameAt
||NavigationSafety.UnhealthyHull(current)||NavigationSafety.HasExternalThreat(current)||!guardian.IsAlive)
throw new InvalidOperationException("CG ship or protection changed while waiting for post-transition telemetry.");
try{guardian.RequireReady(pid);health=await channel.CallAsync(new{op="health.read"},token);break;}
catch(Exception error)when((NavigationRecovery.CanRetry(error,token)||error is InvalidOperationException&&error.Message=="Native main-ship health unavailable or stale")&&DateTimeOffset.UtcNow<readyUntil)
{
// Journal can confirm a real jump before the new main-ship
// sampler publishes its first verified frame. No flight or
// trade input is sent until that original frame is ready.
await Task.Delay(80,token);
}
}
if(!health.GetProperty("localPlayerShipVerified").GetBoolean()||!health.GetProperty("shipHealth").GetProperty("verified").GetBoolean()
||health.GetProperty("shipHealth").GetProperty("hullFraction").GetDouble()<.5)
throw new InvalidOperationException("Fresh independent main-ship health required.");
}
async Task Travel(Station target,Func<CancellationToken,Task> beforeInput,CancellationToken token)
{
await beforeInput(token);if(Player().Docked&&Player().MarketId==target.MarketId)return;
if(Player().Docked)
{
using var source=new NativeCurrentSystemSource(pid,NativeSystemLocationReader.GameSha256,()=>journal.State);
var catalogue=await source.ReadLocationsAsync(token);
var departureTarget=catalogue.Locations.FirstOrDefault(x=>x.Category==SystemLocationCategory.Station&&x.MarketId is >0&&x.MarketId!=Player().MarketId)
??throw new InvalidOperationException("A verified alternate local target is required for native departure.");
Console.WriteLine("原始自动起飞:"+Player().StationName);
await LocalDepartureAcceptance.RunAsync(pid,departureTarget.Name,Path.Combine(directory,"departure-"+Guid.NewGuid().ToString("N")+".json"),token,stopAfterDeparture:false);
}
await Check(token);
if(!Player().SystemName.Equals(target.SystemName,StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine("游戏原始航线:"+target.SystemName);
await InterstellarTravelAcceptance.RunAsync(pid,target.SystemName,Path.Combine(directory,"interstellar-"+Guid.NewGuid().ToString("N")+".json"),token);
}
await Check(token);Console.WriteLine("原始超巡、空管与自动降落:"+target.Name);
await LocalNavigationAcceptance.RunAsync(pid,target.Name,Path.Combine(directory,"arrival-"+Guid.NewGuid().ToString("N")+".json"),
requireCruise:true,useNativeCruise:useNativeCruise,allowObscuration:true,externalToken:token,keepHandoffScene:true);
var dockObservedUntil=DateTimeOffset.UtcNow.AddSeconds(5);
while(!Player().Docked||Player().MarketId!=target.MarketId)
{
token.ThrowIfCancellationRequested();guardian.RequireReady(pid);
if(Player().Docked||DateTimeOffset.UtcNow>=dockObservedUntil)break;
await Task.Delay(50,token);
}
if(!Player().Docked||Player().MarketId!=target.MarketId)throw new InvalidOperationException("Original docking differs from the CG route.");
}
var from=new Station{MarketId=3228792064,Name="Polyakov Station",SystemName="Duamta"};
var to=new Station{MarketId=CommunityGoalCatalogue.HomerMarket,Name="Homer Platform",SystemName="AD Leonis"};
var order=plannedOrder;var leg=new TradeLeg{Number=1,From=from,To=to,Orders=[order]};
var policy=new NativeTradeBatchPolicy(192,Math.Max(initial.Rebuy,4427098));
var driver=new NativeTradeCommandDriver(channel,Player,Path.Combine(directory,"operations"),Travel,Check,batchPolicy:policy);
driver.WalletObserved+=value=>wallet=value;
driver.ServiceConfirmed+=receipt=>ledger.Record(receipt.ToCashEntry());driver.Status+=Console.WriteLine;
string? failure=null;Task? services=null;var started=DateTimeOffset.UtcNow;long? openingCredits=null,closingCredits=null;
var continuousArrivalAccepted=false;var walletReconciled=false;var openingPath=Path.Combine(directory,"opening-wallet.json");
try
{
guardian.RequireReady(pid);await driver.PreflightAsync(stop.Token);await Check(stop.Token);
if(File.Exists(openingPath))
{
using var saved=JsonDocument.Parse(await File.ReadAllTextAsync(openingPath,stop.Token));openingCredits=saved.RootElement.GetProperty("credits").GetInt64();
}
else
{
if(checkpoint.Bought is null)openingCredits=wallet?.Credits??throw new InvalidDataException("Original opening wallet not observed.");
else
{
// Upgrade an existing in-flight checkpoint using its first
// actual confirmed debit, never a derived or assumed balance.
var entries=Directory.EnumerateFiles(Path.Combine(directory,"ledger"),"*.entry.json").Select(path=>
{
using var stored=JsonDocument.Parse(File.ReadAllText(path));var root=stored.RootElement;
return (Sequence:root.GetProperty("Sequence").GetInt64(),Key:root.GetProperty("Entry").GetProperty("OperationKey").GetString()!);
}).OrderBy(entry=>entry.Sequence).ToArray();
if(entries.Length==0)throw new InvalidDataException("Reserved CG cargo is missing its accounting history.");
using var original=JsonDocument.Parse(await File.ReadAllTextAsync(Path.Combine(directory,"operations",entries[0].Key+".confirmed.json"),stop.Token));
var receipt=original.RootElement.GetProperty("receipt");
if(!receipt.GetProperty("serverConfirmed").GetBoolean()||!receipt.GetProperty("clientApplied").GetBoolean())throw new InvalidDataException("Opening CG debit is not confirmed.");
openingCredits=receipt.GetProperty("creditsBefore").GetInt64();
}
await using var file=new FileStream(openingPath,FileMode.CreateNew,FileAccess.Write,FileShare.Read);
await JsonSerializer.SerializeAsync(file,new{credits=openingCredits,at=DateTimeOffset.UtcNow},cancellationToken:stop.Token);
}
TradeJourneyCheckpoints.Save(checkpointPath,checkpoint);
if(checkpoint.Bought is null)
{
if(!Player().Docked||Player().MarketId!=from.MarketId||Player().CargoCount!=0)
throw new InvalidOperationException("First CG batch requires the confirmed empty hold at Polyakov Station.");
Console.WriteLine($"采购 {order.Quantity}t {order.Name},并完成本站支持的维护。");
services=driver.ServiceDockedAsync(new(from.MarketId,order.Quantity),Check,stop.Token);
var action=new TradeAction(TradeActionKind.Buy,leg,order);
var receipt=await driver.ExecuteTradeAsync(action,Check,stop.Token);receipt.Validate(action,Player(),started);
ledger.RecordTrade(receipt);checkpoint=checkpoint with{Bought=receipt};TradeJourneyCheckpoints.Save(checkpointPath,checkpoint);
Console.WriteLine(JsonSerializer.Serialize(new{bought=receipt.QuantityDelta,receipt.UnitPrice,receipt.CreditsAfter}));
await services;services=null;
}
else ledger.RecordTrade(checkpoint.Bought);
if(checkpoint.Sold is null)
{
await Travel(to,Check,stop.Token);await Check(stop.Token);
Console.WriteLine("Homer 原始卖出与加油维修补给。");
services=driver.ServiceDockedAsync(new(to.MarketId,order.Quantity),Check,stop.Token);
var action=new TradeAction(TradeActionKind.Sell,leg,order);
var receipt=await driver.ExecuteTradeAsync(action,Check,stop.Token);receipt.Validate(action,Player(),started);
ledger.RecordTrade(receipt);checkpoint=checkpoint with{Sold=receipt};TradeJourneyCheckpoints.Save(checkpointPath,checkpoint);
await services;services=null;
}
else
{
ledger.RecordTrade(checkpoint.Sold);
// A previous process can have saved the sale before its
// concurrent service task finished. Reconfirm services before
// accepting the completed journey; never purchase again.
await driver.ServiceDockedAsync(new(to.MarketId,order.Quantity),Check,stop.Token);
}
if(!Player().Docked||Player().MarketId!=to.MarketId||Player().CargoCount!=0)throw new InvalidOperationException("CG final docking or empty hold not confirmed.");
var membership=await new NativeCommunityGoalClient(channel).ReadAsync(true,stop.Token);
var goal=CommunityGoalCatalogue.RequireTrade(membership.Goals,DateTimeOffset.UtcNow,true);
await File.WriteAllTextAsync(Path.Combine(directory,"membership-after.json"),JsonSerializer.Serialize(membership),stop.Token);
if(goal.Id!=864||goal.Contribution is null||goal.Contribution<contributionBefore+order.Quantity)
throw new InvalidOperationException("Original CG contribution has not confirmed this batch's delivered increment.");
var inventory=await channel.CallAsync(new{op="commerce.inventory"},stop.Token);closingCredits=inventory.GetProperty("credits").GetInt64();
if(inventory.GetProperty("count").GetInt32()!=0||openingCredits+ledger.Snapshot.CashChange!=closingCredits)
throw new InvalidDataException("Final original wallet does not reconcile with this journey's confirmed receipts.");
walletReconciled=true;
// The next batch must measure its own increment from this newly
// confirmed contribution, rather than reusing the original signup's
// zero baseline. An existing journey still keeps its saved plan.
var membershipTemporary=membershipPath+"."+Guid.NewGuid().ToString("N")+".tmp";
try
{
await File.WriteAllTextAsync(membershipTemporary,JsonSerializer.Serialize(membership),stop.Token);
File.Move(membershipTemporary,membershipPath,true);
}
finally{if(File.Exists(membershipTemporary))File.Delete(membershipTemporary);}
continuousArrivalAccepted=Directory.EnumerateFiles(directory,"arrival-*.json").Any(path=>
{
using var report=JsonDocument.Parse(File.ReadAllText(path));var value=report.RootElement;
bool True(string name)=>value.TryGetProperty(name,out var field)&&field.ValueKind==JsonValueKind.True;
// Provenance diagnostics also end in .json but are not arrival
// reports. Software cruise proves its original safe-drop call
// instead of claiming the disabled game assist engaged.
var cruise=True("cruiseEngaged")||value.TryGetProperty("useNativeCruise",out var native)&&native.ValueKind==JsonValueKind.False
&&True("cruiseObserved")&&True("safeDropRequested");
return True("passed")&&cruise&&True("dockingGranted")&&True("dockingEngaged")&&True("finalDocked");
});
}
catch(Exception error){failure=error.ToString();Console.Error.WriteLine(error.Message);}
finally
{
stop.Cancel();if(services is not null)try{await services;}catch(Exception error){failure??=error.ToString();}
await driver.ReleaseAllAsync(); // The independent protection outlives this journey.
var result=JsonSerializer.Serialize(new{passed=failure is null,pid,started=checkpoint.StartedAt,attemptStarted=started,finished=DateTimeOffset.UtcNow,checkpoint,profit=ledger.Snapshot,
finalSystem=Player().SystemName,finalStation=Player().StationName,Player().CargoCount,Player().Credits,openingCredits,closingCredits,walletReconciled,
continuousArrivalAccepted,failure,rewardsClaimed=0,screenUse=false},ToolboxSettings.JsonOptions);
await File.WriteAllTextAsync(Path.Combine(directory,"attempt-"+attempt+".json"),result);
await File.WriteAllTextAsync(Path.Combine(directory,"result.json"),result);
}
if(failure is not null)throw new InvalidOperationException("CG cargo journey paused for reconciliation; original receipts were retained.");
Console.WriteLine(JsonSerializer.Serialize(new{passed=true,quantity=order.Quantity,profit=ledger.Snapshot}));
}
}