using System.Diagnostics;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Channels;
using XFEEDToolBox.Core.Data;
using XFEEDToolBox.Core.Models;
using XFEEDToolBox.Core.Native;
using XFEEDToolBox.Core.Navigation;
using XFEEDToolBox.Core.Session;
using XFEEDToolBox.NativeSupport;
/// <summary>Production driving, thermal stops and re-engagement on one existing clear SC leg.</summary>
internal static class SCOThermalCycleAcceptance
{
private const double LightSecond = 299792458;
private static readonly JsonSerializerOptions EvidenceJson = new(ToolboxSettings.JsonOptions)
{ NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals };
private static readonly JsonSerializerOptions EventJson = new(EvidenceJson) { WriteIndented = false };
private static bool True(JsonElement value, string key) => value.TryGetProperty(key, out var item) && item.ValueKind == JsonValueKind.True;
private static long At(JsonElement value) => value.GetProperty("observedAtUnixMilliseconds").GetInt64();
private static string Text(JsonElement value, string key) => value.GetProperty(key).GetString()!;
private static Vector3d Vector(JsonElement value, string key)
{ var array = value.GetProperty(key); return new(array[0].GetDouble(), array[1].GetDouble(), array[2].GetDouble()); }
private static string Cargo(PlayerState state) => JsonSerializer.Serialize(state.Inventory
.OrderBy(item => item.Key, StringComparer.Ordinal).ThenBy(item => item.MissionId)
.Select(item => new { item.Key, item.Count, item.Stolen, item.MissionId, item.Group }), ToolboxSettings.JsonOptions);
private sealed class Cycle(JsonElement enable)
{
public JsonElement Enable { get; } = enable.Clone();
public Dictionary<string, JsonElement> Renewals { get; } = new(StringComparer.Ordinal);
public SortedDictionary<long, JsonElement> Active { get; } = new();
public SortedDictionary<long, JsonElement> StoppedHeatSamples { get; } = new();
public long? ReenabledAt { get; set; }
public JsonElement? Stop { get; set; }
public JsonElement? Off { get; set; }
public CruiseOverchargeDecision? PolicyAtStop { get; set; }
public CruiseOverchargeObservation? BeforeEnableObservation { get; init; }
public CruiseOverchargeDecision? BeforeEnablePolicy { get; init; }
public bool CoolingObserved { get; set; }
public double ObservedCoolingPercent => StoppedHeatSamples.Count > 1
? StoppedHeatSamples.First().Value.GetProperty("heatPercent").GetDouble()
- StoppedHeatSamples.Values.Min(sample => sample.GetProperty("heatPercent").GetDouble()) : 0;
public bool ActualCoolingObserved => ObservedCoolingPercent >= .05;
public bool HeatLimited => Stop is { } stop && (Text(stop, "reason") == "heat" || PolicyAtStop?.Reason == CruiseOverchargeReason.Heat);
public double ActiveSeconds => Active.Count > 1 ? (Active.Keys.Last() - Active.Keys.First()) / 1000d : 0;
public bool Matches(JsonElement value) => Text(value, "owner") == Text(Enable, "owner")
&& value.GetProperty("selectionGeneration").GetUInt64() == Enable.GetProperty("selectionGeneration").GetUInt64()
&& Text(value, "entityId") == Text(Enable, "entityId") && Text(value, "referenceWorldAddress") == Text(Enable, "referenceWorldAddress");
public bool Complete => Stop is { } stop && Off is { } off && True(stop, "nativeMethodCalled") && !True(stop, "argument")
&& Text(stop, "nativeMethodRva") == "2eaacd0" && Text(stop, "startRequestId") == Text(Enable, "requestId")
&& Matches(stop) && stop.GetProperty("owningThread").GetInt32() == Enable.GetProperty("owningThread").GetInt32()
&& stop.GetProperty("stateAfter").GetInt32() is 0 or 3 && At(stop) >= At(Enable)
&& (Active.Count == 0 || At(stop) >= Active.Keys.Last()) && At(off) > At(stop)
&& off.GetProperty("state").GetInt32() is 0 or 3 && !True(off, "active");
public bool Qualified => Complete && Active.Count >= 5 && Renewals.Count >= 5;
public object Evidence => new { startRequestId = Text(Enable, "requestId"), enable = Enable,
renewals = Renewals.Values, renewalCount = Renewals.Count, activeSamples = Active.Values, activeSampleCount = Active.Count,
activeSeconds = ActiveSeconds, stop = Stop, policyAtStop = PolicyAtStop, subsequentOff = Off, coolingObserved = CoolingObserved,
heatLimited = HeatLimited, stoppedHeatSamples = StoppedHeatSamples.Values, observedCoolingPercent = ObservedCoolingPercent,
actualCoolingObserved = ActualCoolingObserved, reenabledAtUnixMilliseconds = ReenabledAt,
beforeEnableObservation = BeforeEnableObservation, beforeEnablePolicy = BeforeEnablePolicy, complete = Complete, qualified = Qualified };
}
internal static async Task RunAsync(int pid, string directory)
{
directory = Path.GetFullPath(directory);
if (Directory.Exists(directory)) throw new IOException("Use a fresh SCO thermal-cycle evidence directory.");
var parent = Path.GetDirectoryName(directory)!; Directory.CreateDirectory(parent);
using var exclusive = new FileStream(Path.Combine(parent, "docking-fixture-wait.lock"), FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
Directory.CreateDirectory(directory);
var start = DateTimeOffset.UtcNow; var flightUntil = start.AddSeconds(60);
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(65));
using var sentinel = new Timer(_ => { if (File.Exists(Path.Combine(directory, "stop"))) timeout.Cancel(); }, null, 100, 100);
var token = timeout.Token;
using var process = Process.GetProcessById(pid); var processStarted = process.StartTime.ToUniversalTime().Ticks;
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(token);
var initial = journal.State; var initialCargo = Cargo(initial);
var rawPipe = new NavigationPipeClient(pid); var cruisePipe = new NavigationPipeClient(pid, NativeCruiseControlChannel.Service);
var previousGuardian = PersistentProtectionProcess.Current;
FlightTestProtectionScope? protection = null; NativeSystemFlightControlDriver? driver = null; SystemNavigationTarget? target = null;
SystemFlightFrame? firstFrame = null, lastFrame = null;
JsonElement? inventoryBefore = null, inventoryAfter = null, finalRaw = null, finalCruise = null;
string phase = "preflight", stopReason = "not-started";
string? failure = null, releaseFailure = null, writerFailure = null;
var cycles = new List<Cycle>(); var receipts = new List<JsonElement>(); var stops = new List<JsonElement>();
var recoveryCount = 0; var zeroThrottleObserved = false; var targetPreserved = false; var overallPassed = false; var inputSubmitted = false;
double reserve = double.NaN, maximumSpeed = 0, maximumHeat = 0, minimumDistance = double.PositiveInfinity;
var events = Channel.CreateUnbounded<object>(new() { SingleReader = true });
await using var log = new StreamWriter(new FileStream(Path.Combine(directory, "frames.jsonl"), FileMode.CreateNew, FileAccess.Write, FileShare.Read));
var writer = Task.Run(async () =>
{
try
{
await foreach (var value in events.Reader.ReadAllAsync())
{ await log.WriteLineAsync(JsonSerializer.Serialize(value, EventJson)); await log.FlushAsync(); }
}
catch (Exception error) { writerFailure = error.ToString(); timeout.Cancel(); }
});
void Record(object value)
{ if (!events.Writer.TryWrite(value)) throw new IOException("SCO thermal-cycle evidence writer stopped."); }
void Session(CancellationToken cancellation)
{
cancellation.ThrowIfCancellationRequested(); process.Refresh(); var state = journal.State;
if (process.HasExited || process.StartTime.ToUniversalTime().Ticks != processStarted || !state.InGame || state.GameMode != "Solo"
|| state.LoadGameAt != initial.LoadGameAt || state.CommanderId != initial.CommanderId || state.CurrentShipId != initial.CurrentShipId
|| state.Navigation.CurrentSystemAddress != initial.Navigation.CurrentSystemAddress || state.Docked || !state.CargoReliable
|| state.CargoCount != initial.CargoCount || state.CargoCapacity != initial.CargoCapacity || Cargo(state) != initialCargo
|| NavigationSafety.HasExternalThreat(state) || NavigationSafety.UnhealthyHull(state) || TravelPreparation.HasDeployedEquipment(state))
throw new InvalidOperationException("SCO thermal-cycle session, cargo, ship, equipment, system or safety identity changed.");
if (writerFailure is not null) throw new IOException(writerFailure);
}
Task Protection(CancellationToken cancellation)
{
Session(cancellation);
if (protection?.Stopped == true) throw new InvalidOperationException(protection.LastIssue ?? "Flight protection stopped.");
NativeFlightTestProtection.CheckBeforeInput(pid); return Task.CompletedTask;
}
async Task<SystemFlightFrame> Read(CancellationToken cancellation)
{
Session(cancellation);
var raw = await rawPipe.CallAsync(new { op = "read" }, cancellation);
Record(new { at = DateTimeOffset.UtcNow, phase, kind = "raw-navigation-and-input-diagnostics", raw });
if (True(raw, "playerOverride") || !True(raw, "inGame")
|| raw.TryGetProperty("shipHealth", out var health) && True(health, "emergency"))
throw new InvalidOperationException("Original navigation reports player takeover, scene loss or emergency.");
var frame = await driver!.ReadSystemFlightAsync(cancellation); await Protection(cancellation);
Record(new { at = DateTimeOffset.UtcNow, phase, kind = "local-frame", frame, local = driver.LastTelemetry });
if (frame.Mode != LocalFlightMode.Supercruise || frame.PlayerOverride || frame.ExternalThreat || frame.TargetObscured || frame.CollisionCourse
|| frame.HeatPercent is not { } heat || !double.IsFinite(heat) || heat >= CruiseOverchargePolicy.MaximumHeatPercent
|| firstFrame is { } pinned && (frame.ShipEntity != pinned.ShipEntity || frame.ReferenceWorld != pinned.ReferenceWorld || frame.LoadoutFingerprint != pinned.LoadoutFingerprint)
|| frame.SupercruiseAssist is NativeAssistState.Engaged or NativeAssistState.Requested or NativeAssistState.Aligning)
throw new InvalidOperationException("The unchanged, clear, healthy SC leg is no longer available.");
var assist = driver.LastTelemetry!.Value.GetProperty("supercruiseAssist");
if (True(assist, "enabled") || True(assist, "engaged")) throw new InvalidOperationException("Game SCA acquired control during the thermal-cycle proof.");
ClearRay(frame, raw);
lastFrame = frame; maximumSpeed = Math.Max(maximumSpeed, frame.Speed); maximumHeat = Math.Max(maximumHeat, heat);
minimumDistance = Math.Min(minimumDistance, frame.TargetDistance); return frame;
}
async Task Apply(SystemFlightFrame frame, SystemFlightDecision decision, CancellationToken cancellation)
{
if (DateTimeOffset.UtcNow - frame.ObservedAt > TimeSpan.FromMilliseconds(150))
throw new FlightTelemetryUnavailableException("Proof input source is older than 150ms.");
var deadline = frame.ObservedAt.AddMilliseconds(250);
if (phase != "release-sco-zero-throttle-and-release-inputs" && deadline > flightUntil) deadline = flightUntil;
if (deadline <= DateTimeOffset.UtcNow) throw new TimeoutException("The 60-second flight budget is exhausted.");
inputSubmitted = true;
await driver!.ApplySystemFlightAsync(decision, frame, target!, deadline, cancellation);
}
void RequireRecoverableStop()
{
if (stops.Count == 0) return;
var reason = Text(stops[^1], "reason");
if (reason is not ("heat" or "native-refusal" or "native-telemetry" or "released" or "expired" or "lease-expired" or "no-lease"))
throw new InvalidOperationException("SCO thermal-cycle proof reached a non-recoverable native boundary: " + reason);
}
void CaptureStatus(JsonElement value)
{
Record(new { at = DateTimeOffset.UtcNow, phase, kind = "original-sco-status", value });
if (firstFrame is null || !True(value, "sampleReady") || !value.TryGetProperty("sample", out var sample) || sample.ValueKind != JsonValueKind.Object
|| !True(sample, "verified") || Text(sample, "entityId") != firstFrame.ShipEntity.ToString()
|| Text(sample, "referenceWorldAddress") != firstFrame.ReferenceWorld.ToString()) return;
var at = At(sample); var state = sample.GetProperty("state").GetInt32();
foreach (var cycle in cycles)
{
if (at < At(cycle.Enable)) continue;
if (state == 2 && (cycle.Stop is not { } stopped || at <= At(stopped))) cycle.Active.TryAdd(at, sample.Clone());
if (!True(value, "owned") && state is 0 or 3 && cycle.Stop is { } stop && at > At(stop))
{
cycle.Off ??= sample.Clone();
cycle.CoolingObserved |= state == 3 || !True(sample, "nativeCanEngage");
if (cycle.ReenabledAt is null || at < cycle.ReenabledAt)
cycle.StoppedHeatSamples.TryAdd(at, sample.Clone());
}
}
}
double CumulativeActive() => cycles.Where(cycle => cycle.Qualified).Sum(cycle => cycle.ActiveSeconds);
bool ThermalRestartVerified() => cycles.Zip(cycles.Skip(1)).Any(pair => pair.First.Qualified && pair.Second.Qualified
&& pair.First.HeatLimited && pair.First.CoolingObserved && pair.First.ActualCoolingObserved
&& pair.First.Off is { } off && At(off) < At(pair.Second.Enable)
&& pair.Second.BeforeEnableObservation is { OriginalCanEngage: true, State: CruiseOverchargeState.Idle }
&& pair.Second.BeforeEnablePolicy is { UseOvercharge: true, Reason: CruiseOverchargeReason.Ready });
bool EnoughCycles() => cycles.Count(cycle => cycle.Qualified) >= 2 && CumulativeActive() >= 2 && ThermalRestartVerified();
async Task<JsonElement> Inventory(CancellationToken cancellation)
{
var inventory = await rawPipe.CallAsync(new { op = "commerce.inventory" }, cancellation);
Record(new { at = DateTimeOffset.UtcNow, phase, kind = "actual-inventory", inventory });
if (!True(inventory, "verified") || inventory.GetProperty("count").GetInt64() != initial.CargoCount
|| inventory.GetProperty("capacity").GetInt64() != initial.CargoCapacity
|| firstFrame is { } frame && Text(inventory, "entityId") != frame.ShipEntity.ToString())
throw new InvalidDataException("Original cargo count/capacity differs from the unchanged Journal inventory.");
return inventory.Clone();
}
try
{
if (!initial.InGame || initial.Docked || initial.CurrentShipId is null || initial.GameMode != "Solo" || !initial.CargoReliable
|| initial.Navigation.CurrentSystemAddress <= 0 || NavigationSafety.HasExternalThreat(initial) || NavigationSafety.UnhealthyHull(initial)
|| TravelPreparation.HasDeployedEquipment(initial)) throw new InvalidOperationException("Healthy Solo SC and known unchanged cargo are required.");
using var source = new NativeCurrentSystemSource(pid, NativeSystemLocationReader.GameSha256, () => journal.State);
var catalogue = await source.ReadLocationsAsync(token);
var row = catalogue.Locations.SingleOrDefault(location => location.Selected)
?? throw new InvalidOperationException("An already selected target is required; no replacement target will be chosen.");
if (row.DistanceMetres is not { } distance || !double.IsFinite(distance) || distance < 50_000 * LightSecond
|| row.Category is not (SystemLocationCategory.Station or SystemLocationCategory.Carrier or SystemLocationCategory.Star or SystemLocationCategory.Planet or SystemLocationCategory.LandablePlanet))
throw new InvalidOperationException("The existing clear SC target must be at least 50,000ls away.");
target = new(catalogue.SystemAddress, row.WorldAddress, row.Name,
row.Category is SystemLocationCategory.Station or SystemLocationCategory.Carrier ? SystemTargetKind.Station : SystemTargetKind.Orbit, row.MarketId)
{ NativeKey = row.Key, NativeKind = row.NativeKind, NativeIdentity = row.Identity };
Record(new { at = DateTimeOffset.UtcNow, kind = "selected-existing-target", catalogue, target });
PersistentProtectionProcess.Current = null;
protection = await FlightTestProtectionScope.ArmAsync(pid, exitForFailedHandoff: false, token: token);
driver = new(pid, NativeSystemLocationReader.GameSha256, () => journal.State,
new NativeGameInputChannel(pid, NativeSystemLocationReader.GameSha256, NativeSupportPackage.Directory), Protection, useNativeSupercruiseAssist: false);
await driver.SelectAsync(target, token); // Binds the exact existing identity; no destination change.
firstFrame = await Read(token); inventoryBefore = await Inventory(token);
await driver.PrepareAsync(token);
phase = "align-existing-target-at-zero-throttle";
while (true)
{
var frame = await Read(token); var direction = (frame.TargetPosition - frame.Position).Unit();
await Apply(frame, new(SystemNavigationPhase.Waiting, phase, direction, 0), token);
if (Vector3d.AngleDegrees(frame.Forward, direction) <= 2 && Math.Abs(frame.Throttle) < .001) break;
if (DateTimeOffset.UtcNow >= flightUntil) throw new TimeoutException("Alignment exhausted the 60-second flight budget.");
await Task.Delay(40, token);
}
var prepared = new NativeCruiseControlChannel(pid, NativeSystemLocationReader.GameSha256);
await prepared.PrepareAsync(Path.Combine(NativeSupportPackage.Directory, "CruiseControl"), token);
var ship = initial.Ships.Single(ship => ship.ShipId == initial.CurrentShipId);
if (ship.FuelMain is not > 8 || !double.IsFinite(ship.FuelMain.Value)) throw new InvalidDataException("Verified main tank capacity and fuel reserve are required.");
reserve = Math.Max(8, ship.FuelMain.Value * .25);
await driver.ConfigureOriginalCruiseControlAsync(() => reserve, token: token);
var control = driver.CruiseControl!;
control.Observed += CaptureStatus;
control.OriginalReceipt += value =>
{
receipts.Add(value.Clone()); Record(new { at = DateTimeOffset.UtcNow, phase, kind = "original-sco-receipt", value });
if (True(value, "nativeMethodCalled") && True(value, "argument"))
{
if (cycles.Any(item => Text(item.Enable, "requestId") == Text(value, "requestId"))) return;
if (cycles.Any(item => !item.Complete)) throw new InvalidDataException("A new SCO enable preceded the previous cycle's matched stop and subsequent Off.");
if (cycles.LastOrDefault() is { } previousCycle) previousCycle.ReenabledAt = At(value);
cycles.Add(new(value) { BeforeEnableObservation = control.LastObservation, BeforeEnablePolicy = control.LastDecision });
}
else if (True(value, "applied") && cycles.LastOrDefault() is { } currentCycle && currentCycle.Matches(value))
currentCycle.Renewals.TryAdd(Text(value, "requestId"), value.Clone());
};
control.OriginalStopReceipt += value =>
{
stops.Add(value.Clone()); Record(new { at = DateTimeOffset.UtcNow, phase, kind = "original-sco-stop", value });
var cycle = cycles.SingleOrDefault(item => Text(item.Enable, "requestId") == Text(value, "startRequestId"));
if (cycle is not null && cycle.Matches(value)) { cycle.Stop = value.Clone(); cycle.PolicyAtStop = control.LastDecision; }
};
var navigator = new SystemFlightNavigator(); var handledStops = 0;
phase = "production-driving-and-thermal-recovery"; stopReason = "flight-budget-exhausted";
while (DateTimeOffset.UtcNow < flightUntil)
{
try
{
var frame = await Read(token);
if (EnoughCycles()) { stopReason = "two-qualified-thermal-cycles-and-two-active-seconds"; break; }
RequireRecoverableStop();
if (stops.Count != handledStops)
{
await driver.SuspendSystemFlightAsync(); handledStops = stops.Count; recoveryCount++;
Record(new { at = DateTimeOffset.UtcNow, phase, kind = "native-stop-suspended-and-refreshed", stops = handledStops });
await Task.Delay(40, token); continue;
}
var decision = navigator.Decide(frame, target, DateTimeOffset.UtcNow);
if (decision.Phase is not (SystemNavigationPhase.Supercruise or SystemNavigationPhase.Aligning)
|| (decision.Actions & ~SystemNavigationAction.CancelOwnedAssists) != SystemNavigationAction.None)
throw new InvalidOperationException("The existing SC leg reached an approach, assist or scene-change boundary.");
await Apply(frame, decision, token);
Record(new { at = DateTimeOffset.UtcNow, phase, kind = "production-decision", decision, policy = control.LastDecision, observation = control.LastObservation,
qualifiedCycles = cycles.Count(cycle => cycle.Qualified), cumulativeActiveSeconds = CumulativeActive() });
if (control.LastDecision?.Reason is CruiseOverchargeReason.Fuel or CruiseOverchargeReason.TargetClose)
throw new InvalidOperationException("Production SCO policy reached a real fuel or approach boundary: " + control.LastDecision.Reason);
}
catch (Exception error) when (NavigationRecovery.CanRetry(error, token))
{
Session(token); RequireRecoverableStop(); recoveryCount++;
Record(new { at = DateTimeOffset.UtcNow, phase, kind = "production-recovery", recovery = NavigationRecovery.Kind(error), error = error.ToString() });
await driver.SuspendSystemFlightAsync(); handledStops = stops.Count;
// Preserve this driver/channel and its heat hysteresis; only real
// new observations and native CanEngage may permit another start.
}
await Task.Delay(40, token);
}
}
catch (Exception error) { failure = error.ToString(); Record(new { at = DateTimeOffset.UtcNow, phase, failure }); }
finally
{
phase = "release-sco-zero-throttle-and-release-inputs";
using var cleanup = new CancellationTokenSource(TimeSpan.FromSeconds(8));
try
{
if (driver is not null)
{
if (driver.CruiseControl is { } control) await control.ReleaseAsync(cleanup.Token);
if (protection?.Stopped == false)
{
var frame = await Read(cleanup.Token);
await Apply(frame, new(SystemNavigationPhase.Waiting, phase, null, 0), cleanup.Token);
await Task.Delay(80, cleanup.Token);
zeroThrottleObserved = Math.Abs((await Read(cleanup.Token)).Throttle) < .001;
}
}
}
catch (Exception error) { releaseFailure = error.ToString(); Record(new { at = DateTimeOffset.UtcNow, phase, releaseFailure }); }
finally
{
if (driver is not null)
try { await driver.ReleaseSystemFlightAsync(); }
catch (Exception error) { releaseFailure ??= error.ToString(); Record(new { at = DateTimeOffset.UtcNow, phase, releaseFailure }); }
}
try
{
finalRaw = await rawPipe.CallAsync(new { op = "read" }, cleanup.Token);
finalCruise = await cruisePipe.CallAsync(new { op = "read" }, cleanup.Token); CaptureStatus(finalCruise.Value);
Record(new { at = DateTimeOffset.UtcNow, phase, kind = "final-raw-and-native-status", raw = finalRaw, cruise = finalCruise });
inventoryAfter = await Inventory(cleanup.Token);
using var finalSource = new NativeCurrentSystemSource(pid, NativeSystemLocationReader.GameSha256, () => journal.State);
var final = await finalSource.ReadLocationsAsync(cleanup.Token); var selected = final.Locations.SingleOrDefault(location => location.Selected);
targetPreserved = target is not null && selected?.WorldAddress == target.BodyAddress && selected.NativeKind == target.NativeKind
&& selected.Name == target.Name && selected.MarketId == target.MarketId;
}
catch (Exception error) { releaseFailure ??= error.ToString(); Record(new { at = DateTimeOffset.UtcNow, phase, releaseFailure }); }
try { if (protection is not null) await protection.DisposeAsync(); }
catch (Exception error) { releaseFailure ??= error.ToString(); }
PersistentProtectionProcess.Current = previousGuardian;
events.Writer.TryComplete(); await writer;
var cargoPreserved = journal.State.CargoCount == initial.CargoCount && journal.State.CargoCapacity == initial.CargoCapacity && Cargo(journal.State) == initialCargo;
var inventoryPreserved = inventoryBefore is { } before && inventoryAfter is { } after
&& before.GetProperty("count").GetInt64() == after.GetProperty("count").GetInt64()
&& before.GetProperty("capacity").GetInt64() == after.GetProperty("capacity").GetInt64()
&& before.GetProperty("credits").GetUInt64() == after.GetProperty("credits").GetUInt64();
var activeSamples = cycles.SelectMany(cycle => cycle.Active.Values).OrderBy(At).ToArray();
var firstActiveSpeed = activeSamples.Length > 0 ? activeSamples[0].GetProperty("speedMetresPerSecond").GetDouble() : 0;
var maximumActiveSpeed = activeSamples.Length > 0 ? activeSamples.Max(sample => sample.GetProperty("speedMetresPerSecond").GetDouble()) : 0;
var speedIncreased = maximumActiveSpeed > firstActiveSpeed + Math.Max(1000, firstActiveSpeed * .01);
var originalOff = finalCruise is { } status && !True(status, "owned") && True(status, "sampleReady")
&& status.TryGetProperty("sample", out var sample) && sample.ValueKind == JsonValueKind.Object && sample.GetProperty("state").GetInt32() is 0 or 3
&& cycles.All(cycle => cycle.Complete);
overallPassed = failure is null && releaseFailure is null && writerFailure is null && EnoughCycles() && speedIncreased
&& originalOff && zeroThrottleObserved && cargoPreserved && inventoryPreserved && targetPreserved;
var report = new { passed = overallPassed, pid, processStarted, start, at = DateTimeOffset.UtcNow, maximumFlightSeconds = 60, phase, stopReason,
failure, releaseFailure, writerFailure, initial.CurrentShipId, initial.Ship, initial.CargoCount, initial.CargoCapacity, initialCargo,
finalCargo = Cargo(journal.State), cargoPreserved, inventoryBefore, inventoryAfter, inventoryPreserved, target, targetPreserved,
reserveFuelTonnes = reserve, cycleCount = cycles.Count, qualifiedCycleCount = cycles.Count(cycle => cycle.Qualified), cumulativeActiveSeconds = CumulativeActive(),
minimumActiveSamplesPerCycle = 5, minimumRenewalsPerCycle = 5, requiredCumulativeActiveSeconds = 2, firstActiveSpeed, maximumActiveSpeed, speedIncreased,
maximumSpeed, maximumHeat, initialDistance = firstFrame?.TargetDistance, minimumDistance, finalDistance = lastFrame?.TargetDistance,
observedDistanceProgress = firstFrame is null || !double.IsFinite(minimumDistance) ? (double?)null : firstFrame.TargetDistance - minimumDistance,
thermalRestartVerified = ThermalRestartVerified(), originalOff, zeroThrottleObserved, firstFrame, lastFrame, finalRaw, finalCruise, recoveryCount,
cycles = cycles.Select(cycle => cycle.Evidence), receipts, stops,
inputSubmitted, tradeOrdersSent = 0, serviceOrdersSent = 0, launchCommandsSent = 0, scEntryCommandsSent = 0, targetReplaced = false,
gameSupercruiseAssistUsed = false, crossSystemFlight = false, clientRestarted = false, singleTwentySecondIntervalClaimed = false };
await File.WriteAllTextAsync(Path.Combine(directory, "result.json"), JsonSerializer.Serialize(report, EvidenceJson));
Console.WriteLine(JsonSerializer.Serialize(new { passed = overallPassed, qualifiedCycles = cycles.Count(cycle => cycle.Qualified), cumulativeActiveSeconds = CumulativeActive(),
maximumActiveSpeed, maximumHeat, stopReason, failure, releaseFailure, directory }, EventJson));
}
if (!overallPassed) throw new InvalidOperationException("SCO thermal-cycle proof did not meet its declared requirements; see the preserved report.");
}
private static void ClearRay(SystemFlightFrame frame, JsonElement raw)
{
var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); var geometry = raw.GetProperty("geometry"); var at = At(raw);
if (now - at is < 0 or > 250 || !True(geometry, "fresh") || !True(geometry, "referenceFrameVerified")
|| Text(geometry, "worldAddress") != frame.ReferenceWorld.ToString())
throw new FlightTelemetryUnavailableException("Fresh matching clearance geometry is required.");
var offset = frame.Position - Vector(geometry, "position"); var direction = (frame.TargetPosition - frame.Position).Unit();
var horizon = frame.TargetDistance - 10 * LightSecond;
if (horizon <= 0) throw new InvalidOperationException("The existing target is inside the unchanged SCO approach boundary.");
var bodies = geometry.GetProperty("nearbyBodies");
if (bodies.ValueKind != JsonValueKind.Array || bodies.GetArrayLength() == 0) throw new FlightTelemetryUnavailableException("Original body clearance geometry is unavailable.");
foreach (var body in bodies.EnumerateArray())
{
var centre = Vector(body, "centre") + offset; var radius = body.GetProperty("exclusionRadius").GetDouble();
if (!centre.Finite || !double.IsFinite(radius) || radius <= 0) throw new InvalidDataException("Invalid original exclusion geometry.");
var relative = centre - frame.Position; var projected = Math.Clamp(Vector3d.Dot(relative, direction), 0, horizon);
if ((relative - direction * projected).Length <= radius * 1.2)
throw new InvalidOperationException("The existing target ray crosses an original exclusion envelope.");
}
}
}
using System.Diagnostics;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Channels;
using XFEEDToolBox.Core.Data;
using XFEEDToolBox.Core.Models;
using XFEEDToolBox.Core.Native;
using XFEEDToolBox.Core.Navigation;
using XFEEDToolBox.Core.Session;
using XFEEDToolBox.NativeSupport;
/// <summary>Production driving, thermal stops and re-engagement on one existing clear SC leg.</summary>
internal static class SCOThermalCycleAcceptance
{
private const double LightSecond = 299792458;
private static readonly JsonSerializerOptions EvidenceJson = new(ToolboxSettings.JsonOptions)
{ NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals };
private static readonly JsonSerializerOptions EventJson = new(EvidenceJson) { WriteIndented = false };
private static bool True(JsonElement value, string key) => value.TryGetProperty(key, out var item) && item.ValueKind == JsonValueKind.True;
private static long At(JsonElement value) => value.GetProperty("observedAtUnixMilliseconds").GetInt64();
private static string Text(JsonElement value, string key) => value.GetProperty(key).GetString()!;
private static Vector3d Vector(JsonElement value, string key)
{ var array = value.GetProperty(key); return new(array[0].GetDouble(), array[1].GetDouble(), array[2].GetDouble()); }
private static string Cargo(PlayerState state) => JsonSerializer.Serialize(state.Inventory
.OrderBy(item => item.Key, StringComparer.Ordinal).ThenBy(item => item.MissionId)
.Select(item => new { item.Key, item.Count, item.Stolen, item.MissionId, item.Group }), ToolboxSettings.JsonOptions);
private sealed class Cycle(JsonElement enable)
{
public JsonElement Enable { get; } = enable.Clone();
public Dictionary<string, JsonElement> Renewals { get; } = new(StringComparer.Ordinal);
public SortedDictionary<long, JsonElement> Active { get; } = new();
public SortedDictionary<long, JsonElement> StoppedHeatSamples { get; } = new();
public long? ReenabledAt { get; set; }
public JsonElement? Stop { get; set; }
public JsonElement? Off { get; set; }
public CruiseOverchargeDecision? PolicyAtStop { get; set; }
public CruiseOverchargeObservation? BeforeEnableObservation { get; init; }
public CruiseOverchargeDecision? BeforeEnablePolicy { get; init; }
public bool CoolingObserved { get; set; }
public double ObservedCoolingPercent => StoppedHeatSamples.Count > 1
? StoppedHeatSamples.First().Value.GetProperty("heatPercent").GetDouble()
- StoppedHeatSamples.Values.Min(sample => sample.GetProperty("heatPercent").GetDouble()) : 0;
public bool ActualCoolingObserved => ObservedCoolingPercent >= .05;
public bool HeatLimited => Stop is { } stop && (Text(stop, "reason") == "heat" || PolicyAtStop?.Reason == CruiseOverchargeReason.Heat);
public double ActiveSeconds => Active.Count > 1 ? (Active.Keys.Last() - Active.Keys.First()) / 1000d : 0;
public bool Matches(JsonElement value) => Text(value, "owner") == Text(Enable, "owner")
&& value.GetProperty("selectionGeneration").GetUInt64() == Enable.GetProperty("selectionGeneration").GetUInt64()
&& Text(value, "entityId") == Text(Enable, "entityId") && Text(value, "referenceWorldAddress") == Text(Enable, "referenceWorldAddress");
public bool Complete => Stop is { } stop && Off is { } off && True(stop, "nativeMethodCalled") && !True(stop, "argument")
&& Text(stop, "nativeMethodRva") == "2eaacd0" && Text(stop, "startRequestId") == Text(Enable, "requestId")
&& Matches(stop) && stop.GetProperty("owningThread").GetInt32() == Enable.GetProperty("owningThread").GetInt32()
&& stop.GetProperty("stateAfter").GetInt32() is 0 or 3 && At(stop) >= At(Enable)
&& (Active.Count == 0 || At(stop) >= Active.Keys.Last()) && At(off) > At(stop)
&& off.GetProperty("state").GetInt32() is 0 or 3 && !True(off, "active");
public bool Qualified => Complete && Active.Count >= 5 && Renewals.Count >= 5;
public object Evidence => new { startRequestId = Text(Enable, "requestId"), enable = Enable,
renewals = Renewals.Values, renewalCount = Renewals.Count, activeSamples = Active.Values, activeSampleCount = Active.Count,
activeSeconds = ActiveSeconds, stop = Stop, policyAtStop = PolicyAtStop, subsequentOff = Off, coolingObserved = CoolingObserved,
heatLimited = HeatLimited, stoppedHeatSamples = StoppedHeatSamples.Values, observedCoolingPercent = ObservedCoolingPercent,
actualCoolingObserved = ActualCoolingObserved, reenabledAtUnixMilliseconds = ReenabledAt,
beforeEnableObservation = BeforeEnableObservation, beforeEnablePolicy = BeforeEnablePolicy, complete = Complete, qualified = Qualified };
}
internal static async Task RunAsync(int pid, string directory)
{
directory = Path.GetFullPath(directory);
if (Directory.Exists(directory)) throw new IOException("Use a fresh SCO thermal-cycle evidence directory.");
var parent = Path.GetDirectoryName(directory)!; Directory.CreateDirectory(parent);
using var exclusive = new FileStream(Path.Combine(parent, "docking-fixture-wait.lock"), FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
Directory.CreateDirectory(directory);
var start = DateTimeOffset.UtcNow; var flightUntil = start.AddSeconds(60);
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(65));
using var sentinel = new Timer(_ => { if (File.Exists(Path.Combine(directory, "stop"))) timeout.Cancel(); }, null, 100, 100);
var token = timeout.Token;
using var process = Process.GetProcessById(pid); var processStarted = process.StartTime.ToUniversalTime().Ticks;
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(token);
var initial = journal.State; var initialCargo = Cargo(initial);
var rawPipe = new NavigationPipeClient(pid); var cruisePipe = new NavigationPipeClient(pid, NativeCruiseControlChannel.Service);
var previousGuardian = PersistentProtectionProcess.Current;
FlightTestProtectionScope? protection = null; NativeSystemFlightControlDriver? driver = null; SystemNavigationTarget? target = null;
SystemFlightFrame? firstFrame = null, lastFrame = null;
JsonElement? inventoryBefore = null, inventoryAfter = null, finalRaw = null, finalCruise = null;
string phase = "preflight", stopReason = "not-started";
string? failure = null, releaseFailure = null, writerFailure = null;
var cycles = new List<Cycle>(); var receipts = new List<JsonElement>(); var stops = new List<JsonElement>();
var recoveryCount = 0; var zeroThrottleObserved = false; var targetPreserved = false; var overallPassed = false; var inputSubmitted = false;
double reserve = double.NaN, maximumSpeed = 0, maximumHeat = 0, minimumDistance = double.PositiveInfinity;
var events = Channel.CreateUnbounded<object>(new() { SingleReader = true });
await using var log = new StreamWriter(new FileStream(Path.Combine(directory, "frames.jsonl"), FileMode.CreateNew, FileAccess.Write, FileShare.Read));
var writer = Task.Run(async () =>
{
try
{
await foreach (var value in events.Reader.ReadAllAsync())
{ await log.WriteLineAsync(JsonSerializer.Serialize(value, EventJson)); await log.FlushAsync(); }
}
catch (Exception error) { writerFailure = error.ToString(); timeout.Cancel(); }
});
void Record(object value)
{ if (!events.Writer.TryWrite(value)) throw new IOException("SCO thermal-cycle evidence writer stopped."); }
void Session(CancellationToken cancellation)
{
cancellation.ThrowIfCancellationRequested(); process.Refresh(); var state = journal.State;
if (process.HasExited || process.StartTime.ToUniversalTime().Ticks != processStarted || !state.InGame || state.GameMode != "Solo"
|| state.LoadGameAt != initial.LoadGameAt || state.CommanderId != initial.CommanderId || state.CurrentShipId != initial.CurrentShipId
|| state.Navigation.CurrentSystemAddress != initial.Navigation.CurrentSystemAddress || state.Docked || !state.CargoReliable
|| state.CargoCount != initial.CargoCount || state.CargoCapacity != initial.CargoCapacity || Cargo(state) != initialCargo
|| NavigationSafety.HasExternalThreat(state) || NavigationSafety.UnhealthyHull(state) || TravelPreparation.HasDeployedEquipment(state))
throw new InvalidOperationException("SCO thermal-cycle session, cargo, ship, equipment, system or safety identity changed.");
if (writerFailure is not null) throw new IOException(writerFailure);
}
Task Protection(CancellationToken cancellation)
{
Session(cancellation);
if (protection?.Stopped == true) throw new InvalidOperationException(protection.LastIssue ?? "Flight protection stopped.");
NativeFlightTestProtection.CheckBeforeInput(pid); return Task.CompletedTask;
}
async Task<SystemFlightFrame> Read(CancellationToken cancellation)
{
Session(cancellation);
var raw = await rawPipe.CallAsync(new { op = "read" }, cancellation);
Record(new { at = DateTimeOffset.UtcNow, phase, kind = "raw-navigation-and-input-diagnostics", raw });
if (True(raw, "playerOverride") || !True(raw, "inGame")
|| raw.TryGetProperty("shipHealth", out var health) && True(health, "emergency"))
throw new InvalidOperationException("Original navigation reports player takeover, scene loss or emergency.");
var frame = await driver!.ReadSystemFlightAsync(cancellation); await Protection(cancellation);
Record(new { at = DateTimeOffset.UtcNow, phase, kind = "local-frame", frame, local = driver.LastTelemetry });
if (frame.Mode != LocalFlightMode.Supercruise || frame.PlayerOverride || frame.ExternalThreat || frame.TargetObscured || frame.CollisionCourse
|| frame.HeatPercent is not { } heat || !double.IsFinite(heat) || heat >= CruiseOverchargePolicy.MaximumHeatPercent
|| firstFrame is { } pinned && (frame.ShipEntity != pinned.ShipEntity || frame.ReferenceWorld != pinned.ReferenceWorld || frame.LoadoutFingerprint != pinned.LoadoutFingerprint)
|| frame.SupercruiseAssist is NativeAssistState.Engaged or NativeAssistState.Requested or NativeAssistState.Aligning)
throw new InvalidOperationException("The unchanged, clear, healthy SC leg is no longer available.");
var assist = driver.LastTelemetry!.Value.GetProperty("supercruiseAssist");
if (True(assist, "enabled") || True(assist, "engaged")) throw new InvalidOperationException("Game SCA acquired control during the thermal-cycle proof.");
ClearRay(frame, raw);
lastFrame = frame; maximumSpeed = Math.Max(maximumSpeed, frame.Speed); maximumHeat = Math.Max(maximumHeat, heat);
minimumDistance = Math.Min(minimumDistance, frame.TargetDistance); return frame;
}
async Task Apply(SystemFlightFrame frame, SystemFlightDecision decision, CancellationToken cancellation)
{
if (DateTimeOffset.UtcNow - frame.ObservedAt > TimeSpan.FromMilliseconds(150))
throw new FlightTelemetryUnavailableException("Proof input source is older than 150ms.");
var deadline = frame.ObservedAt.AddMilliseconds(250);
if (phase != "release-sco-zero-throttle-and-release-inputs" && deadline > flightUntil) deadline = flightUntil;
if (deadline <= DateTimeOffset.UtcNow) throw new TimeoutException("The 60-second flight budget is exhausted.");
inputSubmitted = true;
await driver!.ApplySystemFlightAsync(decision, frame, target!, deadline, cancellation);
}
void RequireRecoverableStop()
{
if (stops.Count == 0) return;
var reason = Text(stops[^1], "reason");
if (reason is not ("heat" or "native-refusal" or "native-telemetry" or "released" or "expired" or "lease-expired" or "no-lease"))
throw new InvalidOperationException("SCO thermal-cycle proof reached a non-recoverable native boundary: " + reason);
}
void CaptureStatus(JsonElement value)
{
Record(new { at = DateTimeOffset.UtcNow, phase, kind = "original-sco-status", value });
if (firstFrame is null || !True(value, "sampleReady") || !value.TryGetProperty("sample", out var sample) || sample.ValueKind != JsonValueKind.Object
|| !True(sample, "verified") || Text(sample, "entityId") != firstFrame.ShipEntity.ToString()
|| Text(sample, "referenceWorldAddress") != firstFrame.ReferenceWorld.ToString()) return;
var at = At(sample); var state = sample.GetProperty("state").GetInt32();
foreach (var cycle in cycles)
{
if (at < At(cycle.Enable)) continue;
if (state == 2 && (cycle.Stop is not { } stopped || at <= At(stopped))) cycle.Active.TryAdd(at, sample.Clone());
if (!True(value, "owned") && state is 0 or 3 && cycle.Stop is { } stop && at > At(stop))
{
cycle.Off ??= sample.Clone();
cycle.CoolingObserved |= state == 3 || !True(sample, "nativeCanEngage");
if (cycle.ReenabledAt is null || at < cycle.ReenabledAt)
cycle.StoppedHeatSamples.TryAdd(at, sample.Clone());
}
}
}
double CumulativeActive() => cycles.Where(cycle => cycle.Qualified).Sum(cycle => cycle.ActiveSeconds);
bool ThermalRestartVerified() => cycles.Zip(cycles.Skip(1)).Any(pair => pair.First.Qualified && pair.Second.Qualified
&& pair.First.HeatLimited && pair.First.CoolingObserved && pair.First.ActualCoolingObserved
&& pair.First.Off is { } off && At(off) < At(pair.Second.Enable)
&& pair.Second.BeforeEnableObservation is { OriginalCanEngage: true, State: CruiseOverchargeState.Idle }
&& pair.Second.BeforeEnablePolicy is { UseOvercharge: true, Reason: CruiseOverchargeReason.Ready });
bool EnoughCycles() => cycles.Count(cycle => cycle.Qualified) >= 2 && CumulativeActive() >= 2 && ThermalRestartVerified();
async Task<JsonElement> Inventory(CancellationToken cancellation)
{
var inventory = await rawPipe.CallAsync(new { op = "commerce.inventory" }, cancellation);
Record(new { at = DateTimeOffset.UtcNow, phase, kind = "actual-inventory", inventory });
if (!True(inventory, "verified") || inventory.GetProperty("count").GetInt64() != initial.CargoCount
|| inventory.GetProperty("capacity").GetInt64() != initial.CargoCapacity
|| firstFrame is { } frame && Text(inventory, "entityId") != frame.ShipEntity.ToString())
throw new InvalidDataException("Original cargo count/capacity differs from the unchanged Journal inventory.");
return inventory.Clone();
}
try
{
if (!initial.InGame || initial.Docked || initial.CurrentShipId is null || initial.GameMode != "Solo" || !initial.CargoReliable
|| initial.Navigation.CurrentSystemAddress <= 0 || NavigationSafety.HasExternalThreat(initial) || NavigationSafety.UnhealthyHull(initial)
|| TravelPreparation.HasDeployedEquipment(initial)) throw new InvalidOperationException("Healthy Solo SC and known unchanged cargo are required.");
using var source = new NativeCurrentSystemSource(pid, NativeSystemLocationReader.GameSha256, () => journal.State);
var catalogue = await source.ReadLocationsAsync(token);
var row = catalogue.Locations.SingleOrDefault(location => location.Selected)
?? throw new InvalidOperationException("An already selected target is required; no replacement target will be chosen.");
if (row.DistanceMetres is not { } distance || !double.IsFinite(distance) || distance < 50_000 * LightSecond
|| row.Category is not (SystemLocationCategory.Station or SystemLocationCategory.Carrier or SystemLocationCategory.Star or SystemLocationCategory.Planet or SystemLocationCategory.LandablePlanet))
throw new InvalidOperationException("The existing clear SC target must be at least 50,000ls away.");
target = new(catalogue.SystemAddress, row.WorldAddress, row.Name,
row.Category is SystemLocationCategory.Station or SystemLocationCategory.Carrier ? SystemTargetKind.Station : SystemTargetKind.Orbit, row.MarketId)
{ NativeKey = row.Key, NativeKind = row.NativeKind, NativeIdentity = row.Identity };
Record(new { at = DateTimeOffset.UtcNow, kind = "selected-existing-target", catalogue, target });
PersistentProtectionProcess.Current = null;
protection = await FlightTestProtectionScope.ArmAsync(pid, exitForFailedHandoff: false, token: token);
driver = new(pid, NativeSystemLocationReader.GameSha256, () => journal.State,
new NativeGameInputChannel(pid, NativeSystemLocationReader.GameSha256, NativeSupportPackage.Directory), Protection, useNativeSupercruiseAssist: false);
await driver.SelectAsync(target, token); // Binds the exact existing identity; no destination change.
firstFrame = await Read(token); inventoryBefore = await Inventory(token);
await driver.PrepareAsync(token);
phase = "align-existing-target-at-zero-throttle";
while (true)
{
var frame = await Read(token); var direction = (frame.TargetPosition - frame.Position).Unit();
await Apply(frame, new(SystemNavigationPhase.Waiting, phase, direction, 0), token);
if (Vector3d.AngleDegrees(frame.Forward, direction) <= 2 && Math.Abs(frame.Throttle) < .001) break;
if (DateTimeOffset.UtcNow >= flightUntil) throw new TimeoutException("Alignment exhausted the 60-second flight budget.");
await Task.Delay(40, token);
}
var prepared = new NativeCruiseControlChannel(pid, NativeSystemLocationReader.GameSha256);
await prepared.PrepareAsync(Path.Combine(NativeSupportPackage.Directory, "CruiseControl"), token);
var ship = initial.Ships.Single(ship => ship.ShipId == initial.CurrentShipId);
if (ship.FuelMain is not > 8 || !double.IsFinite(ship.FuelMain.Value)) throw new InvalidDataException("Verified main tank capacity and fuel reserve are required.");
reserve = Math.Max(8, ship.FuelMain.Value * .25);
await driver.ConfigureOriginalCruiseControlAsync(() => reserve, token: token);
var control = driver.CruiseControl!;
control.Observed += CaptureStatus;
control.OriginalReceipt += value =>
{
receipts.Add(value.Clone()); Record(new { at = DateTimeOffset.UtcNow, phase, kind = "original-sco-receipt", value });
if (True(value, "nativeMethodCalled") && True(value, "argument"))
{
if (cycles.Any(item => Text(item.Enable, "requestId") == Text(value, "requestId"))) return;
if (cycles.Any(item => !item.Complete)) throw new InvalidDataException("A new SCO enable preceded the previous cycle's matched stop and subsequent Off.");
if (cycles.LastOrDefault() is { } previousCycle) previousCycle.ReenabledAt = At(value);
cycles.Add(new(value) { BeforeEnableObservation = control.LastObservation, BeforeEnablePolicy = control.LastDecision });
}
else if (True(value, "applied") && cycles.LastOrDefault() is { } currentCycle && currentCycle.Matches(value))
currentCycle.Renewals.TryAdd(Text(value, "requestId"), value.Clone());
};
control.OriginalStopReceipt += value =>
{
stops.Add(value.Clone()); Record(new { at = DateTimeOffset.UtcNow, phase, kind = "original-sco-stop", value });
var cycle = cycles.SingleOrDefault(item => Text(item.Enable, "requestId") == Text(value, "startRequestId"));
if (cycle is not null && cycle.Matches(value)) { cycle.Stop = value.Clone(); cycle.PolicyAtStop = control.LastDecision; }
};
var navigator = new SystemFlightNavigator(); var handledStops = 0;
phase = "production-driving-and-thermal-recovery"; stopReason = "flight-budget-exhausted";
while (DateTimeOffset.UtcNow < flightUntil)
{
try
{
var frame = await Read(token);
if (EnoughCycles()) { stopReason = "two-qualified-thermal-cycles-and-two-active-seconds"; break; }
RequireRecoverableStop();
if (stops.Count != handledStops)
{
await driver.SuspendSystemFlightAsync(); handledStops = stops.Count; recoveryCount++;
Record(new { at = DateTimeOffset.UtcNow, phase, kind = "native-stop-suspended-and-refreshed", stops = handledStops });
await Task.Delay(40, token); continue;
}
var decision = navigator.Decide(frame, target, DateTimeOffset.UtcNow);
if (decision.Phase is not (SystemNavigationPhase.Supercruise or SystemNavigationPhase.Aligning)
|| (decision.Actions & ~SystemNavigationAction.CancelOwnedAssists) != SystemNavigationAction.None)
throw new InvalidOperationException("The existing SC leg reached an approach, assist or scene-change boundary.");
await Apply(frame, decision, token);
Record(new { at = DateTimeOffset.UtcNow, phase, kind = "production-decision", decision, policy = control.LastDecision, observation = control.LastObservation,
qualifiedCycles = cycles.Count(cycle => cycle.Qualified), cumulativeActiveSeconds = CumulativeActive() });
if (control.LastDecision?.Reason is CruiseOverchargeReason.Fuel or CruiseOverchargeReason.TargetClose)
throw new InvalidOperationException("Production SCO policy reached a real fuel or approach boundary: " + control.LastDecision.Reason);
}
catch (Exception error) when (NavigationRecovery.CanRetry(error, token))
{
Session(token); RequireRecoverableStop(); recoveryCount++;
Record(new { at = DateTimeOffset.UtcNow, phase, kind = "production-recovery", recovery = NavigationRecovery.Kind(error), error = error.ToString() });
await driver.SuspendSystemFlightAsync(); handledStops = stops.Count;
// Preserve this driver/channel and its heat hysteresis; only real
// new observations and native CanEngage may permit another start.
}
await Task.Delay(40, token);
}
}
catch (Exception error) { failure = error.ToString(); Record(new { at = DateTimeOffset.UtcNow, phase, failure }); }
finally
{
phase = "release-sco-zero-throttle-and-release-inputs";
using var cleanup = new CancellationTokenSource(TimeSpan.FromSeconds(8));
try
{
if (driver is not null)
{
if (driver.CruiseControl is { } control) await control.ReleaseAsync(cleanup.Token);
if (protection?.Stopped == false)
{
var frame = await Read(cleanup.Token);
await Apply(frame, new(SystemNavigationPhase.Waiting, phase, null, 0), cleanup.Token);
await Task.Delay(80, cleanup.Token);
zeroThrottleObserved = Math.Abs((await Read(cleanup.Token)).Throttle) < .001;
}
}
}
catch (Exception error) { releaseFailure = error.ToString(); Record(new { at = DateTimeOffset.UtcNow, phase, releaseFailure }); }
finally
{
if (driver is not null)
try { await driver.ReleaseSystemFlightAsync(); }
catch (Exception error) { releaseFailure ??= error.ToString(); Record(new { at = DateTimeOffset.UtcNow, phase, releaseFailure }); }
}
try
{
finalRaw = await rawPipe.CallAsync(new { op = "read" }, cleanup.Token);
finalCruise = await cruisePipe.CallAsync(new { op = "read" }, cleanup.Token); CaptureStatus(finalCruise.Value);
Record(new { at = DateTimeOffset.UtcNow, phase, kind = "final-raw-and-native-status", raw = finalRaw, cruise = finalCruise });
inventoryAfter = await Inventory(cleanup.Token);
using var finalSource = new NativeCurrentSystemSource(pid, NativeSystemLocationReader.GameSha256, () => journal.State);
var final = await finalSource.ReadLocationsAsync(cleanup.Token); var selected = final.Locations.SingleOrDefault(location => location.Selected);
targetPreserved = target is not null && selected?.WorldAddress == target.BodyAddress && selected.NativeKind == target.NativeKind
&& selected.Name == target.Name && selected.MarketId == target.MarketId;
}
catch (Exception error) { releaseFailure ??= error.ToString(); Record(new { at = DateTimeOffset.UtcNow, phase, releaseFailure }); }
try { if (protection is not null) await protection.DisposeAsync(); }
catch (Exception error) { releaseFailure ??= error.ToString(); }
PersistentProtectionProcess.Current = previousGuardian;
events.Writer.TryComplete(); await writer;
var cargoPreserved = journal.State.CargoCount == initial.CargoCount && journal.State.CargoCapacity == initial.CargoCapacity && Cargo(journal.State) == initialCargo;
var inventoryPreserved = inventoryBefore is { } before && inventoryAfter is { } after
&& before.GetProperty("count").GetInt64() == after.GetProperty("count").GetInt64()
&& before.GetProperty("capacity").GetInt64() == after.GetProperty("capacity").GetInt64()
&& before.GetProperty("credits").GetUInt64() == after.GetProperty("credits").GetUInt64();
var activeSamples = cycles.SelectMany(cycle => cycle.Active.Values).OrderBy(At).ToArray();
var firstActiveSpeed = activeSamples.Length > 0 ? activeSamples[0].GetProperty("speedMetresPerSecond").GetDouble() : 0;
var maximumActiveSpeed = activeSamples.Length > 0 ? activeSamples.Max(sample => sample.GetProperty("speedMetresPerSecond").GetDouble()) : 0;
var speedIncreased = maximumActiveSpeed > firstActiveSpeed + Math.Max(1000, firstActiveSpeed * .01);
var originalOff = finalCruise is { } status && !True(status, "owned") && True(status, "sampleReady")
&& status.TryGetProperty("sample", out var sample) && sample.ValueKind == JsonValueKind.Object && sample.GetProperty("state").GetInt32() is 0 or 3
&& cycles.All(cycle => cycle.Complete);
overallPassed = failure is null && releaseFailure is null && writerFailure is null && EnoughCycles() && speedIncreased
&& originalOff && zeroThrottleObserved && cargoPreserved && inventoryPreserved && targetPreserved;
var report = new { passed = overallPassed, pid, processStarted, start, at = DateTimeOffset.UtcNow, maximumFlightSeconds = 60, phase, stopReason,
failure, releaseFailure, writerFailure, initial.CurrentShipId, initial.Ship, initial.CargoCount, initial.CargoCapacity, initialCargo,
finalCargo = Cargo(journal.State), cargoPreserved, inventoryBefore, inventoryAfter, inventoryPreserved, target, targetPreserved,
reserveFuelTonnes = reserve, cycleCount = cycles.Count, qualifiedCycleCount = cycles.Count(cycle => cycle.Qualified), cumulativeActiveSeconds = CumulativeActive(),
minimumActiveSamplesPerCycle = 5, minimumRenewalsPerCycle = 5, requiredCumulativeActiveSeconds = 2, firstActiveSpeed, maximumActiveSpeed, speedIncreased,
maximumSpeed, maximumHeat, initialDistance = firstFrame?.TargetDistance, minimumDistance, finalDistance = lastFrame?.TargetDistance,
observedDistanceProgress = firstFrame is null || !double.IsFinite(minimumDistance) ? (double?)null : firstFrame.TargetDistance - minimumDistance,
thermalRestartVerified = ThermalRestartVerified(), originalOff, zeroThrottleObserved, firstFrame, lastFrame, finalRaw, finalCruise, recoveryCount,
cycles = cycles.Select(cycle => cycle.Evidence), receipts, stops,
inputSubmitted, tradeOrdersSent = 0, serviceOrdersSent = 0, launchCommandsSent = 0, scEntryCommandsSent = 0, targetReplaced = false,
gameSupercruiseAssistUsed = false, crossSystemFlight = false, clientRestarted = false, singleTwentySecondIntervalClaimed = false };
await File.WriteAllTextAsync(Path.Combine(directory, "result.json"), JsonSerializer.Serialize(report, EvidenceJson));
Console.WriteLine(JsonSerializer.Serialize(new { passed = overallPassed, qualifiedCycles = cycles.Count(cycle => cycle.Qualified), cumulativeActiveSeconds = CumulativeActive(),
maximumActiveSpeed, maximumHeat, stopReason, failure, releaseFailure, directory }, EventJson));
}
if (!overallPassed) throw new InvalidOperationException("SCO thermal-cycle proof did not meet its declared requirements; see the preserved report.");
}
private static void ClearRay(SystemFlightFrame frame, JsonElement raw)
{
var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); var geometry = raw.GetProperty("geometry"); var at = At(raw);
if (now - at is < 0 or > 250 || !True(geometry, "fresh") || !True(geometry, "referenceFrameVerified")
|| Text(geometry, "worldAddress") != frame.ReferenceWorld.ToString())
throw new FlightTelemetryUnavailableException("Fresh matching clearance geometry is required.");
var offset = frame.Position - Vector(geometry, "position"); var direction = (frame.TargetPosition - frame.Position).Unit();
var horizon = frame.TargetDistance - 10 * LightSecond;
if (horizon <= 0) throw new InvalidOperationException("The existing target is inside the unchanged SCO approach boundary.");
var bodies = geometry.GetProperty("nearbyBodies");
if (bodies.ValueKind != JsonValueKind.Array || bodies.GetArrayLength() == 0) throw new FlightTelemetryUnavailableException("Original body clearance geometry is unavailable.");
foreach (var body in bodies.EnumerateArray())
{
var centre = Vector(body, "centre") + offset; var radius = body.GetProperty("exclusionRadius").GetDouble();
if (!centre.Finite || !double.IsFinite(radius) || radius <= 0) throw new InvalidDataException("Invalid original exclusion geometry.");
var relative = centre - frame.Position; var projected = Math.Clamp(Vector3d.Dot(relative, direction), 0, horizon);
if ((relative - direction * projected).Length <= radius * 1.2)
throw new InvalidOperationException("The existing target ray crosses an original exclusion envelope.");
}
}
}