返回提交历史
Added
XFEEDToolBox.Core/Navigation/CruiseOverchargeNativeGuard.cs
+51
-0
Added
XFEEDToolBox.Core/Navigation/NativeCruiseControlChannel.cs
+119
-0
Modified
XFEEDToolBox.Core/Navigation/NativeFlightControlDriver.cs
+2
-2
Added
XFEEDToolBox.Core/Navigation/NativeSystemFlightControlDriver.CruiseControl.cs
+18
-0
Modified
XFEEDToolBox.Core/Navigation/NativeSystemFlightControlDriver.cs
+3
-1
Added
XFEEDToolBox.Core/Navigation/OriginalCruiseCycleQualification.cs
+42
-0
Modified
XFEEDToolBox.Core/Navigation/OriginalFuelConsumptionBounds.cs
+10
-0
Added
XFEEDToolBox.Test/CruiseOverchargeNativeGuardTests.cs
+36
-0
Added
XFEEDToolBox.Test/OriginalCruiseCycleQualificationTests.cs
+30
-0
Modified
XFEEDToolBox.Test/OriginalFuelConsumptionBoundsTests.cs
+7
-0
Modified
docs/TRADE-WORK-COORDINATION.md
+13
-0
Added
native/ManagedPlugin/CruiseControl/CruiseControl.csproj
+10
-0
Added
native/ManagedPlugin/CruiseControlRuntime.cs
+294
-0
Added
native/ManagedPlugin/Navigation.CruiseControl.cs
+54
-0
Modified
native/ManagedPlugin/Navigation.CruiseObservation.cs
+10
-6
Modified
native/ManagedPlugin/Navigation.RecoveryResetProof.cs
+1
-1
Added
research/NativeAcceptance/CruiseControlAcceptance.cs
+46
-0
Modified
research/NativeAcceptance/Program.cs
+6
-0
XFEstudio/XFEEDToolBox
Add expiring original SCO control and coherent live qualification
Dispatch original enable and disable only on the bound main-ship update, retain heat/fuel/geometry/deadline guards, and require five distinct samples plus one matching original stop and later Off state. Correct sampling-before-clock evaluation found by the first empty SC trial; live SCO qualification remains required before production use. Validation: 10 native guard regressions, 9 cycle qualification counterexamples, 7 fuel conversion regressions, isolated NativeAOT and deployment CLI builds. The initial live trial reached supercruise but was refused before SCO activation; its failure and same-process recovery remain preserved.
f31d1fe
代码差异
18 个文件
+752
-10
@@ -0,0 +1,51 @@
1
namespace XFEEDToolBox.Core.Navigation;
2
3
public sealed record CruiseOverchargeGateFrame
4
{
5
public bool IdentityVerified{get;init;}
6
public bool GeometryVerified{get;init;}
7
public bool TargetVerified{get;init;}
8
public bool InSupercruise{get;init;}
9
public bool AssistEnabled{get;init;}
10
public bool CollisionCourse{get;init;}
11
public bool Emergency{get;init;}
12
public bool NativeCanEngage{get;init;}
13
public int RawFsdState{get;init;}
14
public int OverchargeState{get;init;}=-1;
15
public ulong ObservedAt{get;init;}
16
public double Hull{get;init;}=double.NaN;
17
public double Heat{get;init;}=double.NaN;
18
public double HeatRise{get;init;}=double.NaN;
19
public double MainFuel{get;init;}=double.NaN;
20
public double FuelCapacity{get;init;}=double.NaN;
21
public double FuelRateUpper{get;init;}=double.NaN;
22
public double RequiredFuelReserve{get;init;}=double.NaN;
23
public double Distance{get;init;}=double.NaN;
24
public double Speed{get;init;}=double.NaN;
25
public double AimErrorDegrees{get;init;}=double.NaN;
26
}
27
28
/// <summary>Additional per-update SCO interlock, evaluated inside the original owning thread.</summary>
29
public static class CruiseOverchargeNativeGuard
30
{
31
public static string? Refusal(CruiseOverchargeGateFrame value,ulong now,ulong deadline)
32
{
33
if(!value.IdentityVerified||!value.TargetVerified)return "identity-or-target";
34
if(value.ObservedAt>now||now-value.ObservedAt>200||deadline<=now||deadline-now>250)return "expired";
35
if(!value.InSupercruise||value.RawFsdState!=2||value.OverchargeState is <0 or >2)return "flight-state";
36
if(!value.GeometryVerified||value.CollisionCourse||value.AssistEnabled)return "geometry-or-assist";
37
if(value.Emergency||!double.IsFinite(value.Hull)||value.Hull<.5)return "health";
38
if(!value.NativeCanEngage)return "native-refusal";
39
var active=value.OverchargeState is 1 or 2;
40
if(!double.IsFinite(value.Distance)||!double.IsFinite(value.Speed)||value.Speed<0||!double.IsFinite(value.AimErrorDegrees)
41
||value.Distance<(active?10:15)*299792458d||value.Distance/Math.Max(1,value.Speed)<=(active?30:45)
42
||value.AimErrorDegrees<0||value.AimErrorDegrees>(active?6:3))return "approach-or-alignment";
43
if(!double.IsFinite(value.Heat)||!double.IsFinite(value.HeatRise)||value.Heat<0||Math.Abs(value.HeatRise)>1000
44
||value.Heat>=75||!active&&value.Heat>60||value.Heat+Math.Max(0,value.HeatRise)*2+3>=75)return "heat";
45
if(!double.IsFinite(value.MainFuel)||!double.IsFinite(value.FuelCapacity)||value.FuelCapacity<=0||value.MainFuel<0||value.MainFuel>value.FuelCapacity+.001
46
||!double.IsFinite(value.RequiredFuelReserve)||value.RequiredFuelReserve<=0||value.RequiredFuelReserve>=value.FuelCapacity
47
||!double.IsFinite(value.FuelRateUpper)||value.FuelRateUpper<=0
48
||value.MainFuel-value.RequiredFuelReserve-value.FuelRateUpper*2-Math.Max(.25,value.FuelCapacity*.01)<=0)return "fuel";
49
return null;
50
}
51
}
@@ -0,0 +1,119 @@
1
using System.Globalization;
2
using System.Text.Json;
3
4
namespace XFEEDToolBox.Core.Navigation;
5
6
public sealed record NativeCruiseControlCapabilities(bool Available,bool Qualified,string Service,int OwningThread);
7
8
/// <summary>Program-owned SCO policy and bounded original-command receipts. Never steers or selects a target.</summary>
9
public sealed class NativeCruiseControlChannel(int processId,string gameSha256)
10
{
11
public const string Service="CruiseControl0514c";
12
private readonly NavigationPipeClient pipe=new(processId,Service);
13
private readonly CruiseOverchargeSampleHistory history=new(gameSha256);
14
private readonly CruiseOverchargePolicy policy=new();
15
private ulong entity,world;
16
private bool leaseRequested,originalStartWitness;
17
private string owner="";
18
private string? pendingStopId,lastStopIdentity;
19
public JsonElement? LastStatus{get;private set;}
20
public CruiseOverchargeObservation? LastObservation{get;private set;}
21
public CruiseOverchargeDecision? LastDecision{get;private set;}
22
public event Action<JsonElement>? Observed;
23
public event Action<JsonElement>? OriginalReceipt;
24
public event Action<JsonElement>? OriginalStopReceipt;
25
private static bool Flag(JsonElement value,string key)=>value.TryGetProperty(key,out var found)&&found.ValueKind==JsonValueKind.True;
26
private static ulong Identity(JsonElement value,string key)=>ulong.Parse(value.GetProperty(key).GetString()!,CultureInfo.InvariantCulture);
27
private void CheckIdentity(JsonElement result)
28
{
29
if(result.GetProperty("schema").GetInt32()!=1||result.GetProperty("processId").GetInt32()!=processId
30
||result.GetProperty("gameSha256").GetString()!=gameSha256||result.GetProperty("service").GetString()!=Service
31
||!Flag(result,"overchargeControl")||!Flag(result,"enabled"))throw new InvalidDataException("Original SCO controller does not match this client.");
32
}
33
private void Capture(JsonElement result)
34
{
35
CheckIdentity(result);LastStatus=result.Clone();
36
if(result.TryGetProperty("lastOriginalStop",out var stop)&&stop.ValueKind==JsonValueKind.Object&&stop.GetProperty("owner").GetString()==owner)
37
{
38
var id=stop.GetRawText();if(id!=lastStopIdentity){lastStopIdentity=id;OriginalStopReceipt?.Invoke(stop.Clone());}
39
}
40
// A real Off sample in this response follows its original stop receipt.
41
Observed?.Invoke(result.Clone());
42
}
43
public async Task<NativeCruiseControlCapabilities> InspectAsync(CancellationToken token)
44
{
45
var result=await pipe.CallAsync(new{op="describe"},token);CheckIdentity(result);
46
return new(true,Flag(result,"overchargeControlQualified"),Service,result.GetProperty("owningThread").GetInt32());
47
}
48
public async Task UpdateAsync(string controllerOwner,ulong selectionGeneration,SystemFlightFrame flight,SystemFlightDecision decision,
49
SystemNavigationTarget target,double reserve,DateTimeOffset deadline,CancellationToken token)
50
{
51
if(owner.Length!=0&&owner!=controllerOwner)throw new InvalidOperationException("SCO channel is already bound to another controller.");
52
owner=controllerOwner;
53
var mayBoost=flight.Mode==LocalFlightMode.Supercruise&&decision.Actions==SystemNavigationAction.None
54
&&decision.Phase is SystemNavigationPhase.Supercruise or SystemNavigationPhase.Aligning;
55
if(!mayBoost){await ReleaseAsync(token);return;}
56
if(flight.GameSha256!=gameSha256||flight.ShipEntity==0||flight.ReferenceWorld==0||selectionGeneration==0)
57
throw new InvalidDataException("SCO requires the current original flight target and controller.");
58
if(entity!=flight.ShipEntity||world!=flight.ReferenceWorld)
59
{
60
await ReleaseAsync(token);var bound=await pipe.CallAsync(new{op="watch"},token);Capture(bound);
61
entity=flight.ShipEntity;world=flight.ReferenceWorld;history.Reset();originalStartWitness=false;
62
}
63
var status=await pipe.CallAsync(new{op="read"},token);Capture(status);
64
var observed=history.Read(status,flight,DateTimeOffset.UtcNow);LastObservation=observed;
65
if(!Flag(status,"owned")&&status.TryGetProperty("sample",out var sample)&&sample.ValueKind==JsonValueKind.Object&&!Flag(sample,"active"))originalStartWitness=false;
66
var selected=policy.Decide(flight,observed,reserve,DateTimeOffset.UtcNow);LastDecision=selected;
67
if(!selected.UseOvercharge){await ReleaseAsync(token);return;}
68
var requestId=Guid.NewGuid().ToString("N");
69
// Mark before IPC: a lost reply may still have queued an original call.
70
leaseRequested=true;
71
var result=await pipe.CallAsync(new{op="cruise.set",owner,requestId,selectionGeneration,sequence=flight.Sequence,
72
expiresAtUnixMilliseconds=deadline.ToUnixTimeMilliseconds(),requiredFuelReserveTonnes=reserve,
73
target=new{key=target.NativeKey,bodyAddress=target.BodyAddress.ToString(CultureInfo.InvariantCulture),kind=target.NativeKind,marketId=target.MarketId??uint.MaxValue,name=target.Name}},token);
74
var command=result.GetProperty("generation").GetUInt64();
75
while(true)
76
{
77
Capture(result);
78
if(result.TryGetProperty("receipt",out var receipt)&&receipt.ValueKind==JsonValueKind.Object&&receipt.GetProperty("requestId").GetString()==requestId)
79
{
80
if(Flag(receipt,"failed"))throw new FlightTelemetryUnavailableException("Original SCO interlock refused: "+(receipt.TryGetProperty("reason",out var why)?why.GetString():"unknown"));
81
if(Flag(receipt,"applied"))
82
{
83
if(receipt.GetProperty("owner").GetString()!=owner||receipt.GetProperty("generation").GetUInt64()!=command
84
||receipt.GetProperty("selectionGeneration").GetUInt64()!=selectionGeneration||Identity(receipt,"entityId")!=entity||Identity(receipt,"referenceWorldAddress")!=world)
85
throw new InvalidDataException("Original SCO receipt identity differs.");
86
var called=Flag(receipt,"nativeMethodCalled");
87
if(called)
88
{
89
if(receipt.GetProperty("nativeMethodRva").GetString()!="2eaacd0"||!Flag(receipt,"argument")||receipt.GetProperty("actualStateAfterCall").GetInt32() is not(1 or 2))
90
throw new InvalidDataException("Original SCO enable was not confirmed.");
91
originalStartWitness=true;
92
}
93
else if(!originalStartWitness)throw new InvalidDataException("SCO renewal has no original enable witness.");
94
OriginalReceipt?.Invoke(receipt.Clone());return;
95
}
96
}
97
if(DateTimeOffset.UtcNow>=deadline)throw new FlightTelemetryUnavailableException("Original SCO command expired without a matching receipt.");
98
await Task.Delay(10,token);result=await pipe.CallAsync(new{op="read"},token);
99
}
100
}
101
public async Task ReleaseAsync(CancellationToken token=default)
102
{
103
if(!leaseRequested)return;
104
pendingStopId??=Guid.NewGuid().ToString("N");
105
var result=await pipe.CallAsync(new{op="cruise.release",owner,requestId=pendingStopId},token);
106
var until=DateTimeOffset.UtcNow.AddSeconds(2);
107
while(true)
108
{
109
var status=await pipe.CallAsync(new{op="read"},token);Capture(status);
110
if(!Flag(status,"owned")&&Flag(status,"sampleReady")&&status.TryGetProperty("sample",out var sample)&&sample.ValueKind==JsonValueKind.Object
111
&&Identity(sample,"entityId")==entity&&Identity(sample,"referenceWorldAddress")==world&&sample.GetProperty("state").GetInt32() is 0 or 3)
112
{leaseRequested=false;originalStartWitness=false;pendingStopId=null;return;}
113
if(!Flag(status,"owned")&&result.TryGetProperty("released",out var released)&&released.ValueKind==JsonValueKind.True&&!originalStartWitness)
114
{leaseRequested=false;pendingStopId=null;return;}
115
if(DateTimeOffset.UtcNow>=until)throw new FlightTelemetryUnavailableException("Waiting for the owned SCO to report its real stopped state.");
116
await Task.Delay(15,token);
117
}
118
}
119
}
@@ -9,7 +9,7 @@ namespace XFEEDToolBox.Core.Navigation;
9
9
public sealed class NavigationPipeClient(int processId,string service="Navigation")
10
10
{
11
11
public int ProcessId { get; }=processId>0?processId:throw new ArgumentOutOfRangeException(nameof(processId));
12
private readonly string serviceName=service is "Navigation" or "Interaction" or "SessionRecovery0514" or "SessionRecovery0514c" or "SessionRecovery0514d" or "SessionRecovery0514e" or "SessionRecovery0514f" or "SessionRecovery0514g" or "CruiseObservation0514a" or "CruiseObservation0514b" or "CruiseObservation0514c"?service:throw new ArgumentException("Unknown game service.",nameof(service));
12
private readonly string serviceName=service is "Navigation" or "Interaction" or "SessionRecovery0514" or "SessionRecovery0514c" or "SessionRecovery0514d" or "SessionRecovery0514e" or "SessionRecovery0514f" or "SessionRecovery0514g" or "CruiseObservation0514a" or "CruiseObservation0514b" or "CruiseObservation0514c" or "CruiseControl0514a" or "CruiseControl0514b" or "CruiseControl0514c"?service:throw new ArgumentException("Unknown game service.",nameof(service));
13
13
private readonly SemaphoreSlim gate=new(1,1);
14
14
public async Task<JsonElement> CallAsync(object request,CancellationToken token=default)
15
15
{
@@ -19,7 +19,7 @@ public sealed class NavigationPipeClient(int processId,string service="Navigatio
19
19
using var deadline=CancellationTokenSource.CreateLinkedTokenSource(token);deadline.CancelAfter(TimeSpan.FromMilliseconds(500));
20
20
var data=JsonSerializer.SerializeToUtf8Bytes(request,ToolboxSettings.JsonOptions);
21
21
using(var guardRequest=JsonDocument.Parse(data))
22
if(guardRequest.RootElement.TryGetProperty("op",out var operation)&&operation.GetString() is "apply" or "local.apply" or "local.action" or "fsd" or "fsd.command" or "input.set" or "calibrate.axes" or "calibrate.fsd" or "inputs.set" or "commerce.trade" or "commerce.trade.batch" or "cg.join" or "station.services" or "docking.validation.arm" or "session.recovery.leave" or "docking.reset.arm")
22
if(guardRequest.RootElement.TryGetProperty("op",out var operation)&&operation.GetString() is "apply" or "local.apply" or "local.action" or "fsd" or "fsd.command" or "input.set" or "calibrate.axes" or "calibrate.fsd" or "inputs.set" or "commerce.trade" or "commerce.trade.batch" or "cg.join" or "station.services" or "docking.validation.arm" or "session.recovery.leave" or "docking.reset.arm" or "cruise.set")
23
23
XFEEDToolBox.Core.Session.NativeFlightTestProtection.CheckBeforeInput(ProcessId);
24
24
if(data.Length>=65535)throw new InvalidDataException("导航指令超出消息上限。");
25
25
using var pipe=new NamedPipeClientStream(".",$"XFEEDToolBox.{serviceName}.{ProcessId}",PipeDirection.InOut,PipeOptions.Asynchronous);
@@ -0,0 +1,18 @@
1
namespace XFEEDToolBox.Core.Navigation;
2
3
public sealed partial class NativeSystemFlightControlDriver
4
{
5
private NativeCruiseControlChannel? originalCruiseControl;
6
private Func<double>? originalCruiseFuelReserve;
7
public NativeCruiseControlChannel? CruiseControl=>originalCruiseControl;
8
public async Task ConfigureOriginalCruiseControlAsync(Func<double> fuelReserve,bool allowUnqualifiedEmptyTrial=false,CancellationToken token=default)
9
{
10
if(useNativeSupercruiseAssist)throw new InvalidOperationException("Original SCO requires toolbox navigation with game SCA disabled.");
11
var state=player();if(allowUnqualifiedEmptyTrial&&(!state.InGame||!state.Docked||state.CurrentShipId is null||state.CargoCount!=0))throw new InvalidOperationException("Unqualified SCO testing requires the empty ship to remain docked during setup.");
12
var channel=new NativeCruiseControlChannel(processId,gameHash);var capabilities=await channel.InspectAsync(token);
13
if(!capabilities.Available||!allowUnqualifiedEmptyTrial&&!capabilities.Qualified)throw new NotSupportedException("Original SCO controls have not completed live qualification.");
14
originalCruiseControl=channel;originalCruiseFuelReserve=fuelReserve;
15
}
16
private Task UpdateOriginalCruiseAsync(SystemFlightDecision decision,SystemFlightFrame frame,SystemNavigationTarget target,DateTimeOffset deadline,CancellationToken token)
17
=>originalCruiseControl?.UpdateAsync(owner,checked((ulong)generation),frame,decision,target,originalCruiseFuelReserve!(),deadline,token)??Task.CompletedTask;
18
}
@@ -264,6 +264,7 @@ public sealed partial class NativeSystemFlightControlDriver(int processId,string
264
264
if(ensureFlightProtection is not null)await ensureFlightProtection(token);
265
265
if(selected is null||!selected.SameIdentity(target)||last?.Sequence!=reference.Sequence)throw new InvalidOperationException("驾驶指令与目标或飞行帧不一致。");
266
266
if(expiresAt<=DateTimeOffset.UtcNow)throw new FlightTelemetryUnavailableException("正在刷新星系内飞行数据。");
267
await UpdateOriginalCruiseAsync(decision,reference,target,expiresAt,token);
267
268
if(decision.Actions.HasFlag(SystemNavigationAction.ExitForFailedHandoff))
268
269
{
269
270
emergencyExited=await Session.NativeFlightTestProtection.HandleFailedHandoffAsync(processId);
@@ -405,9 +406,10 @@ public sealed partial class NativeSystemFlightControlDriver(int processId,string
405
406
}
406
407
}
407
408
public async Task SuspendSystemFlightAsync()
408
{attitude.Reset();await pipe.CallAsync(new{op="local.suspend",owner});}
409
{if(originalCruiseControl is not null)await originalCruiseControl.ReleaseAsync();attitude.Reset();await pipe.CallAsync(new{op="local.suspend",owner});}
409
410
public async Task ReleaseSystemFlightAsync()
410
411
{
412
if(originalCruiseControl is not null)await originalCruiseControl.ReleaseAsync();
411
413
await CancelOriginalDockingResetAsync();
412
414
if(emergencyExited){attitude.Reset();ownsCruiseAssist=ownsDockingAssist=false;ownsLaunch=false;return;}
413
415
try
@@ -0,0 +1,42 @@
1
namespace XFEEDToolBox.Core.Navigation;
2
3
public sealed record OriginalCruiseCycleIdentity(string Owner,string StartRequestId,ulong SelectionGeneration,ulong Entity,ulong World);
4
public sealed record OriginalCruiseCycleEvidence(OriginalCruiseCycleIdentity Identity,int ActiveSamples,ulong EnableAt,ulong LastActiveAt,string StopRequestId,ulong StopAt,ulong OffAt);
5
6
/// <summary>A native qualification belongs to one original enable, five distinct updates and an explicit original stop.</summary>
7
public sealed class OriginalCruiseCycleQualification
8
{
9
private OriginalCruiseCycleIdentity? current;
10
private ulong enableAt,lastActiveAt,stopAt;
11
private string stopRequestId="";
12
public int ActiveSamples{get;private set;}
13
public OriginalCruiseCycleEvidence? Completed{get;private set;}
14
public bool Qualified=>Completed is not null;
15
private static bool Key(string value)=>value.Length==32&&value.All(c=>c is >= '0' and <= '9' or >= 'a' and <= 'f');
16
public void ForgetCurrent(){current=null;enableAt=lastActiveAt=stopAt=0;stopRequestId="";ActiveSamples=0;}
17
public bool Begin(OriginalCruiseCycleIdentity identity,ulong at,bool originalCalled,int stateAfter)
18
{
19
ForgetCurrent();
20
if(!originalCalled||stateAfter is not(1 or 2)||at==0||!Key(identity.Owner)||!Key(identity.StartRequestId)
21
||identity.SelectionGeneration==0||identity.Entity==0||identity.World==0)return false;
22
current=identity;enableAt=at;return true;
23
}
24
public bool Stop(OriginalCruiseCycleIdentity identity,string requestId,ulong at,bool originalCalled,int stateBefore,int stateAfter,bool explicitlyReleased)
25
{
26
stopAt=0;stopRequestId="";
27
if(current!=identity||!originalCalled||!explicitlyReleased||!Key(requestId)||stateBefore is not(1 or 2)||stateAfter is not(0 or 3)
28
||at<enableAt||at<lastActiveAt||ActiveSamples<5)return false;
29
stopRequestId=requestId;stopAt=at;return true;
30
}
31
public void Observe(ulong entity,ulong world,ulong at,int actualState)
32
{
33
if(current is null||entity!=current.Entity||world!=current.World||at<enableAt)return;
34
if(stopAt==0)
35
{
36
if(actualState==2&&at>lastActiveAt){lastActiveAt=at;ActiveSamples++;}
37
return;
38
}
39
if(actualState is 0 or 3&&at>stopAt)
40
Completed=new(current,ActiveSamples,enableAt,lastActiveAt,stopRequestId,stopAt,at);
41
}
42
}
@@ -7,6 +7,16 @@ public static class OriginalFuelConsumptionBounds
7
7
// This is an energy conversion, not an assumed kg/tonne or hours/second factor.
8
8
public const double OriginalEnergyConversion=45000;
9
9
10
public static bool TryConvertRange(OriginalCurveRange energyCurve,double moduleMultiplier,double fuelConversion,
11
out OriginalCurveRange tonnesPerSecond)
12
{
13
tonnesPerSecond=default;
14
if(!TryConvert(energyCurve,moduleMultiplier,fuelConversion,out var upper))return false;
15
var lower=Math.Max(0,energyCurve.Minimum*moduleMultiplier/(fuelConversion*OriginalEnergyConversion)*(1-1e-5)-1e-10);
16
if(!double.IsFinite(lower)||lower>upper)return false;
17
tonnesPerSecond=new(lower,upper);return true;
18
}
19
10
20
public static bool TryConvert(OriginalCurveRange energyCurve,double moduleMultiplier,double fuelConversion,
11
21
out double upperTonnesPerSecond)
12
22
{
@@ -0,0 +1,36 @@
1
using XFEEDToolBox.Core.Navigation;
2
using XFEExtension.NetCore.XUnit.Attributes;
3
namespace XFEEDToolBox.Test;
4
5
[TestFixture]
6
public class CruiseOverchargeNativeGuardTests
7
{
8
private static CruiseOverchargeGateFrame Good=>new(){IdentityVerified=true,GeometryVerified=true,TargetVerified=true,InSupercruise=true,NativeCanEngage=true,
9
RawFsdState=2,OverchargeState=0,ObservedAt=1000,Hull=1,Heat=40,HeatRise=0,MainFuel=32,FuelCapacity=32,FuelRateUpper=1,RequiredFuelReserve=8,Distance=1e12,Speed=1e6,AimErrorDegrees=0};
10
private static string? Refuse(CruiseOverchargeGateFrame? frame=null,ulong now=1100,ulong deadline=1250)=>CruiseOverchargeNativeGuard.Refusal(frame??Good,now,deadline);
11
[Test]public void VerifiedOriginalDistantAlignedShipIsAllowed()=>Check.That(Refuse() is null);
12
[Test]public void ALeaseCannotOutliveItsOriginalDeadline()
13
{Check.Equal("expired",Refuse(now:1250));Check.Equal("expired",Refuse(deadline:1351));Check.Equal("expired",Refuse(Good with{ObservedAt=1101}));}
14
[Test]public void IdentityTargetAndAssistChangesStopBoost()
15
{Check.That(Refuse(Good with{TargetVerified=false}) is not null);Check.That(Refuse(Good with{IdentityVerified=false}) is not null);Check.That(Refuse(Good with{AssistEnabled=true}) is not null);}
16
[Test]public void NormalSpaceUnknownFsdAndCoolingCannotStart()
17
{Check.That(Refuse(Good with{InSupercruise=false}) is not null);Check.That(Refuse(Good with{RawFsdState=3}) is not null);Check.That(Refuse(Good with{OverchargeState=3}) is not null);}
18
[Test]public void AcknowledgementDoesNotReplaceOriginalReadiness()
19
{Check.That(Refuse(Good with{NativeCanEngage=false}) is not null);Check.That(Refuse(Good with{GeometryVerified=false}) is not null);}
20
[Test]public void DamageAndCollisionStopBoostImmediately()
21
{Check.That(Refuse(Good with{Hull=.49}) is not null);Check.That(Refuse(Good with{Emergency=true}) is not null);Check.That(Refuse(Good with{CollisionCourse=true}) is not null);}
22
[Test]public void RisingHeatAndUnknownSlopeStopBeforeCeiling()
23
{Check.Equal("heat",Refuse(Good with{Heat=50,HeatRise=12}));Check.Equal("heat",Refuse(Good with{HeatRise=double.NaN}));Check.Equal("heat",Refuse(Good with{Heat=75,OverchargeState=2}));}
24
[Test]public void LargeShipsUseTheirOwnFuelRateAndReserve()
25
{Check.That(Refuse(Good with{MainFuel=128,FuelCapacity=128,FuelRateUpper=12,RequiredFuelReserve=70}) is null);Check.Equal("fuel",Refuse(Good with{MainFuel=14,FuelRateUpper=4}));}
26
[Test]public void UnknownFuelOrGeometryCannotEnable()
27
{Check.Equal("fuel",Refuse(Good with{FuelRateUpper=double.NaN}));Check.Equal("approach-or-alignment",Refuse(Good with{Distance=1e8}));Check.Equal("approach-or-alignment",Refuse(Good with{AimErrorDegrees=4}));}
28
[Test]public void ANewFrameIsEvaluatedAgainstTheTimeAfterItWasSampled()
29
{
30
var observed=Good with{ObservedAt=1101};
31
Check.Equal("expired",Refuse(observed,now:1100));
32
Check.That(Refuse(observed,now:1102) is null);
33
Check.Equal("expired",Refuse(observed,now:1302,deadline:1350));
34
Check.Equal("expired",Refuse(observed,now:1250));
35
}
36
}
@@ -0,0 +1,30 @@
1
using XFEEDToolBox.Core.Navigation;
2
using XFEExtension.NetCore.XUnit.Attributes;
3
namespace XFEEDToolBox.Test;
4
5
[TestFixture]
6
public class OriginalCruiseCycleQualificationTests
7
{
8
private static readonly OriginalCruiseCycleIdentity Id=new(new('a',32),new('b',32),4,12,24);
9
private static readonly string StopId=new('c',32);
10
private static OriginalCruiseCycleQualification Started(int count=5)
11
{var proof=new OriginalCruiseCycleQualification();Check.That(proof.Begin(Id,1000,true,1));for(var i=1;i<=count;i++)proof.Observe(12,24,(ulong)(1000+i*20),2);return proof;}
12
[Test]public void OneOriginalCycleRequiresFiveUpdatesAndLaterOff()
13
{var proof=Started();Check.That(proof.Stop(Id,StopId,1120,true,2,3,true));Check.That(!proof.Qualified);proof.Observe(12,24,1140,3);Check.That(proof.Qualified);Check.Equal(5,proof.Completed!.ActiveSamples);}
14
[Test]public void DuplicateAndReversedTimestampsCannotCountAsFiveUpdates()
15
{var proof=Started(0);for(var i=0;i<8;i++){proof.Observe(12,24,1020,2);proof.Observe(12,24,1010,2);}Check.Equal(1,proof.ActiveSamples);Check.That(!proof.Stop(Id,StopId,1120,true,2,0,true));}
16
[Test]public void AnotherShipOrReferenceCannotSupplyActiveFrames()
17
{var proof=Started(0);for(var i=1;i<8;i++){proof.Observe(13,24,(ulong)(1000+i*20),2);proof.Observe(12,25,(ulong)(1000+i*20),2);}Check.Equal(0,proof.ActiveSamples);}
18
[Test]public void ASecondEnableCannotBorrowTheEarlierCyclesFrames()
19
{var proof=Started();var next=Id with{StartRequestId=new('d',32)};Check.That(proof.Begin(next,1200,true,1));Check.Equal(0,proof.ActiveSamples);Check.That(!proof.Stop(next,StopId,1250,true,2,0,true));}
20
[Test]public void AcknowledgedEnableWithoutOriginalCallCannotQualify()
21
{var proof=new OriginalCruiseCycleQualification();Check.That(!proof.Begin(Id,1000,false,1));for(var i=1;i<8;i++)proof.Observe(12,24,(ulong)(1000+i*20),2);Check.That(!proof.Stop(Id,StopId,1200,true,2,0,true));}
22
[Test]public void StopMustMatchOwnerRequestSelectionAndShip()
23
{foreach(var different in new[]{Id with{Owner=new('d',32)},Id with{StartRequestId=new('d',32)},Id with{SelectionGeneration=5},Id with{Entity=13},Id with{World=25}}){var proof=Started();Check.That(!proof.Stop(different,StopId,1120,true,2,0,true));proof.Observe(12,24,1140,0);Check.That(!proof.Qualified);}}
24
[Test]public void IdleObservationAndGlobalDisableCountsCannotReplaceAnOriginalStop()
25
{var proof=Started();Check.That(!proof.Stop(Id,StopId,1120,false,0,0,true));proof.Observe(12,24,1140,0);Check.That(!proof.Qualified);}
26
[Test]public void AutomaticExpiryIsNotTheExplicitReleaseQualification()
27
{var proof=Started();Check.That(!proof.Stop(Id,StopId,1120,true,2,3,false));proof.Observe(12,24,1140,3);Check.That(!proof.Qualified);}
28
[Test]public void OffMustFollowTheCallAndBelongToTheSameMainShip()
29
{var proof=Started();Check.That(proof.Stop(Id,StopId,1120,true,2,3,true));proof.Observe(12,24,1120,3);proof.Observe(13,24,1140,3);proof.Observe(12,25,1140,3);Check.That(!proof.Qualified);proof.Observe(12,24,1160,0);Check.That(proof.Qualified);}
30
}
@@ -6,6 +6,13 @@ namespace XFEEDToolBox.Test;
6
6
[TestFixture]
7
7
public class OriginalFuelConsumptionBoundsTests
8
8
{
9
[Test]public void OriginalCurveLowerAndUpperAreBothConverted()
10
{
11
Check.That(OriginalFuelConsumptionBounds.TryConvertRange(new(450,900),2,.5,out var range));
12
Check.That(range.Minimum>.03999&&range.Minimum<.04&&range.Maximum>.08&&range.Maximum<.08001);
13
}
14
[Test]public void RoundingCannotTurnAZeroCurveMinimumIntoNegativeFuel()
15
{Check.That(OriginalFuelConsumptionBounds.TryConvertRange(new(-1e-5,1),45000,1,out var range));Check.That(range.Minimum==0&&range.Maximum>1);}
9
16
[Test]public void OriginalEnergyUnitsAreConvertedUsingTheShipsFuelParameter()
10
17
{
11
18
Check.That(OriginalFuelConsumptionBounds.TryConvert(new(450,900),2,.5,out var rate));
@@ -2,6 +2,19 @@
2
2
3
3
更新:2026-10-24(Asia/Shanghai)。最新状态以紧接下方的记录为准,后文保留历史故障及验收过程。
4
4
5
## SCO首次实飞完成SC入口,原期限检查顺序已修正(2026-10-24 16:38)
6
7
- b(SHAd74dcdd766e36cfee679ee8e4e36108216ad0c614de9f15f349a6d1a2cc2c868)已实际加载且8次2047自测/协议通过;a仅停用自身hook。b将qualification限定同owner/startRequest/selection/实体/参考系内五个不同post-update采样、显式原false回执及之后Off,9项反例通过,不使用全局启停累计量拼接验收。
8
- 子智能体唯一95792/新ticks639284275904552388执行sco-control-0514b-01:修正后的原bodyAddress→已统一系centre预检通过、原维护0Cr、真实起飞、SC入口及对准A3通过;首SCO请求被expired拒绝,enableCalls=0/Active未成立。原失败保留;原菜单与同PID Solo真实恢复已通过,原recovery-solo.json为证据,不冒充已停靠。恢复新实体11823869650069/空舱/健康100%、Homer约295.6km,正在完成同轮软件SC/g返站及维护,不发第二脉冲。
9
- 拒绝根因已定位为PollControl先取now,再采样得到更新的frame.utcMilliseconds,误触发observedAt>now;实际时间线只过约43ms,非250ms预算耗尽。候选c在完整采样后重新取now,同时保留前置过期、200ms样本、250ms意图及调用前截止复检;10项守卫回归和AOT/CLI构建通过。c已构建SHA78547c29142a9e0d2729f2b76865277a421d0dfe7593a4290162ac5697daf941,尚未部署,不覆盖已加载b。
10
- 首a轮预检的误拒因目录Position在站体系、geometry在统一星系系,现以同bodyAddress原centre及原distance核对,50ls/原误差/排除裕量不变。原数据仅诊断重放找到21个清晰远目标,明确不能用拟合坐标驾驶或将重建header当实机证据。
11
- 子智能体另修本次CLI局部日志未知NaN导致writer fault/最后Save未执行的问题;真实恢复将写独立reconciliation,不覆盖旧失败。保护87996持续,游戏88060未终止。持久.75、Polyakov连续、SCO真实启停、原逃逸矢量与生产自主贸易均仍待验收,禁止新采购;无定时任务/键鼠。
12
## SCO原生控制已部署,首次空载预检拒绝(2026-10-24 16:26)
13
14
- 仍为游戏88060/同启动身份及唯一独立保护87996;交回前原始Homer ready/serviceReady/fresh/healthy、state5、空舱实体11823841535223均确认,保护ready126ms。c仅停用自身hook,g和基础14包保持。新独立CruiseControl0514a已实际装入,SHA478b841ae633862dd918c53f1d3ca519bafb6a15c4e808955094b8f535c02eea;8次1023原生自测及ABI/实际只读协议通过,控制未qualified,尚无SCO输入。
15
- 原生控制只在本主船更新线程、匹配控制owner/原目标/实体/参考系、250ms以内意图下调用原2EAACD0;热量预测、原始燃油区间/实际储备、避障、受损及5秒单段上限保持。独立原false调用回执与之后实际Off采样分开记录,Core先发布回执再发布同响应观测。没有SCA、直接修改SCO状态或账户字段。
16
- 子智能体唯一runner97292已执行sco-control-0514a-01并终止,原result.passed=false保留:停靠预检未找到通过原目录/距离/排除区核验的至少50ls目标。maintainedBefore/launched/entered/activeFive均false,没有起飞、SCO脉冲或新增订单;不把准备或自测当实飞。正在离线分析已存catalogue/geometry拒绝原因,不能原样重飞或臆造坐标。
17
- 75%持久油门及Polyakov连续衔接仍未通过;f的.05归零与g另轮初始0接管不得拼接为该验收。SCO实际启停、拦截原逃逸矢量、生产工具箱自主跑商也仍未完整验收。财务仍净14716005Cr/钱包40108122Cr、CG384,001/002不重放。没有定时任务。
5
18
## 空载超巡至原生停靠完整通过,SCO控制接入仍待完成(2026-10-24 16:01)
6
19
7
20
- c已实际加载到88060,SHA db6ceea2a3bcc7b1da571bbae7f176701b5574e17941d8a7b255e9f1f6aeb297;b仅停用自身SCO hook,g未改。新c隔离原生自测8次255、实际加载/只读协议通过,没有SCO启停能力。
@@ -0,0 +1,10 @@
1
<Project Sdk="Microsoft.NET.Sdk">
2
<PropertyGroup><AssemblyName>XFEEDInteraction</AssemblyName><PluginSymbol>PLUGIN_CRUISE_CONTROL</PluginSymbol></PropertyGroup>
3
<Import Project="../NativePlugin.props" />
4
<ItemGroup>
5
<Compile Include="../../../XFEEDToolBox.Core/Navigation/OriginalCurveBounds.cs" Link="OriginalCurveBounds.cs" />
6
<Compile Include="../../../XFEEDToolBox.Core/Navigation/OriginalFuelConsumptionBounds.cs" Link="OriginalFuelConsumptionBounds.cs" />
7
<Compile Include="../../../XFEEDToolBox.Core/Navigation/CruiseOverchargeNativeGuard.cs" Link="CruiseOverchargeNativeGuard.cs" />
8
<Compile Include="../../../XFEEDToolBox.Core/Navigation/OriginalCruiseCycleQualification.cs" Link="OriginalCruiseCycleQualification.cs" />
9
</ItemGroup>
10
</Project>