XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
#if PLUGIN_CRUISE_CONTROL
using System.IO.Pipes;
using System.Text;
using XFEEDToolBox.Core.Navigation;
namespace XFEEDToolBox.Plugin;

// The only game mutation is the verified original SCO enable/disable method,
// dispatched from the original main-ship update on its owning thread.
internal static class CruiseControlRuntime
{
    private const nuint Update=0x2EADB40,OriginalSet=0x2EAACD0;
    private const string Service=CruiseControlProtocol.Service;
    private static bool usingLegacyCommandObserver;
    private sealed record Intent(string Owner,string RequestId,ulong Generation,ulong SelectionGeneration,ulong Deadline,JsonElement Target,double Reserve);
    private static readonly object sync=new();
    private static bool started,enabled,owned,releaseRequested,awaitingOff,damageSeen;
    private static nuint image,original,originalSet;
    [ThreadStatic]private static int ownedSetDepth;
    [ThreadStatic]private static int originalUpdateDepth;
    private static readonly Queue<JsonObject> originalCommands=new();
    private static ulong originalCommandSerial;
    private static bool replacedLegacyUpdate;
    internal static void EnterOwnedOriginalCommand()=>ownedSetDepth++;
    internal static void ExitOwnedOriginalCommand()=>ownedSetDepth--;
    private static uint thread;
    private static Navigation.RecoveryCruiseBinding? binding;
    private static Intent? intent;
    private static string activeOwner="";
    private static string activeStartRequestId="",lastLeaseRequestId="",releaseRequestId="";
    private static ulong activeSelectionGeneration;
    private static ulong entity,world,calls,matches,errors,lastSampleTick,generation,consumed,applied,failed,enableCalls,disableCalls,activeSamples,offSamples;
    private static readonly ScoHeatSampleHistory heatHistory=new();
    private static readonly ScoExitSimulationClock exitClock=new();
    private static readonly ScoExitEstimatePolicy exitPolicy=new();
    private static double previousHull=double.NaN,previousShield=double.NaN,heatSlope=double.NaN;
    private static double simulationStepSeconds=double.NaN;
    private static double originalUpdateThirdArgument=double.NaN;
    private static ulong lastOriginalStopCallMilliseconds;
    private static JsonObject? latest,receipt,lastOriginalStop,lastGate;
    private static readonly OriginalCruiseCycleQualification qualification=new();
    private static string? issue;
    private static bool Key(string value)=>value.Length==32&&value.All(c=>c is >= '0' and <= '9' or >= 'a' and <= 'f');
    private static bool Fresh(ulong at,ulong now)=>at<=now&&now-at<=200;
    private static void ObserveHeat(ulong at,double heat)
    {
        heatHistory.Observe(at,heat);heatSlope=heatHistory.Slope;
    }
    private static double GuardHeatRise(ulong at)=>heatHistory.ConservativeSlope(at);
    private static JsonObject HeatObservation(ulong at)=>Obj(("samplingPhase","before-original-update"),("usedForControl",false),("slopePercentPerSecond",heatSlope),
        ("windowStartUnixMilliseconds",heatHistory.SlopeStartAt),("windowEndUnixMilliseconds",heatHistory.SlopeEndAt),
        ("windowMilliseconds",heatHistory.SlopeEndAt-heatHistory.SlopeStartAt),("windowStartHeatPercent",heatHistory.SlopeStartHeat),
        ("windowEndHeatPercent",heatHistory.SlopeEndHeat),("recentPeakPercentPerSecond",heatHistory.RecentPeakRise),("recentPeakExpiresAtUnixMilliseconds",heatHistory.RecentPeakExpiresAt),
        ("guardHeatRisePercentPerSecond",GuardHeatRise(at)));
    private static bool Flag(JsonObject value,string key)=>value[key] is JsonValue v&&v.TryGetValue<bool>(out var flag)&&flag;
    private static double Number(JsonObject value,string key)
    {
        if(value[key] is not JsonValue v)return double.NaN;
        if(v.TryGetValue<double>(out var d))return d;if(v.TryGetValue<float>(out var f))return f;
        if(v.TryGetValue<int>(out var i))return i;if(v.TryGetValue<uint>(out var u))return u;
        if(v.TryGetValue<byte>(out var b))return b;if(v.TryGetValue<ulong>(out var n))return n;
        return double.NaN;
    }
    private static void AttachThermalTiming(JsonObject sample)
    {
        sample["originalUpdateClockEpoch"]=exitClock.Epoch;sample["originalUpdateCompletedSteps"]=exitClock.CompletedSteps;
        sample["originalUpdateCompletedSimulationSeconds"]=exitClock.Valid?(double?)exitClock.CompletedSeconds:null;
        sample["originalUpdateClockValid"]=exitClock.Valid;
        sample["originalUpdateClockScope"]="Sum of every completed matched original SCO callback delta; no sparse IPC dt or UTC substitution";
        sample["scoExitApproach"]=exitPolicy.Latest.Json();
        sample["sameFrameSimulationStepSeconds"]=double.IsFinite(simulationStepSeconds)?(double?)simulationStepSeconds:null;
        sample["sameFrameSimulationStepSource"]="owning original SCO update delta argument";
        sample["originalUpdateThirdArgument"]=double.IsFinite(originalUpdateThirdArgument)?(double?)originalUpdateThirdArgument:null;
        sample["originalUpdateThirdArgumentMeaningVerified"]=false;
        if(sample["thermal"] is not JsonObject thermal)return;
        var heatStep=Number(thermal,"nativeHeatStepSeconds");
        var response=Math.Max(simulationStepSeconds,heatStep);
        thermal["responseDelaySeconds"]=double.IsFinite(response)&&response is >0 and <=.25?(double?)response:null;
        thermal["afterStopObservationSeconds"]=.5;
        thermal["heatNumericalUncertaintyPercent"]=.001;
        thermal["stopCallWallMilliseconds"]=lastOriginalStopCallMilliseconds;
        thermal["currentSimulationStepSeconds"]=double.IsFinite(simulationStepSeconds)?(double?)simulationStepSeconds:null;
        thermal["wallLeaseIsSimulationBound"]=false;
        thermal["timingBasis"]="Current actual simulation dt is guarded before original Active heat input; UTC lease is not a proven cumulative simulation horizon; synchronous stop wall time does not continue SCO heating";
    }
    private static ScoThermalInput? ThermalInput(JsonObject sample,ulong now,ulong deadline)
    {
        if(sample["thermal"] is not JsonObject thermal)return null;
        return new(){Verified=Flag(thermal,"verified")&&thermal["entityId"]?.ToString()==entity.ToString()
                &&thermal["referenceWorldAddress"]?.ToString()==world.ToString(),
            HeatPercent=Number(sample,"heatPercent"),HeatCapacity=Number(thermal,"heatCapacity"),HeatDisplayScale=Number(thermal,"heatDisplayScale"),
            ScoInputUpper=Number(thermal,"scoInputUpper"),OtherInputUpper=Number(thermal,"otherInputUpper"),
            CoolingPassive=Number(thermal,"coolingPassive"),CoolingMaximum=Number(thermal,"coolingMaximum"),CoolingScale=Number(thermal,"coolingScale"),
            CoolingHeatLower=Number(thermal,"coolingHeatLower"),CoolingHeatUpper=Number(thermal,"coolingHeatUpper"),
            SourceAgeSeconds=(now-Number(thermal,"sourceObservedAtUnixMilliseconds"))/1000d,
            HeatAgeSeconds=(now-Number(thermal,"heatObservedAtUnixMilliseconds"))/1000d,
            LeaseRemainingSeconds=deadline>now?(deadline-now)/1000d:0,ResponseDelaySeconds=Number(thermal,"responseDelaySeconds"),
            AfterStopSeconds=.5,HeatUncertaintyPercent=.001};
    }
    private static bool CurrentSimulationHeatBound(JsonObject sample,ScoThermalInput? model,ulong now,out double peak)
    {
        peak=double.NaN;if(model is null||sample["thermal"] is not JsonObject thermal)return false;
        var storedHeat=Number(thermal,"currentStoredEnergyHeatPercent");var storedAt=Number(thermal,"currentStoredEnergyObservedAtUnixMilliseconds");
        if(!double.IsFinite(storedHeat)||storedHeat<0||!double.IsFinite(storedAt)||storedAt>now||now-storedAt>200)return false;
        var heat=Math.Max(Number(sample,"heatPercent"),storedHeat);
        return ScoThermalPredictor.TryBoundCurrentSimulationStep(model with{HeatPercent=heat,HeatAgeSeconds=0},simulationStepSeconds,out peak);
    }
    private static JsonObject? ThermalForecast(ScoThermalForecast? forecast)=>forecast is null?null:Obj(("known",forecast.Known),
        ("scoOnNetRisePercentPerSecond",double.IsFinite(forecast.ScoOnNetRisePercentPerSecond)?(double?)forecast.ScoOnNetRisePercentPerSecond:null),
        ("scoOffNetRisePercentPerSecond",double.IsFinite(forecast.ScoOffNetRisePercentPerSecond)?(double?)forecast.ScoOffNetRisePercentPerSecond:null),
        ("secondsTo100LowerBound",forecast.SecondsTo100LowerBound),
        ("continueUntilDeadlinePeakPercent",double.IsFinite(forecast.ContinueUntilDeadlinePeakPercent)?(double?)forecast.ContinueUntilDeadlinePeakPercent:null),
        ("deadlineStopPeakPercent",double.IsFinite(forecast.DeadlineStopPeakPercent)?(double?)forecast.DeadlineStopPeakPercent:null),
        ("immediateStopPeakPercent",double.IsFinite(forecast.ImmediateStopPeakPercent)?(double?)forecast.ImmediateStopPeakPercent:null),
        ("immediateStopEndPercent",double.IsFinite(forecast.ImmediateStopEndPercent)?(double?)forecast.ImmediateStopEndPercent:null),
        ("deadlineStopEndPercent",double.IsFinite(forecast.DeadlineStopEndPercent)?(double?)forecast.DeadlineStopEndPercent:null),
        ("stopHorizonSeconds",forecast.StopHorizonSeconds),("shouldStop",forecast.ShouldStop),("reason",forecast.Reason));
    private static async Task<JsonElement> NavAsync(JsonObject request,string service="Navigation")
    {
        using var timeout=new CancellationTokenSource(TimeSpan.FromMilliseconds(150));
        using var pipe=new NamedPipeClientStream(".",$"XFEEDToolBox.{service}.{ProcessId}",PipeDirection.InOut,PipeOptions.Asynchronous);
        await pipe.ConnectAsync(timeout.Token);await pipe.WriteAsync(Encoding.UTF8.GetBytes(request.ToJsonString()+"\n"),timeout.Token);await pipe.FlushAsync(timeout.Token);
        var bytes=new byte[65536];var count=0;
        while(count<bytes.Length){var n=await pipe.ReadAsync(bytes.AsMemory(count),timeout.Token);if(n==0)break;count+=n;if(bytes[count-1]=='\n')break;}
        if(count is 0 or 65536)throw new IOException("Invalid original navigation response.");
        using var document=JsonDocument.Parse(bytes.AsMemory(0,count));var response=document.RootElement;
        if(!response.GetProperty("ok").GetBoolean())throw new InvalidOperationException(response.Str("error"));
        return response.GetProperty("result").Clone();
    }
    private static JsonElement Nav(JsonObject request)=>NavAsync(request).GetAwaiter().GetResult();
    private static bool SameTarget(JsonElement a,JsonElement b)=>a.Int("key")==b.Int("key")&&a.Str("bodyAddress")==b.Str("bodyAddress")
        &&a.U64("kind")==b.U64("kind")&&a.U64("marketId")==b.U64("marketId")&&a.Str("name")==b.Str("name");
    private static JsonObject Describe()
    {
        lock(sync)
        {
            var observed=latest is null?0:(ulong)Number(latest,"observedAtUnixMilliseconds");
            return Obj(("schema",1),("service",Service),("version",Platform.PluginVersion),("processId",ProcessId),("gameSha256",GameHash),("enabled",enabled),("owningThread",thread),
                ("replacedIdleLegacyUpdate",replacedLegacyUpdate),("usingReadOnlyLegacyCommandObserver",usingLegacyCommandObserver),
                ("watchEntityId",entity.ToString()),("watchReferenceWorld",world.ToString()),("watchInterface",binding?.component.ToString("x")),
                ("sampleReady",observed!=0&&Fresh(observed,Utc())),("sample",latest?.DeepClone()),("overchargeControl",true),
                ("originalFuelRangeObservations",true),("componentThermalModel",true),("nativeStopReceipts",true),("expiringOriginalControl",true),("controlTelemetrySchema",1),
                ("overchargeControlQualified",qualification.Qualified),("cycleActiveSamples",qualification.ActiveSamples),("qualifiedCycle",QualifiedCycle()),
                ("owned",owned),("leaseExpiresAtUnixMilliseconds",intent?.Deadline??0),("receipt",receipt?.DeepClone()),("lastOriginalStop",lastOriginalStop?.DeepClone()),("lastGate",lastGate?.DeepClone()),
                ("scoExitEstimate",exitPolicy.Status()),
                ("originalCommandObservations",new JsonArray(originalCommands.Select(value=>(JsonNode)value.DeepClone()).ToArray())),("originalCommandSerial",originalCommandSerial),
                ("generation",generation),("consumed",consumed),("applied",applied),("failed",failed),
                ("calls",calls),("matches",matches),("errors",errors),("issue",issue),("originalEnableCalls",enableCalls),("originalDisableCalls",disableCalls),
                ("actualActiveSamples",activeSamples),("actualOffSamples",offSamples),("overchargeCommandsSent",enableCalls+disableCalls),
                ("steeringInputsSent",0),("targetSelectionsSent",0));
        }
    }
    private static JsonObject? QualifiedCycle()
    {
        var proof=qualification.Completed;if(proof is null)return null;var identity=proof.Identity;
        return Obj(("owner",identity.Owner),("startRequestId",identity.StartRequestId),("selectionGeneration",identity.SelectionGeneration),
            ("entityId",identity.Entity.ToString()),("referenceWorldAddress",identity.World.ToString()),("activeSamples",proof.ActiveSamples),
            ("enableAtUnixMilliseconds",proof.EnableAt),("lastActiveAtUnixMilliseconds",proof.LastActiveAt),("releaseRequestId",proof.StopRequestId),
            ("originalStopAtUnixMilliseconds",proof.StopAt),("offAtUnixMilliseconds",proof.OffAt));
    }
    private static JsonNode Handle(JsonElement request)
    {
        var op=request.Str("op");if(op is "describe" or "read")return Describe();
        if(op=="sco.exit.configure")
        {
            lock(sync)
            {
                if(request.Str("mode")=="shadow"){exitPolicy.ShadowOnly();return Describe();}
                var mode=request.Str("mode");
                if(mode is not("trial" or "production")||owned||intent is not null)throw new InvalidOperationException("Exit strategy configuration requires an idle owned-command channel.");
                var expectedEntity=request.U64("entityId");var expectedWorld=request.U64("referenceWorldAddress");
                string why;
                var accepted=mode=="production"
                    ?exitPolicy.PromoteProduction(expectedEntity,expectedWorld,Utc(),request.U64("expiresAtUnixMilliseconds"),out why)
                    :exitPolicy.ArmTrial(expectedEntity,expectedWorld,Utc(),request.U64("expiresAtUnixMilliseconds"),request.Int("maximumBursts"),out why);
                if(!accepted)throw new InvalidOperationException("SCO exit strategy refused: "+why);
                return Describe();
            }
        }
        if(op=="deactivate")
        {
            lock(sync){if(owned||releaseRequested)throw new InvalidOperationException("Release the owned SCO and observe its original stopped state first.");enabled=false;}
            HookDisable(image+Update);return Describe();
        }
        if(!enabled)throw new InvalidOperationException("Original cruise controller is inactive.");
        if(op=="watch")
        {
            var fingerprint=request.Str("loadoutFingerprint");
            if(fingerprint.Length!=0&&(fingerprint.Length!=64||!fingerprint.All(Uri.IsHexDigit)))throw new InvalidOperationException("Trusted SHA256 loadout fingerprint required.");
            lock(sync)if(owned)throw new InvalidOperationException("Cannot rebind an owned SCO lease.");
            var frame=Nav(Obj(("op","read")));var health=frame.GetProperty("shipHealth");var now=Utc();
            if(!frame.Bool("inGame")||!frame.Bool("localPlayerShipVerified")||!health.Bool("verified")||!Fresh(frame.U64("observedAtUnixMilliseconds"),now)
                ||frame.Str("entityId")!=health.Str("entityId"))throw new InvalidOperationException("Fresh original main ship required.");
            var next=ulong.Parse(frame.Str("entityId"));var reference=ulong.Parse(frame.Str("referenceWorldAddress"));var selected=Navigation.BindRecoveryCruise(next);
            lock(sync)
            {
                if(owned)throw new InvalidOperationException("Cannot rebind an owned SCO lease.");
                if(entity!=next||world!=reference||binding?.component!=selected.component)
                {latest=lastGate=null;heatHistory.Reset();exitClock.Reset();exitPolicy.ResetBinding();previousHull=previousShield=heatSlope=double.NaN;damageSeen=false;intent=null;releaseRequested=awaitingOff=false;qualification.ForgetCurrent();}
                exitPolicy.BindLoadout(fingerprint);
                entity=next;world=reference;binding=selected;issue=selected.component==0?"current-entity-has-no-SCO-interface":"awaiting-original-main-ship-update";
            }
            return Describe();
        }
        if(op=="cruise.release")
        {
            var owner=request.Str("owner");var stopId=request.Str("requestId");if(!Key(owner)||!Key(stopId))throw new InvalidOperationException("Explicit SCO owner and stop request required.");
            lock(sync)
            {
                if(owned&&activeOwner!=owner||intent is not null&&intent.Owner!=owner)throw new InvalidOperationException("SCO belongs to another controller.");
                if(owned){releaseRequestId=stopId;releaseRequested=true;}consumed=generation;intent=null;
                return Obj(("requestId",stopId),("released",!owned),("pending",owned),("originalDisableCalls",disableCalls),("lastOriginalStop",lastOriginalStop?.DeepClone()));
            }
        }
        if(op!="cruise.set")throw new InvalidOperationException("Unknown original cruise command.");
        var ownerId=request.Str("owner");var requestId=request.Str("requestId");var deadline=request.U64("expiresAtUnixMilliseconds");var sequence=request.U64("sequence");
        var reserve=request.Num("requiredFuelReserveTonnes",double.NaN);var selection=request.U64("selectionGeneration");var began=Utc();
        if(!Key(ownerId)||!Key(requestId)||selection==0||sequence==0||deadline<=began||deadline-began>250||!double.IsFinite(reserve)||reserve<=0)
            throw new InvalidOperationException("A bounded original SCO request is required.");
        lock(sync)
        {
            if(intent?.RequestId==requestId)return Describe();
            if(owned&&activeOwner!=ownerId||intent is not null&&intent.Owner!=ownerId&&(owned||intent.Deadline>began))throw new InvalidOperationException("Another controller owns SCO.");
            if(owned&&activeSelectionGeneration!=selection)throw new InvalidOperationException("Release SCO before changing its original target selection.");
            if(intent is not null&&consumed<intent.Generation&&intent.Deadline>began)throw new InvalidOperationException("Previous SCO request is still pending.");
            if(!owned&&latest is not null&&Number(latest,"state") is 1 or 2)throw new InvalidOperationException("SCO was already active outside this controller.");
        }
        var raw=Nav(Obj(("op","read")));var local=Nav(Obj(("op","local.read")));
        var status=Nav(Obj(("op","local.status"),("owner",ownerId),("generation",selection)));var at=Utc();
        var healthNow=raw.GetProperty("shipHealth");var referenceTarget=request.Required("target");
        if(!raw.Bool("inGame")||!raw.Bool("localPlayerShipVerified")||raw.Bool("playerOverride")||!healthNow.Bool("verified")||healthNow.Bool("emergency")
            ||!Fresh(raw.U64("observedAtUnixMilliseconds"),at)||!Fresh(local.U64("observedAtUnixMilliseconds"),at)||deadline<=at||deadline>local.U64("observedAtUnixMilliseconds")+250
            ||!status.Bool("selected")||!status.Bool("telemetryReady")||!local.Bool("inSupercruise")||local.Int("rawFsdState")!=2
            ||!SameTarget(referenceTarget,local.Required("target"))||!SameTarget(referenceTarget,status.Required("target"))
            ||sequence>local.U64("sequence")||local.U64("sequence")-sequence>16||raw.Str("entityId")!=local.Str("entityId")
            ||raw.Str("referenceWorldAddress")!=local.Str("referenceWorldAddress"))throw new InvalidOperationException("Original target owner or fresh SC reference changed.");
        lock(sync)
        {
            if(!enabled||binding?.component is null or 0||raw.Str("entityId")!=entity.ToString()||raw.Str("referenceWorldAddress")!=world.ToString()
                ||owned&&activeOwner!=ownerId||intent is not null&&intent.Owner!=ownerId&&owned||deadline<=Utc())throw new InvalidOperationException("Current main-ship SCO binding changed.");
            intent=new(ownerId,requestId,++generation,selection,deadline,local.Required("target").Clone(),reserve);releaseRequested=false;
            receipt=Obj(("owner",ownerId),("requestId",requestId),("generation",generation),("selectionGeneration",selection),("queued",true),("applied",false),("nativeMethodCalled",false));return Describe();
        }
    }
    private static void StopOwned(Navigation.RecoveryCruiseBinding selected,nuint sco,string reason,JsonObject? telemetryFailure=null,JsonObject? gate=null)
    {
        if(!owned)return;
        var callStarted=Utc();
        if(!Navigation.SetOriginalCruise(selected,sco,false,out var state,out var called,out var before)){issue="original-disable-not-confirmed:"+reason;return;}
        lastOriginalStopCallMilliseconds=Utc()-callStarted;
        exitPolicy.OriginalStopped(called,before,state);
        var stoppedAt=Utc();qualification.Stop(new(activeOwner,activeStartRequestId,activeSelectionGeneration,entity,world),releaseRequestId,stoppedAt,called,before,state,reason=="released");
        lastOriginalStop=Obj(("owner",activeOwner),("startRequestId",activeStartRequestId),("leaseRequestId",lastLeaseRequestId),
            ("selectionGeneration",activeSelectionGeneration),("releaseRequestId",releaseRequestId.Length==0?null:releaseRequestId),("entityId",entity.ToString()),("referenceWorldAddress",world.ToString()),
            ("nativeMethodRva",called?"2eaacd0":null),("nativeMethodCalled",called),("argument",false),("stateBefore",before),("stateAfter",state),
            ("reason",reason),("telemetryFailure",telemetryFailure?.DeepClone()),("gate",gate?.DeepClone()),("stopCallWallMilliseconds",lastOriginalStopCallMilliseconds),("observedAtUnixMilliseconds",stoppedAt),("owningThread",GetCurrentThreadId()));
        if(called)disableCalls++;owned=false;activeOwner="";releaseRequested=false;awaitingOff=true;issue=reason;
    }
    private static void PollControl(Navigation.RecoveryCruiseBinding selected,nuint sco)
    {
        lock(sync)
        {
            var wanted=intent;var now=Utc();
            if(releaseRequested||wanted is null||wanted.Deadline<=now)
            {StopOwned(selected,sco,releaseRequested?"released":wanted is null?"no-lease":"lease-expired");wanted=null;}
            // Derive heat only here, once per original update. Mixing before/after
            // readings divides a whole simulation heat step by a partial wall-clock
            // interval and can retain that artificial peak for the next 250 ms.
            // Continue sampling without a lease so starting requires a real baseline.
            var sample=Navigation.ObserveCruiseControl(selected,sco,wanted?.Target,thread,world);
            if(sample is not null)
            {
                exitPolicy.Observe(ScoExitNativeFrame.Read(sample,exitClock.Epoch,exitClock.CompletedSteps,exitClock.CompletedSeconds,exitClock.Valid,simulationStepSeconds));
                AttachThermalTiming(sample);ObserveHeat((ulong)Number(sample,"observedAtUnixMilliseconds"),Number(sample,"heatPercent"));
            }
            else{heatHistory.Reset();heatSlope=double.NaN;}
            if(wanted is null)return;
            string? reason="native-telemetry";
            JsonObject? gateObservation=null;
            if(sample is not null)
            {
                // Sampling stamps the actual frame. Evaluate its age only after
                // that sampling has finished; the pre-sample clock is older.
                now=Utc();
                var at=(ulong)Number(sample,"observedAtUnixMilliseconds");var heat=Number(sample,"heatPercent");
                var hull=Number(sample,"hullFraction");var shield=Number(sample,"shieldFraction");
                if(double.IsFinite(previousHull)&&hull<previousHull-1e-6||double.IsFinite(previousShield)&&shield<previousShield-1e-6)damageSeen=true;
                previousHull=hull;previousShield=shield;
                var state=(int)Number(sample,"state");
                var gate=new CruiseOverchargeGateFrame{IdentityVerified=Flag(sample,"verified"),GeometryVerified=Flag(sample,"geometryVerified"),
                    TargetVerified=Flag(sample,"targetVerified"),InSupercruise=Flag(sample,"inSupercruise"),RawFsdState=(int)Number(sample,"rawFsdState"),
                    AssistEnabled=Flag(sample,"nativeAssistEnabled"),CollisionCourse=Flag(sample,"collisionCourse"),OverchargeState=state,
                    Emergency=Flag(sample,"healthEmergency")||Flag(sample,"playerOverride")||damageSeen&&double.IsFinite(shield)&&shield<=1e-6,
                    NativeCanEngage=Flag(sample,"nativeCanEngage"),ObservedAt=at,Hull=hull,Heat=heat,
                    HeatRise=GuardHeatRise(at),ThermalInput=ThermalInput(sample,now,wanted.Deadline),
                    MainFuel=Number(sample,"fuelMainTonnes"),FuelCapacity=Number(sample,"fuelCapacityTonnes"),FuelRateUpper=Flag(sample,"fuelBoundVerified")?Number(sample,"fuelRateUpperTonnesPerSecond"):double.NaN,
                    RequiredFuelReserve=wanted.Reserve,Distance=Number(sample,"distanceMetres"),Speed=Number(sample,"speedMetresPerSecond"),AimErrorDegrees=Number(sample,"aimErrorDegrees"),
                    CruiseParametersVerified=Flag(sample,"cruiseParametersVerified"),CruiseApproachSeconds=Number(sample,"cruiseApproachSeconds")};
                gate=gate with{ShipEntity=entity,ReferenceWorld=world,TargetKey=wanted.Target.Int("key"),ExitApproach=exitPolicy.Latest};
                reason=CruiseOverchargeNativeGuard.Refusal(gate,now,wanted.Deadline);
                // This guard covers the exact incoming simulation step, independent of UTC-to-simulation clock mapping.
                // Do not spend cooling from a later HeatTick before this Active call has submitted its gross heat input.
                var frameHeatKnown=CurrentSimulationHeatBound(sample,gate.ThermalInput,now,out var frameHeatPeak);
                if(reason is null&&!frameHeatKnown)reason="thermal-model";
                if(reason is null&&frameHeatPeak>=100)reason="heat";
                var thermalForecast=gate.ThermalInput is {} thermal?ScoThermalPredictor.Predict(thermal):null;
                gateObservation=Obj(("sampleObservedAtUnixMilliseconds",at),("evaluatedAtUnixMilliseconds",now),("leaseExpiresAtUnixMilliseconds",wanted.Deadline),
                    ("heatPercent",gate.Heat),("heatRisePercentPerSecond",gate.HeatRise),
                    ("predictedHeatPercent",thermalForecast is {Known:true}?thermalForecast.DeadlineStopPeakPercent:(double?)null),
                    ("ceilingHeatPercent",100),("thermalModel",sample["thermal"]?.DeepClone()),("thermalForecast",ThermalForecast(thermalForecast)),
                    ("currentFrameGrossHeatBoundKnown",frameHeatKnown),("currentFrameGrossHeatPeakPercent",frameHeatKnown?(double?)frameHeatPeak:null),
                    ("currentSimulationStepSeconds",double.IsFinite(simulationStepSeconds)?(double?)simulationStepSeconds:null),
                    ("currentFrameHeatBoundScope","Full-pressure SCO input and recent other-source envelope over this actual simulation dt; no future radiator credit; other future sources are not an absolute bound"),
                    ("identityVerified",gate.IdentityVerified),("targetVerified",gate.TargetVerified),("geometryVerified",gate.GeometryVerified),
                    ("inSupercruise",gate.InSupercruise),("rawFsdState",gate.RawFsdState),("nativeCanEngage",gate.NativeCanEngage),("nativeRefusalReason",Number(sample!,"nativeRefusalReason")),
                    ("assistEnabled",gate.AssistEnabled),("collisionCourse",gate.CollisionCourse),("emergency",gate.Emergency),
                    ("overchargeState",state),("hullFraction",hull),("shieldFraction",shield),("damageSeen",damageSeen),
                    ("distanceMetres",gate.Distance),("speedMetresPerSecond",gate.Speed),("aimErrorDegrees",gate.AimErrorDegrees),
                    ("cruiseParametersVerified",gate.CruiseParametersVerified),("cruiseApproachSeconds",double.IsFinite(gate.CruiseApproachSeconds)?(double?)gate.CruiseApproachSeconds:null),
                    ("approachStopHorizonSeconds",ScoApproachEnvelope.StopHorizonSeconds(gate.CruiseParametersVerified,gate.CruiseApproachSeconds)),
                    ("scoExitApproach",exitPolicy.Latest.Json()),
                    ("mainFuelTonnes",gate.MainFuel),("fuelCapacityTonnes",gate.FuelCapacity),("requiredFuelReserveTonnes",gate.RequiredFuelReserve),
                    ("fuelRateUpperTonnesPerSecond",gate.FuelRateUpper),("heatObservation",HeatObservation(at)));
                lastGate=gateObservation;
                if(!owned&&state is 1 or 2)reason="foreign-active";
            }
            if(reason is not null)
            {
                StopOwned(selected,sco,reason,sample is null?Navigation.CruiseTelemetryFailure():null,gateObservation);issue="interlock:"+reason;
                if(consumed<wanted.Generation)
                {
                    consumed=failed=wanted.Generation;receipt=Obj(("owner",wanted.Owner),("requestId",wanted.RequestId),("generation",wanted.Generation),
                        ("selectionGeneration",wanted.SelectionGeneration),("applied",false),("failed",true),("reason",reason),("nativeMethodCalled",false),
                        ("nativeObservedAtUnixMilliseconds",sample is null?null:(ulong?)Number(sample,"observedAtUnixMilliseconds")),
                        ("evaluatedAtUnixMilliseconds",now),("expiresAtUnixMilliseconds",wanted.Deadline),("gate",gateObservation?.DeepClone()));
                }
                return;
            }
            if(consumed>=wanted.Generation)return;
            if(wanted.Deadline<=Utc())
            {consumed=failed=wanted.Generation;StopOwned(selected,sco,"expired-before-original-call");receipt=Obj(("requestId",wanted.RequestId),("generation",wanted.Generation),("applied",false),("failed",true),("reason","expired-before-original-call"),("nativeMethodCalled",false));return;}
            var called=false;var actualState=(byte)Number(sample!,"state");
            if(!owned)
            {
                if(!Navigation.SetOriginalCruise(selected,sco,true,out actualState,out called,out var before)){consumed=failed=wanted.Generation;issue="original-enable-not-confirmed";return;}
                if(called)enableCalls++;owned=true;activeOwner=wanted.Owner;activeSelectionGeneration=wanted.SelectionGeneration;activeStartRequestId=wanted.RequestId;releaseRequestId="";
                exitPolicy.BeginOwnedBurst(called,before,actualState);
                qualification.Begin(new(activeOwner,activeStartRequestId,activeSelectionGeneration,entity,world),Utc(),called,actualState);
            }
            lastLeaseRequestId=wanted.RequestId;
            consumed=applied=wanted.Generation;receipt=Obj(("owner",wanted.Owner),("requestId",wanted.RequestId),("generation",wanted.Generation),("selectionGeneration",wanted.SelectionGeneration),
                ("entityId",entity.ToString()),("referenceWorldAddress",world.ToString()),("queued",false),("applied",true),
                ("nativeMethodCalled",called),("nativeMethodRva",called?"2eaacd0":null),("argument",called?(bool?)true:null),("actualStateAfterCall",actualState),
                ("observedAtUnixMilliseconds",Utc()),("owningThread",GetCurrentThreadId()));issue=null;
        }
    }
    // Observe all actual original commands, including engine-side cancellation.
    // The original method is always forwarded exactly once; this hook decides nothing.
    [UnmanagedCallersOnly]private static unsafe void ObserveOriginalCommand(nuint sco,byte engage)
    {
        Navigation.RecoveryCruiseBinding? selected=null;JsonObject? before=null;string source="game-other";ulong began=0;
        try
        {
            lock(sync)
            {
                if(enabled&&GetCurrentThreadId()==thread&&binding?.component==sco)
                {selected=binding;source=ownedSetDepth>0?"toolbox-original-command":originalUpdateDepth>0?"original-sco-update":"game-other";began=Utc();}
            }
            if(selected is not null)before=Navigation.ObserveRecoveryCruise(selected,sco);
        }
        catch{/* Diagnostics cannot prevent forwarding the original call. */}
        try{Call(originalSet,sco,engage);}
        catch{return;}
        if(selected is null)return;
        try
        {
            var after=Navigation.ObserveRecoveryCruise(selected,sco);
            lock(sync)
            {
                originalCommands.Enqueue(Obj(("serial",++originalCommandSerial),("source",source),("argument",engage!=0),
                    ("owner",owned?activeOwner:intent?.Owner),("startRequestId",activeStartRequestId),("leaseRequestId",intent?.RequestId),
                    ("selectionGeneration",intent?.SelectionGeneration??activeSelectionGeneration),("entityId",entity.ToString()),("referenceWorldAddress",world.ToString()),
                    ("observedAtUnixMilliseconds",began),("returnedAtUnixMilliseconds",Utc()),("owningThread",GetCurrentThreadId()),
                    ("stateBefore",before is null?null:Number(before,"state")),("stateAfter",after is null?null:Number(after,"state")),
                    ("nativeCanEngageBefore",before is null?null:(bool?)Flag(before,"nativeCanEngage")),
                    ("nativeRefusalReasonBefore",before is null?null:Number(before,"nativeRefusalReason")),
                    ("nativeCanEngageAfter",after is null?null:(bool?)Flag(after,"nativeCanEngage")),
                    ("nativeRefusalReasonAfter",after is null?null:Number(after,"nativeRefusalReason")),("diagnosticOnly",true)));
                while(originalCommands.Count>12)originalCommands.Dequeue();
            }
        }
        catch{/* Keep original behavior even if observation fails. */}
    }
    [UnmanagedCallersOnly]private static unsafe ulong Observe(nuint sco,float delta,double time)
    {
        Navigation.RecoveryCruiseBinding? selected;lock(sync){calls++;selected=enabled&&GetCurrentThreadId()==thread&&binding?.component==sco?binding:null;}
        if(selected is not null)
        {try{simulationStepSeconds=float.IsFinite(delta)&&delta is >0 and <=.25f?delta:double.NaN;exitClock.AcceptIncoming(delta);originalUpdateThirdArgument=double.IsFinite(time)?time:double.NaN;PollControl(selected,sco);}catch(Exception error){lock(sync){errors++;issue=error.GetType().Name+": "+error.Message;try{StopOwned(selected,sco,"control-error");}catch{}}}}
        ulong result;originalUpdateDepth++;
        try{result=Call(original,(ulong)sco,F32(delta),F64(time));}catch{return 0;}finally{originalUpdateDepth--;}
        if(selected is null)return result;
        exitClock.Complete(delta);
        try
        {
            var tick=GetTickCount64();if(tick-lastSampleTick<20)return result;lastSampleTick=tick;
            JsonElement? target;lock(sync)target=intent?.Target;
            var observed=Navigation.ObserveCruiseControl(selected,sco,target,thread,world);if(observed is null)return result;
            AttachThermalTiming(observed);
            lock(sync)
            {
                if(selected!=binding)return result;
                var at=(ulong)Number(observed,"observedAtUnixMilliseconds");
                observed["heatRisePercentPerSecond"]=double.IsFinite(heatSlope)?(double?)heatSlope:null;
                observed["heatObservation"]=HeatObservation(at);
                observed["overchargeControl"]=true;observed["overchargeCommandsSent"]=enableCalls+disableCalls;latest=observed;matches++;
                if(owned&&Number(observed,"state")==2)activeSamples++;
                qualification.Observe(entity,world,at,(int)Number(observed,"state"));
                if(awaitingOff&&Number(observed,"state") is 0 or 3){offSamples++;awaitingOff=false;}
            }
        }
        catch(Exception error){lock(sync){errors++;issue=error.GetType().Name+": "+error.Message;}}
        return result;
    }
    internal static unsafe uint Start(nuint owningThread)
    {
        lock(sync)
        {
            if(started)return thread==owningThread?1u:0u;
            if(owningThread is 0 or >uint.MaxValue||ImageHash(ModulePath())!=GameHash)return 0;
            image=ImageBase;thread=(uint)owningThread;Navigation.ConfigureRecoveryReadOnlyImage();
            if(!ReadBytes(image+Update,10).AsSpan().SequenceEqual(new byte[]{0x48,0x89,0x5C,0x24,0x18,0x57,0x48,0x83,0xEC,0x40}))return 0;
            if(HookInitialize() is not(0 or 1))return 0;
            var updateHook=HookCreate(image+Update,(nuint)(delegate* unmanaged<nuint,float,double,ulong>)&Observe,out original);
            if(updateHook==3)
            {
                // A disabled 0.5.24 extension can keep its MinHook registration.
                // Require its own inactive receipt and the same live stopped
                // main ship before removing only that original SCO update hook.
                var legacy=NavAsync(Obj(("op","read")),CruiseControlProtocol.LegacyService).GetAwaiter().GetResult();
                var raw=Nav(Obj(("op","read")));var at=Utc();
                if(legacy.Str("service")!=CruiseControlProtocol.LegacyService||legacy.Int("processId")!=ProcessId||legacy.Str("gameSha256")!=GameHash
                    ||legacy.Bool("enabled")||legacy.Bool("owned")||legacy.U64("leaseExpiresAtUnixMilliseconds")>at
                    ||!raw.Bool("inGame")||!raw.Bool("localPlayerShipVerified")||!Fresh(raw.U64("observedAtUnixMilliseconds"),at))return 0;
                var current=Navigation.BindRecoveryCruise(ulong.Parse(raw.Str("entityId")));
                if(!Navigation.RecoveryCruiseStopped(current,ulong.Parse(raw.Str("referenceWorldAddress")))||raw.Str("entityId")!=legacy.Str("watchEntityId")
                    ||raw.Str("referenceWorldAddress")!=legacy.Str("watchReferenceWorld")||HookRemove(image+Update)!=0)return 0;
                replacedLegacyUpdate=true;
                updateHook=HookCreate(image+Update,(nuint)(delegate* unmanaged<nuint,float,double,ulong>)&Observe,out original);
            }
            if(updateHook!=0)return 0;
            var commandHook=HookCreate(image+OriginalSet,(nuint)(delegate* unmanaged<nuint,byte,void>)&ObserveOriginalCommand,out originalSet);
            // The verified inactive predecessor retains its pinned, read-only
            // command observer. Leave that forwarding hook untouched; only the
            // update controller is replaced. Never remove an unknown hook.
            if(commandHook==3&&replacedLegacyUpdate)usingLegacyCommandObserver=true;
            else if(commandHook!=0)return 0;
            if(!Pin((nuint)(delegate* unmanaged<nuint,float,double,ulong>)&Observe)||!Pin((nuint)(delegate* unmanaged<nuint,byte,void>)&ObserveOriginalCommand)
                ||!usingLegacyCommandObserver&&HookEnable(image+OriginalSet)!=0||HookEnable(image+Update)!=0)return 0;
            started=enabled=true;PipeServer.StartJson(Service,32768,Handle);return 1;
        }
    }
    internal static uint SelfTest()
    {
        if(started||ImageHash(ModulePath())==GameHash)return 0;uint result=0;
        var heatTest=new ScoHeatSampleHistory();heatTest.Observe(1000,40);heatTest.Observe(1010,40.2);
        heatTest.Observe(1110,41.7);heatTest.Observe(1210,42.7);heatTest.Observe(1270,43.3);
        if(Math.Abs(heatTest.ConservativeSlope(1270)-15)>1e-7||heatTest.RecentPeakExpiresAt!=1360
            ||Math.Abs(heatTest.ConservativeSlope(1361)-10)>1e-7||heatTest.SlopeStartAt!=1210||heatTest.SlopeEndAt!=1270)return 0;
        var good=new CruiseOverchargeGateFrame{IdentityVerified=true,GeometryVerified=true,TargetVerified=true,InSupercruise=true,NativeCanEngage=true,
            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,
            ThermalInput=new(){Verified=true,HeatCapacity=100,HeatDisplayScale=1,ScoInputUpper=10,OtherInputUpper=5,
                CoolingPassive=10,CoolingMaximum=10,CoolingScale=1,CoolingHeatLower=0,CoolingHeatUpper=1,
                LeaseRemainingSeconds=.15,ResponseDelaySeconds=.01,AfterStopSeconds=.5}};
        if(Key(new('a',32))&&!Key(new('g',32)))result|=1;
        if(CruiseOverchargeNativeGuard.Refusal(good,1100,1250) is null)result|=2;
        if(CruiseOverchargeNativeGuard.Refusal(good with{OverchargeState=3},1100,1250) is not null)result|=4;
        if(CruiseOverchargeNativeGuard.Refusal(good with{InSupercruise=false},1100,1250) is not null)result|=8;
        if(CruiseOverchargeNativeGuard.Refusal(good with{Heat=99.5},1100,1250)=="heat")result|=16;
        if(CruiseOverchargeNativeGuard.Refusal(good with{FuelRateUpper=20},1100,1250)=="fuel")result|=32;
        if(CruiseOverchargeNativeGuard.Refusal(good,1250,1250)=="expired"&&!Fresh(100,301))result|=64;
        var actual=good with{OverchargeState=2,CruiseParametersVerified=true,CruiseApproachSeconds=6,Distance=784.9268650541314*ScoApproachEnvelope.LightSecond,Speed=26.417687132075883*ScoApproachEnvelope.LightSecond};
        if(CruiseOverchargeNativeGuard.Refusal(good with{TargetVerified=false},1100,1250) is not null
            &&CruiseOverchargeNativeGuard.Refusal(actual,1100,1250) is null
            &&CruiseOverchargeNativeGuard.Refusal(actual with{Distance=100*ScoApproachEnvelope.LightSecond},1100,1250)=="approach-or-alignment")result|=128;
        foreach(var op in new[]{"activate","input.set","local.select","cruise.set"})
        {using var request=JsonDocument.Parse(Obj(("op",op)).ToJsonString());try{Handle(request.RootElement);return result;}catch(InvalidOperationException){}}
        result|=256;var status=Describe();if(!status["enabled"]!.GetValue<bool>()&&status["overchargeCommandsSent"]!.GetValue<ulong>()==0)result|=512;
        var cycle=new OriginalCruiseCycleQualification();var identity=new OriginalCruiseCycleIdentity(new('a',32),new('b',32),1,2,3);
        cycle.Begin(identity,1000,true,1);for(ulong i=1;i<=5;i++){cycle.Observe(2,3,1000+i*20,2);cycle.Observe(2,3,1000+i*20,2);}
        if(cycle.ActiveSamples==5&&cycle.Stop(identity,new('c',32),1120,true,2,3,true)&&!cycle.Qualified)
        {cycle.Observe(2,3,1140,3);if(cycle.Qualified)result|=1024;}
        if(Navigation.TestCruiseMotionHistory())result|=2048;
        return result;
    }
}
internal static class CruiseControlExports
{
    [UnmanagedCallersOnly(EntryPoint="XFEInteractionStart")]internal static uint Start(nuint thread){try{return CruiseControlRuntime.Start(thread);}catch{return 0;}}
    [UnmanagedCallersOnly(EntryPoint="XFECruiseControlSelfTest")]internal static uint SelfTest(){try{return CruiseControlRuntime.SelfTest();}catch{return 0;}}
}
#endif