返回提交历史
Modified
XFEEDToolBox.Core/Execution/NativeTradeNavigation.cs
+1
-1
Modified
XFEEDToolBox.Core/Navigation/NativeSystemFlightControlDriver.DockingReset.cs
+6
-1
Modified
XFEEDToolBox.Core/Navigation/SystemNavigationRunner.cs
+30
-5
Modified
XFEEDToolBox.Test/SystemNavigationRunnerTests.cs
+26
-1
Modified
docs/TRADE-WORK-COORDINATION.md
+8
-0
XFEstudio/XFEEDToolBox
Observe an uncertain docking reset without replaying controls
A lost acknowledgement could end a trade leg even while the original game completed its automatic landing. Production now observes fresh AI and exact-market docking after the immutable reset request, without another arm, axis lease or suspend. Retain the two-second moving-ship handoff guard, identity checks and strict diagnostic receipt requirements. Preserve the original internal error for any terminal failure. Validated by 43 system-navigation and 23 docking regressions. The toolbox resumed the original 752t Gallite checkpoint, sold it once, reconciled all services and continued the loop in the same game process.
b423b31
代码差异
5 个文件
+71
-8
@@ -84,7 +84,7 @@ public sealed class NativeTradeNavigation(int processId,string gameHash,Func<Pla
84
84
await arrival.RunAsync(target,local,token);
85
85
if(arrival.HandoffFailed)throw new DockingHandoffException(lastIssue??"原生自动降落交接未完成。",destination.MarketId);
86
86
if(arrival.State!=NavigationRunState.Completed||!player().Docked||player().MarketId!=destination.MarketId)
87
throw new InvalidOperationException(lastIssue??"目的站停靠尚未确认完成。");
87
throw new InvalidOperationException(lastIssue??"目的站停靠尚未确认完成。",arrival.Failure);
88
88
}
89
89
finally{gate.Release();}
90
90
}
@@ -76,12 +76,17 @@ public sealed partial class NativeSystemFlightControlDriver
76
76
{
77
77
// Resolve an uncertain acknowledgement by the same immutable ID;
78
78
// arm is never sent again, even if a native reselect already happened.
79
JsonElement reconciled;
79
80
try
80
81
{
81
82
using var reconciliation=new CancellationTokenSource(TimeSpan.FromMilliseconds(500));
82
AdoptOriginalResetReceipt(await service.CallAsync(new{op="docking.reset.status",requestId=dockingResetRequestId,owner},reconciliation.Token));
83
reconciled=await service.CallAsync(new{op="docking.reset.status",requestId=dockingResetRequestId,owner},reconciliation.Token);
83
84
}
85
catch(Exception status)when(!emptyDockingDiagnostic&&!token.IsCancellationRequested
86
&&(NavigationRecovery.CanRetry(status,CancellationToken.None)||status is InvalidOperationException&&status.Message=="No matching original reset request"))
87
{throw new DockingResetObservationPendingException(dockingResetRequestId,selected.MarketId!.Value,frame.ObservedAt,new AggregateException(original,status));}
84
88
catch(Exception status){throw new InvalidOperationException("Original reset outcome is unknown; the arm request was not replayed.",new AggregateException(original,status));}
89
AdoptOriginalResetReceipt(reconciled);
85
90
}
86
91
while(true)
87
92
{
@@ -15,6 +15,9 @@ public interface ISystemFlightControlDriver
15
15
public sealed record SystemNavigationProgress(NavigationRunState State,SystemNavigationTarget Target,SystemNavigationPhase Phase,string Message);
16
16
public sealed class DockingHandoffException(string message,long marketId):InvalidOperationException(message)
17
17
{public long MarketId{get;}=marketId;}
18
public sealed class DockingResetObservationPendingException(string requestId,long marketId,DateTimeOffset requestedAt,Exception inner)
19
:Exception("归零请求回执暂未确认,正在只读观察自动降落,不重发请求。",inner)
20
{public string RequestId{get;}=requestId;public long MarketId{get;}=marketId;public DateTimeOffset RequestedAt{get;}=requestedAt;}
18
21
19
22
/// <summary>One local leg, with expiring inputs, actual target acknowledgements and bounded telemetry recovery.</summary>
20
23
public sealed class SystemNavigationRunner(Func<PlayerState> player,Func<DateTimeOffset>? clock=null)
@@ -24,6 +27,7 @@ public sealed class SystemNavigationRunner(Func<PlayerState> player,Func<DateTim
24
27
private CancellationTokenSource? cancellation;
25
28
public NavigationRunState State{get;private set;}
26
29
public bool HandoffFailed{get;private set;}
30
public Exception? Failure{get;private set;}
27
31
public event Action<SystemNavigationProgress>? Progress;
28
32
public void Stop(){lock(gate)cancellation?.Cancel();}
29
33
public async Task RunAsync(SystemNavigationTarget target,ISystemFlightControlDriver driver,CancellationToken token=default)
@@ -34,7 +38,7 @@ public sealed class SystemNavigationRunner(Func<PlayerState> player,Func<DateTim
34
38
if(cancellation is not null)throw new InvalidOperationException("星系内导航已在运行。");
35
39
stop=CancellationTokenSource.CreateLinkedTokenSource(token);cancellation=stop;
36
40
}
37
HandoffFailed=false;var phase=SystemNavigationPhase.Preparing;var state=NavigationRunState.Running;var terminal="星系内导航已停止。";
41
HandoffFailed=false;Failure=null;var phase=SystemNavigationPhase.Preparing;var state=NavigationRunState.Running;var terminal="星系内导航已停止。";
38
42
void Report(SystemNavigationPhase next,string message){phase=next;State=NavigationRunState.Running;Progress?.Invoke(new(State,target,next,message));}
39
43
try
40
44
{
@@ -46,6 +50,7 @@ public sealed class SystemNavigationRunner(Func<PlayerState> player,Func<DateTim
46
50
await driver.SelectAsync(target,stop.Token);
47
51
await driver.PrepareAsync(stop.Token);
48
52
var policy=new SystemFlightNavigator();long previous=0;ulong entity=0;string? loadout=null;DateTimeOffset? missingSince=null;
53
DockingResetObservationPendingException? pendingReset=null;var landingObserved=false;
49
54
Report(phase,"正在核对星系内目标和驾驶状态。");
50
55
while(true)
51
56
{
@@ -64,6 +69,14 @@ public sealed class SystemNavigationRunner(Func<PlayerState> player,Func<DateTim
64
69
if(entity!=0&&frame.ShipEntity!=entity&&!confirmedTransition||loadout is not null&&frame.LoadoutFingerprint!=loadout)throw new InvalidOperationException("受控飞船或装配改变,已停止导航。");
65
70
entity=frame.ShipEntity;loadout=frame.LoadoutFingerprint;previous=frame.Sequence;
66
71
var decision=policy.Decide(frame,target,now());
72
if(pendingReset is not null)
73
{
74
landingObserved|=frame.DockingAssist==NativeAssistState.Engaged;
75
if(!landingObserved&&now()-pendingReset.RequestedAt>TimeSpan.FromSeconds(2)&&frame.Speed>1)
76
decision=new(SystemNavigationPhase.Stopped,"自动降落未接管,飞船仍在移动,正在保护退出。",null,null,SystemNavigationAction.ExitForFailedHandoff);
77
else if(!landingObserved&&!decision.Complete&&now()-pendingReset.RequestedAt>TimeSpan.FromSeconds(30))
78
throw new InvalidOperationException("自动降落回执和接管状态仍未确认,原请求未重发。",pendingReset);
79
}
67
80
Report(decision.Phase,decision.Message);
68
81
if(decision.Phase==SystemNavigationPhase.Stopped)
69
82
{
@@ -74,14 +87,26 @@ public sealed class SystemNavigationRunner(Func<PlayerState> player,Func<DateTim
74
87
}
75
88
state=NavigationRunState.Stopped;terminal=decision.Message;break;
76
89
}
77
await driver.ApplySystemFlightAsync(decision,frame,target,frame.ObservedAt.AddMilliseconds(250),stop.Token);
90
// A lost acknowledgement is not permission for a second
91
// arm or another axis lease. Observe the actual AI/dock;
92
// the same two-second handoff guard remains above.
93
if(pendingReset is null)
94
await driver.ApplySystemFlightAsync(decision,frame,target,frame.ObservedAt.AddMilliseconds(250),stop.Token);
95
else if(!decision.Complete)
96
Report(SystemNavigationPhase.NativeLanding,landingObserved?"自动降落已接管,正在核对实际停靠。":"正在核对归零回执与自动降落状态,不重发请求。");
78
97
missingSince=null;
79
98
if(decision.Complete){state=NavigationRunState.Completed;terminal=decision.Message;break;}
80
99
}
100
catch(DockingResetObservationPendingException error)
101
{
102
if(error.MarketId!=target.MarketId)throw new InvalidDataException("未确认的归零请求属于其他市场。",error);
103
pendingReset=error;Report(SystemNavigationPhase.NativeLanding,error.Message);
104
}
81
105
catch(Exception error)when(NavigationRecovery.CanRetry(error,stop.Token))
82
106
{
83
107
missingSince??=now();
84
try{await driver.SuspendSystemFlightAsync();}catch(Exception release)when(NavigationRecovery.CanRetry(release,stop.Token)){}
108
if(pendingReset is null)
109
try{await driver.SuspendSystemFlightAsync();}catch(Exception release)when(NavigationRecovery.CanRetry(release,stop.Token)){}
85
110
if(now()-missingSince>TimeSpan.FromSeconds(30))throw new InvalidOperationException("星系内飞行数据恢复未成功,已停止并交还控制。",error);
86
111
Report(SystemNavigationPhase.Waiting,"正在重新连接飞行数据并自动重试。");
87
112
}
@@ -89,11 +114,11 @@ public sealed class SystemNavigationRunner(Func<PlayerState> player,Func<DateTim
89
114
}
90
115
}
91
116
catch(OperationCanceledException)when(stop.IsCancellationRequested){state=NavigationRunState.Stopped;phase=SystemNavigationPhase.Stopped;}
92
catch(Exception error){state=NavigationRunState.Failed;phase=SystemNavigationPhase.Stopped;terminal=error.Message;}
117
catch(Exception error){Failure=error;state=NavigationRunState.Failed;phase=SystemNavigationPhase.Stopped;terminal=error.Message;}
93
118
finally
94
119
{
95
120
try{await driver.ReleaseSystemFlightAsync();}
96
catch(Exception error){state=NavigationRunState.Failed;terminal="已停止导航,输入释放未获确认:"+error.Message;}
121
catch(Exception error){Failure=Failure is null?error:new AggregateException(Failure,error);state=NavigationRunState.Failed;terminal="已停止导航,输入释放未获确认:"+error.Message;}
97
122
lock(gate){cancellation=null;stop.Dispose();State=state;}
98
123
Progress?.Invoke(new(state,target,phase,terminal));
99
124
}
@@ -19,6 +19,7 @@ public class SystemNavigationRunnerTests
19
19
public Func<int,SystemFlightFrame> Frame=_=>Docked;
20
20
public Action? OnPrepare;
21
21
public Action? OnApply;
22
public Action<SystemFlightDecision>? OnDecision;
22
23
public bool Ready=true;
23
24
private int reads;
24
25
public Task<SystemNavigationCapabilities> InspectAsync(CancellationToken t){Calls.Add("inspect");return Task.FromResult(new SystemNavigationCapabilities(new('a',64),Ready,true,true,true,true,true,"Isolated fixture"));}
@@ -26,7 +27,7 @@ public class SystemNavigationRunnerTests
26
27
public Task PrepareAsync(CancellationToken t){Calls.Add("prepare");OnPrepare?.Invoke();return Task.CompletedTask;}
27
28
public Task<SystemFlightFrame> ReadSystemFlightAsync(CancellationToken t){Calls.Add("read");return Task.FromResult(Frame(++reads));}
28
29
public Task ApplySystemFlightAsync(SystemFlightDecision d,SystemFlightFrame f,SystemNavigationTarget target,DateTimeOffset expires,CancellationToken t)
29
{Calls.Add("apply");Check.That(target.SameIdentity(Target)&&expires==f.ObservedAt.AddMilliseconds(250));OnApply?.Invoke();return Task.CompletedTask;}
30
{Calls.Add("apply");Check.That(target.SameIdentity(Target)&&expires==f.ObservedAt.AddMilliseconds(250));OnDecision?.Invoke(d);OnApply?.Invoke();return Task.CompletedTask;}
30
31
public Task ReleaseSystemFlightAsync(){Calls.Add("release");return Task.CompletedTask;}
31
32
}
32
33
[Test] public async Task ExactDockedAcknowledgementCompletesAndAlwaysReleasesControl()
@@ -34,6 +35,30 @@ public class SystemNavigationRunnerTests
34
35
var driver=new Fake();var runner=new SystemNavigationRunner(()=>Player,()=>Now);await runner.RunAsync(Target,driver);
35
36
Check.Equal(NavigationRunState.Completed,runner.State);Check.Equal("inspect,select,prepare,read,apply,release",string.Join(',',driver.Calls));
36
37
}
38
private static SystemFlightFrame Landing(int sequence,DateTimeOffset at,NativeAssistState assist)=>Docked with{Sequence=sequence,ObservedAt=at,Mode=LocalFlightMode.Normal,
39
TargetDistance=7000,TargetPosition=new(0,0,7000),Speed=50,Assists=new(false,true,false),DockingAssist=assist,
40
Clearance=new(20,DockingPermission.Granted,9,Now,Now.AddMinutes(5))};
41
[Test] public async Task LostResetAcknowledgementObservesActualAiAndDockingWithoutRearmingOrSuspending()
42
{
43
var at=Now;var arms=0;var driver=new Fake{Frame=n=>{at=Now.AddMilliseconds((n-1)*100);if(n==2)throw new IOException("Brief telemetry interruption during handoff");return n==4?Docked with{Sequence=n,ObservedAt=at}:Landing(n,at,n==1?NativeAssistState.Ready:NativeAssistState.Engaged);},
44
OnDecision=d=>{if(d.Actions.HasFlag(SystemNavigationAction.EnableDockingAssist)){arms++;throw new DockingResetObservationPendingException("immutable-request",20,Now,new IOException("Lost status reply"));}}};
45
var runner=new SystemNavigationRunner(()=>Player,()=>at);await runner.RunAsync(Target,driver);
46
Check.Equal(NavigationRunState.Completed,runner.State);Check.Equal(1,arms);Check.Equal(1,driver.Calls.Count(x=>x=="apply"));Check.Equal(1,driver.Calls.Count(x=>x=="release"));
47
}
48
[Test] public async Task LostAcknowledgementDoesNotExtendTheTwoSecondMovingShipGuard()
49
{
50
var at=Now;var arms=0;var exits=0;var driver=new Fake{Frame=n=>{at=Now.AddMilliseconds((n-1)*2100);return Landing(n,at,NativeAssistState.Ready);},
51
OnDecision=d=>{if(d.Actions.HasFlag(SystemNavigationAction.EnableDockingAssist)){arms++;throw new DockingResetObservationPendingException("immutable-request",20,Now,new IOException("Lost status reply"));}
52
if(d.Actions.HasFlag(SystemNavigationAction.ExitForFailedHandoff))exits++;}};
53
var runner=new SystemNavigationRunner(()=>Player,()=>at);await runner.RunAsync(Target,driver);
54
Check.Equal(NavigationRunState.Stopped,runner.State);Check.That(runner.HandoffFailed);Check.Equal(1,arms);Check.Equal(1,exits);
55
}
56
[Test] public async Task AResetReceiptForAnotherMarketCannotBecomeAnObservedHandoff()
57
{
58
var driver=new Fake{Frame=_=>Landing(1,Now,NativeAssistState.Ready),OnApply=()=>throw new DockingResetObservationPendingException("wrong-market",99,Now,new IOException())};
59
var runner=new SystemNavigationRunner(()=>Player,()=>Now);await runner.RunAsync(Target,driver);
60
Check.Equal(NavigationRunState.Failed,runner.State);Check.That(runner.Failure is InvalidDataException);Check.Equal(1,driver.Calls.Count(x=>x=="apply"));
61
}
37
62
[Test] public async Task WrongSystemOrUnhealthyHullNeverSelectsOrControlsATarget()
38
63
{
39
64
foreach(var player in new[]{Player with{Navigation=new(){CurrentSystemAddress=2}},Player with{Ships=[Player.Ships[0] with{HullHealth=.49}]}})
@@ -2,6 +2,14 @@
2
2
3
3
更新:2026-10-25(Asia/Shanghai)。最新状态以紧接下方的记录为准,后文保留历史故障及验收过程。
4
4
5
## 归零回执瞬断改为只读观察;原752t已售出并继续(2026-10-25 07:16)
6
7
- 用户重申工具箱持续自动往返,助手只检测,出错停了才修复,不建定时任务。检查发现原98656在06:38:51因“Original reset outcome is unknown”终止流程,但原游戏随后06:40:50已实际停靠Polyakov;货物仍是原已确认买入的752t Gallite,不能再买同批。
8
- Core现在将生产归零请求已发送、同一请求ID的回执暂未核对作为只读观察状态;不重发arm、不创建新轴控制租约,也不因观察期间短暂传输失败调用suspend。继续核对新鲜同船/同装配/同目标的实际AI及停靠;2秒内未接管且仍移动时的退出、30秒未接管上限、身份和错误回执校验均保留。明确诊断试验仍要求原回执证明,不以实际停靠替代原归零试验通过。完整内部错误现在向旅程保留,不再只剩一行提示。
9
- 43项SystemNavigation和23项Docking相关回归通过,包括回执丢失后AI真实接管/停靠、不重发或suspend、2秒保护和错市场拒绝。Core Release构建0警告0错误,只更新工具箱至r9/95564,游戏97564及0.5.17加载器保持,没有重新进入游戏或另起控制器。
10
- 原journey-8aaeccbae5b244ccb2155bdb26e298f8已实际passed=true;旧采购保持原键,07:13:38一次售出全部752t,销售10,183,584、采购8,265,984、燃油141、维修/完整度154,确认净1,917,305Cr。开账158,390,712→结账160,308,017严格核对。不得重买/重卖此批;前次失败尝试保留在attempts。
11
- 当前本工作目录确认净48,684,876Cr、完成5批,工具箱已自行离站继续;新批次收益仍按实际回执记账。没有定时任务、子智能体、键鼠或助手逐项驾驶。未知成交订单的防重和实际受损保护保持,对话结束不取消程序任务。
12
5
13
## 362t整趟已真实通过,工具箱已自行满载752t返程(2026-10-25 06:37)
6
14
7
15
- journey-20707eb1485946fd8137be96204dfd6f/result.json实际passed=true:362t Uranium全部按原始57104Cr/t售出;采购2,621,242、销售20,671,648、燃油259、维修/完整度230,确认净18,049,917Cr。开账加净收益严格等于结账,服务器和客户端原始回执均确认,空舱与全部维护通过。CG864原始个人贡献1152→1514、已报名/有效,未领取奖励0。不把此前准备、失败尝试或未售货物计入已赚。