Decompiled source of PhaseSteer v1.1.0

Mods/PhaseSteer.dll

Decompiled 8 hours ago
using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using MelonLoader;
using MelonLoader.Preferences;
using PhaseSteer;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(Core), "PhaseSteer", "1.1.0", "you", null)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")]
[assembly: AssemblyVersion("0.0.0.0")]
namespace PhaseSteer;

public class Core : MelonMod
{
	private MelonPreferences_Entry<bool> _prefEnabled;

	private MelonPreferences_Entry<float> _prefKp;

	private MelonPreferences_Entry<float> _prefMaxPercent;

	private MelonPreferences_Entry<float> _prefTarget;

	private MelonPreferences_Entry<int> _prefToggleKey;

	private MelonPreferences_Entry<int> _prefSummarySeconds;

	private readonly PhaseSteerController _ctl = new PhaseSteerController();

	private bool _steerOn = true;

	private bool _prevKey;

	private int _stepsThisFrame;

	private PhaseSteerController.Phase _lastMode;

	private long _onFrames;

	private long _onAnom;

	private long _offFrames;

	private long _offAnom;

	private double _phaseSum;

	private double _phaseMin = 9.0;

	private double _phaseMax = -9.0;

	private double _dtMin = double.MaxValue;

	private double _dtMax;

	private long _phaseN;

	private long _anomGlitch;

	private long _anomEdge;

	private long _anomOther;

	private long _late;

	private long _stalls;

	private long _paceFrames;

	private bool _prevGlitchy;

	private double _prevPhase = 0.5;

	private float _nextSummary;

	[DllImport("user32.dll")]
	private static extern short GetAsyncKeyState(int vKey);

