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

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
#if PLUGIN_CRUISE_CONTROL || PLUGIN_CRUISE_OBSERVATION
namespace XFEEDToolBox.Plugin;

internal static unsafe partial class Navigation
{
    internal static JsonObject? ObserveScoThermal(in Snapshot frame,nuint sco)
    {
        if(!ReadSharedScoThermal(out var source))return Obj(("verified",false),("reason","main-loader-thermal-unavailable"));
        var now=UtcMilliseconds();var root=sco-0x58;
        var same=source.Entity==frame.entity&&source.World==frame.referenceWorld&&source.HeatManager==frame.heatManager;
        var fresh=source.ObservedAtUnixMilliseconds<=now&&now-source.ObservedAtUnixMilliseconds<=200;
        var component=sco>=0x10000&&Pointer(sco)==image+0x533E6E0&&Pointer(root)==image+0x533E5B0
            &&Pointer(Pointer(sco)+0x20)==image+0x2EAACD0&&Pointer(root+0x168)==frame.heatManager+0x60;
        var multiplier=double.NaN;var pressure=double.NaN;
        var provider=component?Pointer(root+0x258):0;
        if(provider>=0x10000&&Pointer(provider)==image+0x532A288&&Pointer(Pointer(provider)+0x20)==image+0x2DCDC40)
        {
            var module=Pointer(provider+0x5B8);var detail=module>=0x10000?Pointer(module+0x60):0;
            var inner=detail>=0x10000?Pointer(detail+8)&~(nuint)3:0;
            var entry=inner>=0x10000?Pointer(inner+0x30):0;var parameters=entry>=0x10000?Pointer(entry+0x30):0;
            if(parameters>=0x10000){multiplier=Read<float>(parameters+0x214);pressure=Read<float>(root+0x298);}
        }
        var parametersReady=double.IsFinite(multiplier)&&multiplier>=0&&double.IsFinite(pressure)&&pressure is >=0 and <=1;
        var future=default(XFEEDToolBox.Core.Navigation.OriginalCurveRange);var current=default(XFEEDToolBox.Core.Navigation.OriginalCurveRange);
        var curveReady=component&&CruiseCurveBound(root+0x2E0,out future,out _,out _);
        // A point interval retains the original interpolation/rounding bounds. The full interval is a future pressure envelope, not the current rate.
        var pointReady=curveReady&&parametersReady&&ScoThermalCurvePoint(root+0x2E0,pressure,out current);
        var scoUpper=curveReady&&parametersReady?Math.Max(0,future.Maximum)*multiplier:double.NaN;
        // Read the shared pool again on this owning-thread sample. A previously published pre-radiator value is not the current baseline.
        var storedNow=same&&component?ScoThermalEncoded(frame.heatManager+0x190):double.NaN;
        var storedAt=UtcMilliseconds();
        var storedHeat=source.HeatCapacity>0&&source.HeatDisplayScale>0?storedNow*100/(source.HeatCapacity*source.HeatDisplayScale):double.NaN;
        var rows=new JsonArray();
        for(var index=0;index<source.SourceCount;index++)
        {
            var row=source.Sources[index];rows.Add((JsonNode)Obj(("sourceId",row.Id.ToString("x")),("label",ScoThermalSourceLabel((nuint)row.Id)),
                ("verifiedSco",row.VerifiedSco!=0),("requestedEnergyPerSecond",row.RequestedRate),("upperEnergyPerSecond",row.UpperRate)));
        }
        static JsonNode? Number(double value)=>double.IsFinite(value)?JsonValue.Create(value):null;
        var verified=same&&fresh&&source.Ready!=0&&component&&curveReady&&parametersReady&&pointReady&&double.IsFinite(scoUpper)
            &&double.IsFinite(storedNow)&&storedNow>=0&&double.IsFinite(storedHeat)&&storedHeat>=0;
        return Obj(("schema",1),("verified",verified),("entityId",source.Entity.ToString()),("referenceWorldAddress",source.World.ToString()),
            ("sourceObservedAtUnixMilliseconds",source.ObservedAtUnixMilliseconds),("heatObservedAtUnixMilliseconds",frame.utcMilliseconds),
            ("heatPercent",HeatHudPercent(frame)),("heatCapacity",source.HeatCapacity),("heatDisplayScale",source.HeatDisplayScale),
            ("otherInputUpper",source.OtherInputUpper),("scoInputUpper",Number(scoUpper)),("scoObservedInputUpper",source.ScoObservedInputUpper),
            ("coolingPassive",source.CoolingPassive),("coolingMaximum",source.CoolingMaximum),("coolingScale",source.CoolingScale*.97),
            ("nativeCoolingScale",source.CoolingScale),("coolingLowerBoundFactor",.97),
            ("coolingBoundScope","original radiator curve times native scale times 0.97; matches the existing minimum actual-to-curve verification ratio"),
            ("coolingHeatLower",source.CoolingHeatLower),("coolingHeatUpper",source.CoolingHeatUpper),("coolingActual",source.CoolingActual),
            ("coolingActive",source.CoolingActive!=0),("nativeHeatStepSeconds",source.NativeHeatStepSeconds),
            ("sourceWindowSimulationSeconds",source.WindowSimulationSeconds),("totalRequestedEnergyPerSecond",source.TotalRequestedRate),
            ("pressure",Number(pressure)),("scoHeatMultiplier",Number(multiplier)),("scoCurveMinimum",curveReady?Number(future.Minimum):null),
            ("scoCurveMaximum",curveReady?Number(future.Maximum):null),("scoCurrentRateMinimum",pointReady?Number(Math.Max(0,current.Minimum)*multiplier):null),
            ("scoCurrentRateMaximum",pointReady?Number(Math.Max(0,current.Maximum)*multiplier):null),
            ("storedEnergy",source.StoredEnergy),("storedEnergyHeatPercent",source.HeatCapacity>0&&source.HeatDisplayScale>0?Number(source.StoredEnergy*100/(source.HeatCapacity*source.HeatDisplayScale)):null),
            ("currentStoredEnergy",Number(storedNow)),("currentStoredEnergyHeatPercent",Number(storedHeat)),("currentStoredEnergyObservedAtUnixMilliseconds",storedAt),
            ("sources",rows),("sourceAccounting","requested-energy-upper"),("environmentSeparated",false),
            ("otherBoundScope","positive per-source observed envelopes over the last 0.2 simulation seconds; unidentified sources retained"),
            ("scoBoundScope","original 2E0 curve pressure 0..1 times module heat multiplier +214; SCO-off future contribution is zero"));
    }
    private static bool ScoThermalCurvePoint(nuint curve,double pressure,out XFEEDToolBox.Core.Navigation.OriginalCurveRange value)
    {
        value=default;var mode=Read<int>(curve);var count=Read<ulong>(curve+0x10);var data=Pointer(curve+0x18);
        if(count is 0 or >256||data<0x10000)return false;
        var points=new XFEEDToolBox.Core.Navigation.OriginalCurveKnot[(int)count];
        for(var index=0;index<points.Length;index++)
        {var at=data+(nuint)index*20;points[index]=new(Read<float>(at),Read<float>(at+4),Read<float>(at+12),Read<float>(at+16));}
        return XFEEDToolBox.Core.Navigation.OriginalCurveBounds.TryBound(points,mode,pressure,pressure,out value);
    }
}
#endif