返回提交历史
Modified
docs/CG-TRADING-VALIDATION.md
+2
-0
Added
research/NativeAcceptance/DockedMaintenance.cs
+59
-0
Modified
research/NativeAcceptance/Program.cs
+2
-0
XFEstudio/XFEEDToolBox
Account protected empty-ship maintenance outside cargo journeys
Add a docked-only maintenance entry point for the verified CG route ship. Record confirmed original service debits in a separate ledger and require its final wallet to reconcile. It sends no trade or flight orders. Validation: isolated CLI publish and live read-only source diagnostics; actual empty-ship refuel/integrity service and wallet reconciliation passed. Continuous SCA handoff remains unresolved; recovered normal-space docking does not count as continuous arrival acceptance.
e7c5314
代码差异
3 个文件
+63
-0
@@ -27,3 +27,5 @@
27
27
`cg-cargo-journey <PID> <旅程目录>` 固定验证Corsair/5、192t、Duamta/Polyakov→AD Leonis/Homer的Uranium路线。首次采购前要求本机原1t已完成回执及CG成员证明。买价上限7,241、卖价下限57,104只是限价,执行前仍读原始价格和库存。每站维护与交易均完成才离站,保留实际重购资金;提高后的CG报价不得再乘8。
28
28
29
29
继续已有货物必须复用原目录。原始回执、钱包、会话身份及日志只存本机,不能提交到仓库。`continuousArrivalAccepted` 必须来自同一完整到站报告,不能用正常空间重试替代。未领取CG奖励不计入净利润。
30
31
空载诊断停靠可使用 `service-cg-docked <PID> <独立维护目录>`。它仅接受已核对路线站点、空舱及受保护的Corsair/5,不启动贸易或飞行;每笔原始服务费用记入独立账本,结尾必须与原始钱包核对。该路径已在恢复停靠后完成加油与完整性服务实测,诊断费用也应从总收益扣除。
@@ -0,0 +1,59 @@
1
using System.Text.Json;
2
using XFEEDToolBox.Core.Data;
3
using XFEEDToolBox.Core.Execution;
4
using XFEEDToolBox.Core.Models;
5
using XFEEDToolBox.Core.Navigation;
6
7
internal static class DockedMaintenance
8
{
9
internal static async Task RunAsync(int pid,string directory)
10
{
11
directory=Path.GetFullPath(directory);Directory.CreateDirectory(directory);
12
using var lease=new FileStream(Path.Combine(directory,"runner.lock"),FileMode.OpenOrCreate,FileAccess.ReadWrite,FileShare.None);
13
var settings=ToolboxSettings.Load(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),"XFEstudio","XFEEDToolBox","settings.json"));
14
await using var journal=new JournalMonitor(settings.JournalDirectory);await journal.StartAsync();var initial=journal.State;
15
if(!initial.InGame||!initial.Docked||initial.CurrentShipId!=5||initial.CargoCount!=0||initial.CargoCapacity!=192||!initial.StationServicesKnown
16
||initial.MarketId is not (3228792064 or 4210924803)||string.IsNullOrWhiteSpace(initial.CommanderId))
17
throw new InvalidOperationException("Expected the empty, docked CG ship at its verified route station.");
18
await using var protection=await FlightTestProtectionScope.ArmAsync(pid);
19
var channel=new NativeCommerceChannel(pid);using var ledger=new TradeProfitLedger(Path.Combine(directory,"ledger"));
20
NativeWalletObservation? wallet=null;PlayerState Player()=>wallet?.Project(journal.State)??journal.State;
21
async Task Check(CancellationToken token)
22
{
23
var player=Player();token.ThrowIfCancellationRequested();
24
if(!player.InGame||!player.Docked||player.MarketId!=initial.MarketId||player.CommanderId!=initial.CommanderId||player.CurrentShipId!=initial.CurrentShipId
25
||player.LoadGameAt!=initial.LoadGameAt||player.CargoCount!=0||protection.Stopped||NavigationSafety.HasExternalThreat(player))
26
throw new InvalidOperationException("Docked maintenance ship, cargo or protection changed.");
27
var h=(await channel.CallAsync(new{op="health.read"},token)).GetProperty("shipHealth");
28
if(!h.GetProperty("verified").GetBoolean()||h.GetProperty("hullFraction").GetDouble()<.5)throw new InvalidOperationException("Fresh original ship health required.");
29
}
30
var driver=new NativeTradeCommandDriver(channel,Player,Path.Combine(directory,"operations"),(_,_,_)=>throw new InvalidOperationException("No travel in docked maintenance."),Check);
31
driver.WalletObserved+=value=>wallet=value;driver.ServiceConfirmed+=receipt=>ledger.Record(receipt.ToCashEntry());driver.Status+=Console.WriteLine;
32
using var stop=new CancellationTokenSource(TimeSpan.FromMinutes(3));
33
using var sentinel=new Timer(_=>{if(File.Exists(Path.Combine(directory,"stop")))stop.Cancel();},null,100,100);
34
string? failure=null;long? opening=null,closing=null;
35
try
36
{
37
await driver.PreflightAsync(stop.Token);var path=Path.Combine(directory,"opening-wallet.json");
38
if(File.Exists(path)){using var saved=JsonDocument.Parse(await File.ReadAllTextAsync(path,stop.Token));opening=saved.RootElement.GetProperty("credits").GetInt64();}
39
else
40
{
41
opening=wallet!.Credits;await using var file=new FileStream(path,FileMode.CreateNew,FileAccess.Write,FileShare.Read);
42
await JsonSerializer.SerializeAsync(file,new{credits=opening},cancellationToken:stop.Token);
43
}
44
await driver.ServiceDockedAsync(new(initial.MarketId!.Value,192),Check,stop.Token);
45
await Check(stop.Token);var inventory=await channel.CallAsync(new{op="commerce.inventory"},stop.Token);closing=inventory.GetProperty("credits").GetInt64();
46
if(inventory.GetProperty("count").GetInt32()!=0||opening+ledger.Snapshot.CashChange!=closing)
47
throw new InvalidDataException("Docked maintenance wallet does not reconcile with confirmed service debits.");
48
}
49
catch(Exception error){failure=error.ToString();}
50
finally
51
{
52
await driver.ReleaseAllAsync();
53
await File.WriteAllTextAsync(Path.Combine(directory,"result.json"),JsonSerializer.Serialize(new{passed=failure is null,pid,finished=DateTimeOffset.UtcNow,
54
initial.MarketId,opening,closing,profit=ledger.Snapshot,failure,cargoCount=0,tradeOrdersSent=0,screenUse=false},ToolboxSettings.JsonOptions));
55
}
56
if(failure is not null)throw new InvalidOperationException("Docked maintenance requires reconciliation: "+failure);
57
Console.WriteLine(JsonSerializer.Serialize(new{passed=true,opening,closing,profit=ledger.Snapshot}));
58
}
59
}
@@ -7,6 +7,8 @@ using XFEEDToolBox.Core.Galaxy;
7
7
8
8
try
9
9
{
10
if(args.Length==3&&args[0]=="service-cg-docked")
11
{await DockedMaintenance.RunAsync(int.Parse(args[1]),args[2]);return;}
10
12
if(args.Length==3&&args[0]=="inspect-docking-input-sources")
11
13
{
12
14
if(File.Exists(args[2]))throw new IOException("Use a new read-only docking input report.");