	public override void OnInitializeMelon()
	{
		MelonPreferences_Category val = MelonPreferences.CreateCategory("PhaseSteer");
		_prefEnabled = val.CreateEntry<bool>("Enabled", true, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
		_prefKp = val.CreateEntry<float>("Kp", 0.01f, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
		_prefMaxPercent = val.CreateEntry<float>("MaxAdjustPercent", 1f, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
		_prefTarget = val.CreateEntry<float>("TargetPhase", 0.3f, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
		_prefToggleKey = val.CreateEntry<int>("ToggleKey", 121, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
		_prefSummarySeconds = val.CreateEntry<int>("SummarySeconds", 60, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
		_steerOn = _prefEnabled.Value;
		_ctl.Kp = Math.Max(0.0, _prefKp.Value);
		_ctl.MaxAdjust = Math.Min(0.05, Math.Max(0.0, (double)_prefMaxPercent.Value / 100.0));
		_ctl.TargetPhase = Math.Min(0.9, Math.Max(0.1, _prefTarget.Value));
		((MelonBase)this).LoggerInstance.Msg("PhaseSteer loaded. Steering " + (_steerOn ? "ON" : "OFF") + " (Kp=" + _ctl.Kp + ", max +/-" + _ctl.MaxAdjust * 100.0 + "%, hotkey vk=" + _prefToggleKey.Value + ").");
		_nextSummary = Time.realtimeSinceStartup + (float)Math.Max(10, _prefSummarySeconds.Value);
	}

	public override void OnFixedUpdate()
	{
		_stepsThisFrame++;
	}

	public override void OnLateUpdate()
	{
		PollHotkey();
		PhaseSteerController.FrameInput f = new PhaseSteerController.FrameInput
		{
			Time = Time.timeAsDouble,
			FixedTime = Time.fixedTimeAsDouble,
			FixedDt = Time.fixedDeltaTime,
			DeltaTime = Time.deltaTime,
			UnscaledDt = Time.unscaledDeltaTime,
			TimeScale = Time.timeScale
		};
		if (_ctl.Step(in f, _steerOn, out var newFixedDt))
		{
			Time.fixedDeltaTime = (float)newFixedDt;
		}
		if (_ctl.Mode != _lastMode)
		{
			if (_ctl.Mode == PhaseSteerController.Phase.Steering)
			{
				((MelonBase)this).LoggerInstance.Msg("Steering engaged (physics steps per frame = " + _ctl.Ratio + ", base step " + (_ctl.BaseDt * 1000.0).ToString("F4") + " ms).");
			}
			else if (_ctl.Mode == PhaseSteerController.Phase.Disabled)
			{
				((MelonBase)this).LoggerInstance.Warning("Steering disabled for safety: " + _ctl.DisabledReason);
			}
			else if (_lastMode == PhaseSteerController.Phase.Steering)
			{
				((MelonBase)this).LoggerInstance.Msg("Steering paused (physics step and frame period no longer match).");
			}
			_lastMode = _ctl.Mode;
		}
		if (_ctl.FrameEma > 0.0 && f.DeltaTime > 0.0 && f.TimeScale > 0.0)
		{
			double num = f.UnscaledDt / _ctl.FrameEma;
			if (num > 1.15)
			{
				_late++;
			}
			if (num > 2.0)
			{
				_stalls++;
			}
			_paceFrames++;
		}
		if (_ctl.Mode == PhaseSteerController.Phase.Steering && _ctl.FrameEma > 0.0 && f.DeltaTime > 0.0 && f.TimeScale > 0.0 && f.UnscaledDt > 0.5 * _ctl.FrameEma && f.UnscaledDt < 1.5 * _ctl.FrameEma)
		{
			bool flag = _stepsThisFrame != _ctl.Ratio;
			bool flag2 = Math.Abs(f.UnscaledDt / _ctl.FrameEma - 1.0) > 0.12;
			if (_steerOn)
			{
				_onFrames++;
				if (flag)
				{
					_onAnom++;
					double num2 = Math.Min(_prevPhase, 1.0 - _prevPhase);
					if (flag2 || _prevGlitchy)
					{
						_anomGlitch++;
					}
					else if (num2 < 0.15)
					{
						_anomEdge++;
					}
					else
					{
						_anomOther++;
					}
				}
			}
			else
			{
				_offFrames++;
				if (flag)
				{
					_offAnom++;
				}
			}
			_phaseSum += _ctl.LastPhase;
			if (_ctl.LastPhase < _phaseMin)
			{
				_phaseMin = _ctl.LastPhase;
			}
			if (_ctl.LastPhase > _phaseMax)
			{
				_phaseMax = _ctl.LastPhase;
			}
			_phaseN++;
			double fixedDt = f.FixedDt;
			if (fixedDt < _dtMin)
			{
				_dtMin = fixedDt;
			}
			if (fixedDt > _dtMax)
			{
				_dtMax = fixedDt;
			}
			_prevGlitchy = flag2;
			_prevPhase = _ctl.LastPhase;
		}
		_stepsThisFrame = 0;
		if (Time.realtimeSinceStartup >= _nextSummary)
		{
			WriteSummary();
			_nextSummary = Time.realtimeSinceStartup + (float)Math.Max(10, _prefSummarySeconds.Value);
		}
	}

	private void PollHotkey()
	{
		int value = _prefToggleKey.Value;
		if (value <= 0)
		{
			return;
		}
		bool flag = (GetAsyncKeyState(value) & 0x8000) != 0;
		if (flag && !_prevKey)
		{
			_steerOn = !_steerOn;
			if (!_steerOn && _ctl.Release(Time.fixedDeltaTime, out var restoreDt))
			{
				Time.fixedDeltaTime = (float)restoreDt;
			}
			((MelonBase)this).LoggerInstance.Msg("Steering " + (_steerOn ? "ON" : "OFF") + " (hotkey).");
		}
		_prevKey = flag;
	}

	private void WriteSummary()
	{
		string text = ((_onFrames > 0) ? ((1000.0 * (double)_onAnom / (double)_onFrames).ToString("F2") + "/1000 over " + _onFrames + " frames") : "no data");
		string text2 = ((_offFrames > 0) ? ((1000.0 * (double)_offAnom / (double)_offFrames).ToString("F2") + "/1000 over " + _offFrames + " frames") : "no data");
		string text3 = ((_phaseN > 0) ? ("phase mean " + (_phaseSum / (double)_phaseN).ToString("F2") + " [" + _phaseMin.ToString("F2") + ".." + _phaseMax.ToString("F2") + "]") : "phase n/a");
		string text4 = ((_dtMax > 0.0) ? ("step " + (_dtMin * 1000.0).ToString("F4") + ".." + (_dtMax * 1000.0).ToString("F4") + " ms") : "step n/a");
		((MelonBase)this).LoggerInstance.Msg("[summary] mode=" + _ctl.Mode.ToString() + " | steering ON: " + text + " | OFF: " + text2 + " | " + text3 + " | " + text4 + " | ON anomalies by cause: late/early frame " + _anomGlitch + ", near boundary " + _anomEdge + ", other " + _anomOther + " | pacing: late frames (>15%) " + _late + ", stalls (>2x) " + _stalls + " of " + _paceFrames + " frames");
		_onFrames = (_onAnom = (_offFrames = (_offAnom = 0L)));
		_anomGlitch = (_anomEdge = (_anomOther = (_late = (_stalls = (_paceFrames = 0L)))));
		_phaseSum = 0.0;
		_phaseN = 0L;
		_phaseMin = 9.0;
		_phaseMax = -9.0;
		_dtMin = double.MaxValue;
		_dtMax = 0.0;
	}
}
internal sealed class PhaseSteerController
{
	public struct FrameInput
	{
		public double Time;

		public double FixedTime;

		public double FixedDt;

		public double DeltaTime;

		public double UnscaledDt;

		public double TimeScale;
	}

	public enum Phase
	{
		Idle,
		Calibrating,
		Steering,
		Disabled
	}

	public double Kp = 0.01;

	public double MaxAdjust = 0.01;

	public double TargetPhase = 0.3;

	public double RatioTolerance = 0.03;

	public int MaxRatio = 4;

	public int EngageAfterFrames = 120;

	public int CalibrationFrames = 600;

	private bool _haveBase;

	private double _lastWritten;

	private int _outliers;

	private int _stable;

	private int _calibCount;

	private readonly int[] _inRange = new int[3];

	private static readonly double[] Offsets = new double[3] { 0.0, 1.0, -1.0 };

	private double _phaseOffset;

	private bool _calibrated;

	private int _monitorCount;

	private int _monitorBad;

	public Phase Mode { get; private set; }

	public string DisabledReason { get; private set; }

	public double BaseDt { get; private set; }

	public double LastPhase { get; private set; }

	public double LastAdjust { get; private set; }

	public int Ratio { get; private set; } = 1;

	public double FrameEma { get; private set; }

	public bool Step(in FrameInput f, bool apply, out double newFixedDt)
	{
		newFixedDt = f.FixedDt;
		if (Mode == Phase.Disabled)
		{
			return false;
		}
		if (!(f.FixedDt > 1E-05) || !(f.DeltaTime > 0.0) || !(f.UnscaledDt > 0.0) || !(f.TimeScale > 0.0))
		{
			return false;
		}
		if (!_haveBase || Math.Abs(f.FixedDt - _lastWritten) > 1E-09 + 1E-06 * _lastWritten)
		{
			BaseDt = f.FixedDt;
			_lastWritten = f.FixedDt;
			_haveBase = true;
		}
		UpdateFrameEma(f.UnscaledDt);
		if (FrameEma <= 0.0)
		{
			return false;
		}
		double num = FrameEma * f.TimeScale / BaseDt;
		double num2 = Math.Round(num);
		if (!(num2 >= 1.0) || !(num2 <= (double)MaxRatio) || !(Math.Abs(num - num2) <= RatioTolerance))
		{
			_stable = 0;
			bool num3 = Mode != Phase.Idle;
			Mode = Phase.Idle;
			_calibCount = 0;
			Array.Clear(_inRange, 0, _inRange.Length);
			if (num3 && apply && Math.Abs(f.FixedDt - BaseDt) > 1E-12)
			{
				_lastWritten = BaseDt;
				newFixedDt = BaseDt;
				return true;
			}
			return false;
		}
		Ratio = (int)num2;
		if (_stable < int.MaxValue)
		{
			_stable++;
		}
		if (_stable < EngageAfterFrames)
		{
			return false;
		}
		double num4 = (f.Time - f.FixedTime) / f.FixedDt;
		if (Mode == Phase.Idle)
		{
			if (_calibrated)
			{
				Mode = Phase.Steering;
				_monitorCount = 0;
				_monitorBad = 0;
			}
			else
			{
				Mode = Phase.Calibrating;
				_calibCount = 0;
				Array.Clear(_inRange, 0, _inRange.Length);
			}
		}
		if (Mode == Phase.Calibrating)
		{
			_calibCount++;
			for (int i = 0; i < Offsets.Length; i++)
			{
				double num5 = num4 + Offsets[i];
				if (num5 >= -0.02 && num5 <= 1.02)
				{
					_inRange[i]++;
				}
			}
			if (_calibCount >= CalibrationFrames)
			{
				int num6 = 0;
				for (int j = 1; j < Offsets.Length; j++)
				{
					if (_inRange[j] > _inRange[num6])
					{
						num6 = j;
					}
				}
				double num7 = (double)_inRange[num6] / (double)_calibCount;
				if (num7 >= 0.97)
				{
					_phaseOffset = Offsets[num6];
					_calibrated = true;
					Mode = Phase.Steering;
					_monitorCount = 0;
					_monitorBad = 0;
				}
				else
				{
					Disable("phase model does not match this game (only " + (int)(num7 * 100.0) + "% of frames had phase in [0,1))");
				}
			}
			return false;
		}
		num4 = (LastPhase = num4 + _phaseOffset);
		_monitorCount++;
		if (num4 < -0.05 || num4 > 1.05)
		{
			_monitorBad++;
		}
		if (_monitorCount >= 600)
		{
			if ((double)_monitorBad > 0.2 * (double)_monitorCount)
			{
				Disable("phase left the expected range too often (" + _monitorBad + " of " + _monitorCount + " frames)");
				if (apply && Math.Abs(f.FixedDt - BaseDt) > 1E-12)
				{
					_lastWritten = BaseDt;
					newFixedDt = BaseDt;
					return true;
				}
				return false;
			}
			_monitorCount = 0;
			_monitorBad = 0;
		}
		if (num4 < -0.5 || num4 > 1.5)
		{
			return false;
		}
		if (!apply)
		{
			LastAdjust = 0.0;
			return false;
		}
		double num9 = Kp * (num4 - TargetPhase);
		if (num9 > MaxAdjust)
		{
			num9 = MaxAdjust;
		}
		else if (num9 < 0.0 - MaxAdjust)
		{
			num9 = 0.0 - MaxAdjust;
		}
		LastAdjust = num9;
		float num10 = (float)(BaseDt * (1.0 + num9));
		_lastWritten = num10;
		newFixedDt = num10;
		return true;
	}

	public bool Release(double currentFixedDt, out double restoreDt)
	{
		restoreDt = currentFixedDt;
		if (_haveBase && Math.Abs(currentFixedDt - BaseDt) > 1E-12)
		{
			_lastWritten = BaseDt;
			restoreDt = BaseDt;
			LastAdjust = 0.0;
			return true;
		}
		return false;
	}

	private void Disable(string reason)
	{
		Mode = Phase.Disabled;
		DisabledReason = reason;
	}

	private void UpdateFrameEma(double unscaledDt)
	{
		if (FrameEma <= 0.0)
		{
			FrameEma = unscaledDt;
			_outliers = 0;
		}
		else if (unscaledDt > 0.85 * FrameEma && unscaledDt < 1.15 * FrameEma)
		{
			FrameEma += 0.02 * (unscaledDt - FrameEma);
			_outliers = 0;
		}
		else if (++_outliers >= 30)
		{
			if (unscaledDt >= 1.0 / 360.0 && unscaledDt <= 1.0 / 24.0)
			{
				FrameEma = unscaledDt;
			}
			_outliers = 0;
		}
	}
}