From cde93a81da732f07f23f88d4de9072078bf9e618 Mon Sep 17 00:00:00 2001 From: DevoLutioner <127433058+DevoLutioner@users.noreply.github.com> Date: Tue, 11 Aug 2026 19:24:33 +0800 Subject: [PATCH 1/6] fix: support Koikatu legacy Mono runtime --- CHANGELOG.md | 4 +++ README.md | 3 +-- .../StudioFrameStabilizer.csproj | 8 +++--- .../StudioFrameStabilizerPlugin.cs | 27 ++++++++++--------- tools/Build-StudioFrameStabilizer.ps1 | 3 +-- 5 files changed, 25 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cb64bb..38ac453 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.1.1 - 2026-08-11 + +- Avoid modern BCL helpers so the plugin starts on Koikatu's legacy Unity Mono runtime. + ## 0.1.0 - 2026-08-11 - Initial Studio-only frame pacing, background-load budgeting and hitch diagnostics. diff --git a/README.md b/README.md index 9cbe0c4..83e3a87 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ 面向 Koikatu CharaStudio 的低风险帧稳定与诊断插件。只在 `CharaStudio.exe` 加载。 -## 0.1.0 +## 0.1.1 - 使用 1 ms Windows 计时精度改善限帧节奏。 - 将 Studio 进程设为 AboveNormal,减少普通后台程序抢占。 @@ -19,4 +19,3 @@ BepInEx/config/codex.koikatu.studioframestabilizer.cfg ``` 所有优化项都可单独关闭。插件不会强制 GC、修改角色数据或改动物理参数。 - diff --git a/src/StudioFrameStabilizer/StudioFrameStabilizer.csproj b/src/StudioFrameStabilizer/StudioFrameStabilizer.csproj index aa91eee..c2afca4 100644 --- a/src/StudioFrameStabilizer/StudioFrameStabilizer.csproj +++ b/src/StudioFrameStabilizer/StudioFrameStabilizer.csproj @@ -1,13 +1,13 @@ net472 - latest + 7.3 disable StudioFrameStabilizer StudioFrameStabilizer - 0.1.0 - 0.1.0.0 - 0.1.0.0 + 0.1.1 + 0.1.1.0 + 0.1.1.0 Z:\Koikatu diff --git a/src/StudioFrameStabilizer/StudioFrameStabilizerPlugin.cs b/src/StudioFrameStabilizer/StudioFrameStabilizerPlugin.cs index 790cd7f..70af9c3 100644 --- a/src/StudioFrameStabilizer/StudioFrameStabilizerPlugin.cs +++ b/src/StudioFrameStabilizer/StudioFrameStabilizerPlugin.cs @@ -9,7 +9,8 @@ using UnityEngine; using UnityEngine.SceneManagement; -namespace StudioFrameStabilizer; +namespace StudioFrameStabilizer +{ [BepInProcess("CharaStudio.exe")] [BepInPlugin(PluginGuid, PluginName, PluginVersion)] @@ -17,7 +18,7 @@ public sealed class StudioFrameStabilizerPlugin : BaseUnityPlugin { public const string PluginGuid = "codex.koikatu.studioframestabilizer"; public const string PluginName = "Studio Frame Stabilizer"; - public const string PluginVersion = "0.1.0"; + public const string PluginVersion = "0.1.1"; private const int FrameCapacity = 2048; private const int TimerResolutionMs = 1; @@ -90,7 +91,7 @@ private void Awake() ApplySettings(); _enabled.SettingChanged += OnEnabledChanged; - Logger.LogInfo("Studio Frame Stabilizer 0.1.0 loaded. F11 toggles the frame overlay."); + Logger.LogInfo("Studio Frame Stabilizer 0.1.1 loaded. F11 toggles the frame overlay."); } private void BindConfig() @@ -99,16 +100,16 @@ private void BindConfig() "Apply frame pacing and background-load stability settings."); _targetFrameRate = Config.Bind("Frame pacing", "Target frame rate", 60, new ConfigDescription("Unity frame-rate cap. Set 0 to leave unchanged.", - new AcceptableValueRange(0, 240))); + new AcceptableValueRange(0, 240), new object[0])); _vSyncCount = Config.Bind("Frame pacing", "VSync count", -1, new ConfigDescription("-1 keeps the current setting; 0 disables VSync; 1 enables it.", - new AcceptableValueRange(-1, 1))); + new AcceptableValueRange(-1, 1), new object[0])); _maximumDeltaTime = Config.Bind("Frame pacing", "Maximum catch-up delta seconds", 0.10f, new ConfigDescription("Limits simulation catch-up after a long stall.", - new AcceptableValueRange(0.05f, 0.3333333f))); + new AcceptableValueRange(0.05f, 0.3333333f), new object[0])); _maximumParticleDeltaTime = Config.Bind("Frame pacing", "Maximum particle delta seconds", 0.0333333f, new ConfigDescription("Limits particle catch-up work after a hitch.", - new AcceptableValueRange(0.0166667f, 0.10f))); + new AcceptableValueRange(0.0166667f, 0.10f), new object[0])); _highResolutionTimer = Config.Bind("System", "Use 1 ms timer resolution", true, "Improves sleep and frame-cap timing precision while Studio is open."); @@ -118,21 +119,22 @@ private void BindConfig() "Trades slightly longer loads for fewer frame spikes while assets stream in."); _asyncUploadTimeSlice = Config.Bind("Loading", "Async upload time slice ms", 2, new ConfigDescription("Per-frame GPU upload CPU budget, when supported by this Unity build.", - new AcceptableValueRange(1, 8))); + new AcceptableValueRange(1, 8), new object[0])); _asyncUploadBufferSize = Config.Bind("Loading", "Async upload buffer MB", 32, new ConfigDescription("Upload ring buffer size, when supported by this Unity build.", - new AcceptableValueRange(16, 128))); + new AcceptableValueRange(16, 128), new object[0])); _reportInterval = Config.Bind("Diagnostics", "Report interval seconds", 10f, new ConfigDescription("Interval for rolling frame summaries.", - new AcceptableValueRange(5f, 60f))); + new AcceptableValueRange(5f, 60f), new object[0])); _hitchThresholdMs = Config.Bind("Diagnostics", "Hitch threshold ms", 50f, new ConfigDescription("Frames slower than this are counted as hitches.", - new AcceptableValueRange(25f, 250f))); + new AcceptableValueRange(25f, 250f), new object[0])); _logReports = Config.Bind("Diagnostics", "Log frame reports", true, "Write one compact SFS_FRAME report per interval."); _overlayKey = Config.Bind("Diagnostics", "Overlay key", - new KeyboardShortcut(KeyCode.F11), "Toggle the lightweight frame statistics overlay."); + new KeyboardShortcut(KeyCode.F11, new KeyCode[0]), + "Toggle the lightweight frame statistics overlay."); } private void ApplySettings() @@ -395,3 +397,4 @@ private static void Prefix() } } } +} diff --git a/tools/Build-StudioFrameStabilizer.ps1 b/tools/Build-StudioFrameStabilizer.ps1 index 90bd621..c6e0e80 100644 --- a/tools/Build-StudioFrameStabilizer.ps1 +++ b/tools/Build-StudioFrameStabilizer.ps1 @@ -1,6 +1,6 @@ param( [string]$GameRoot = 'Z:\Koikatu', - [string]$Version = '0.1.0', + [string]$Version = '0.1.1', [switch]$Force ) @@ -33,4 +33,3 @@ $hash = (Get-FileHash -LiteralPath $zip -Algorithm SHA256).Hash.ToLowerInvariant Set-Content -LiteralPath "$zip.sha256" -Value "$hash $([IO.Path]::GetFileName($zip))" -Encoding ASCII Write-Host "Archive: $zip" Write-Host "SHA256: $hash" - From 463ea39198418908c6567f65e552bceab4d54cc2 Mon Sep 17 00:00:00 2001 From: DevoLutioner <127433058+DevoLutioner@users.noreply.github.com> Date: Tue, 11 Aug 2026 19:26:58 +0800 Subject: [PATCH 2/6] fix: avoid unsupported reflection operators --- CHANGELOG.md | 4 ++++ README.md | 2 +- src/StudioFrameStabilizer/StudioFrameStabilizer.csproj | 6 +++--- src/StudioFrameStabilizer/StudioFrameStabilizerPlugin.cs | 7 ++++--- tools/Build-StudioFrameStabilizer.ps1 | 2 +- 5 files changed, 13 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38ac453..c4e8d1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.1.2 - 2026-08-11 + +- Avoid modern reflection comparison operators missing from Koikatu's legacy Mono runtime. + ## 0.1.1 - 2026-08-11 - Avoid modern BCL helpers so the plugin starts on Koikatu's legacy Unity Mono runtime. diff --git a/README.md b/README.md index 83e3a87..e6c4333 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ 面向 Koikatu CharaStudio 的低风险帧稳定与诊断插件。只在 `CharaStudio.exe` 加载。 -## 0.1.1 +## 0.1.2 - 使用 1 ms Windows 计时精度改善限帧节奏。 - 将 Studio 进程设为 AboveNormal,减少普通后台程序抢占。 diff --git a/src/StudioFrameStabilizer/StudioFrameStabilizer.csproj b/src/StudioFrameStabilizer/StudioFrameStabilizer.csproj index c2afca4..f4d345c 100644 --- a/src/StudioFrameStabilizer/StudioFrameStabilizer.csproj +++ b/src/StudioFrameStabilizer/StudioFrameStabilizer.csproj @@ -5,9 +5,9 @@ disable StudioFrameStabilizer StudioFrameStabilizer - 0.1.1 - 0.1.1.0 - 0.1.1.0 + 0.1.2 + 0.1.2.0 + 0.1.2.0 Z:\Koikatu diff --git a/src/StudioFrameStabilizer/StudioFrameStabilizerPlugin.cs b/src/StudioFrameStabilizer/StudioFrameStabilizerPlugin.cs index 70af9c3..e8288f4 100644 --- a/src/StudioFrameStabilizer/StudioFrameStabilizerPlugin.cs +++ b/src/StudioFrameStabilizer/StudioFrameStabilizerPlugin.cs @@ -18,7 +18,7 @@ public sealed class StudioFrameStabilizerPlugin : BaseUnityPlugin { public const string PluginGuid = "codex.koikatu.studioframestabilizer"; public const string PluginName = "Studio Frame Stabilizer"; - public const string PluginVersion = "0.1.1"; + public const string PluginVersion = "0.1.2"; private const int FrameCapacity = 2048; private const int TimerResolutionMs = 1; @@ -91,7 +91,7 @@ private void Awake() ApplySettings(); _enabled.SettingChanged += OnEnabledChanged; - Logger.LogInfo("Studio Frame Stabilizer 0.1.1 loaded. F11 toggles the frame overlay."); + Logger.LogInfo("Studio Frame Stabilizer 0.1.2 loaded. F11 toggles the frame overlay."); } private void BindConfig() @@ -349,7 +349,8 @@ private void TrySetQualityProperty(string propertyName, int value) { PropertyInfo property = typeof(QualitySettings).GetProperty(propertyName, BindingFlags.Public | BindingFlags.Static); - if (property != null && property.CanWrite && property.PropertyType == typeof(int)) + if (!ReferenceEquals(property, null) && property.CanWrite && + property.PropertyType == typeof(int)) property.SetValue(null, value, null); } catch (Exception ex) diff --git a/tools/Build-StudioFrameStabilizer.ps1 b/tools/Build-StudioFrameStabilizer.ps1 index c6e0e80..a714d97 100644 --- a/tools/Build-StudioFrameStabilizer.ps1 +++ b/tools/Build-StudioFrameStabilizer.ps1 @@ -1,6 +1,6 @@ param( [string]$GameRoot = 'Z:\Koikatu', - [string]$Version = '0.1.1', + [string]$Version = '0.1.2', [switch]$Force ) From d0d298d66708515a43449ef671a1ce17ce861ba9 Mon Sep 17 00:00:00 2001 From: DevoLutioner <127433058+DevoLutioner@users.noreply.github.com> Date: Tue, 11 Aug 2026 19:29:25 +0800 Subject: [PATCH 3/6] fix: remove final legacy BCL operator --- CHANGELOG.md | 4 ++++ README.md | 2 +- src/StudioFrameStabilizer/StudioFrameStabilizer.csproj | 6 +++--- src/StudioFrameStabilizer/StudioFrameStabilizerPlugin.cs | 6 +++--- tools/Build-StudioFrameStabilizer.ps1 | 2 +- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4e8d1e..d1ee47a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.1.3 - 2026-08-11 + +- Remove the last legacy-incompatible `System.Type` equality operator call. + ## 0.1.2 - 2026-08-11 - Avoid modern reflection comparison operators missing from Koikatu's legacy Mono runtime. diff --git a/README.md b/README.md index e6c4333..83a47b7 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ 面向 Koikatu CharaStudio 的低风险帧稳定与诊断插件。只在 `CharaStudio.exe` 加载。 -## 0.1.2 +## 0.1.3 - 使用 1 ms Windows 计时精度改善限帧节奏。 - 将 Studio 进程设为 AboveNormal,减少普通后台程序抢占。 diff --git a/src/StudioFrameStabilizer/StudioFrameStabilizer.csproj b/src/StudioFrameStabilizer/StudioFrameStabilizer.csproj index f4d345c..bd9509a 100644 --- a/src/StudioFrameStabilizer/StudioFrameStabilizer.csproj +++ b/src/StudioFrameStabilizer/StudioFrameStabilizer.csproj @@ -5,9 +5,9 @@ disable StudioFrameStabilizer StudioFrameStabilizer - 0.1.2 - 0.1.2.0 - 0.1.2.0 + 0.1.3 + 0.1.3.0 + 0.1.3.0 Z:\Koikatu diff --git a/src/StudioFrameStabilizer/StudioFrameStabilizerPlugin.cs b/src/StudioFrameStabilizer/StudioFrameStabilizerPlugin.cs index e8288f4..b0a7363 100644 --- a/src/StudioFrameStabilizer/StudioFrameStabilizerPlugin.cs +++ b/src/StudioFrameStabilizer/StudioFrameStabilizerPlugin.cs @@ -18,7 +18,7 @@ public sealed class StudioFrameStabilizerPlugin : BaseUnityPlugin { public const string PluginGuid = "codex.koikatu.studioframestabilizer"; public const string PluginName = "Studio Frame Stabilizer"; - public const string PluginVersion = "0.1.2"; + public const string PluginVersion = "0.1.3"; private const int FrameCapacity = 2048; private const int TimerResolutionMs = 1; @@ -91,7 +91,7 @@ private void Awake() ApplySettings(); _enabled.SettingChanged += OnEnabledChanged; - Logger.LogInfo("Studio Frame Stabilizer 0.1.2 loaded. F11 toggles the frame overlay."); + Logger.LogInfo("Studio Frame Stabilizer 0.1.3 loaded. F11 toggles the frame overlay."); } private void BindConfig() @@ -350,7 +350,7 @@ private void TrySetQualityProperty(string propertyName, int value) PropertyInfo property = typeof(QualitySettings).GetProperty(propertyName, BindingFlags.Public | BindingFlags.Static); if (!ReferenceEquals(property, null) && property.CanWrite && - property.PropertyType == typeof(int)) + ReferenceEquals(property.PropertyType, typeof(int))) property.SetValue(null, value, null); } catch (Exception ex) diff --git a/tools/Build-StudioFrameStabilizer.ps1 b/tools/Build-StudioFrameStabilizer.ps1 index a714d97..d175b5b 100644 --- a/tools/Build-StudioFrameStabilizer.ps1 +++ b/tools/Build-StudioFrameStabilizer.ps1 @@ -1,6 +1,6 @@ param( [string]$GameRoot = 'Z:\Koikatu', - [string]$Version = '0.1.2', + [string]$Version = '0.1.3', [switch]$Force ) From 9c3c9de6ea51d85a4c9210323d7d572254f579db Mon Sep 17 00:00:00 2001 From: DevoLutioner <127433058+DevoLutioner@users.noreply.github.com> Date: Tue, 11 Aug 2026 19:34:28 +0800 Subject: [PATCH 4/6] feat: classify hitches and auto-tune VSync --- CHANGELOG.md | 5 + README.md | 4 +- .../StudioFrameStabilizer.csproj | 6 +- .../StudioFrameStabilizerPlugin.cs | 96 ++++++++++++++++--- tools/Build-StudioFrameStabilizer.ps1 | 2 +- 5 files changed, 97 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1ee47a..d8dced8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 0.2.0 - 2026-08-11 + +- Automatically choose a VSync divisor near the configured frame-rate target. +- Correlate hitches with GC, Studio activity windows and BepInEx/Unity log bursts. + ## 0.1.3 - 2026-08-11 - Remove the last legacy-incompatible `System.Type` equality operator call. diff --git a/README.md b/README.md index 83a47b7..721125a 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,16 @@ 面向 Koikatu CharaStudio 的低风险帧稳定与诊断插件。只在 `CharaStudio.exe` 加载。 -## 0.1.3 +## 0.2.0 - 使用 1 ms Windows 计时精度改善限帧节奏。 - 将 Studio 进程设为 AboveNormal,减少普通后台程序抢占。 - 降低 Unity 后台资源加载优先级,并限制异步上传的单帧预算。 - 将长暂停后的模拟追帧限制到 100 ms、粒子追帧限制到约 33 ms。 - 默认将 Studio 目标帧率设为 60,不强制修改 VSync。 +- 高刷新率且 VSync 开启时自动选择分频,避免 60 FPS 目标被 Unity 忽略后持续跑到约 150 FPS。 - 每 10 秒输出 `SFS_FRAME`:平均 FPS、1% low、P95 帧时间、卡顿次数、最差帧、GC 卡顿及换人/换动作窗口卡顿。 +- 将卡顿进一步分为 GC、场景/动作活动、日志突发和未知主线程停顿。 - `F11` 显示或隐藏帧统计浮层。 配置文件首次启动后生成: diff --git a/src/StudioFrameStabilizer/StudioFrameStabilizer.csproj b/src/StudioFrameStabilizer/StudioFrameStabilizer.csproj index bd9509a..f41815b 100644 --- a/src/StudioFrameStabilizer/StudioFrameStabilizer.csproj +++ b/src/StudioFrameStabilizer/StudioFrameStabilizer.csproj @@ -5,9 +5,9 @@ disable StudioFrameStabilizer StudioFrameStabilizer - 0.1.3 - 0.1.3.0 - 0.1.3.0 + 0.2.0 + 0.2.0.0 + 0.2.0.0 Z:\Koikatu diff --git a/src/StudioFrameStabilizer/StudioFrameStabilizerPlugin.cs b/src/StudioFrameStabilizer/StudioFrameStabilizerPlugin.cs index b0a7363..1a107e2 100644 --- a/src/StudioFrameStabilizer/StudioFrameStabilizerPlugin.cs +++ b/src/StudioFrameStabilizer/StudioFrameStabilizerPlugin.cs @@ -2,8 +2,10 @@ using System.Diagnostics; using System.Reflection; using System.Runtime.InteropServices; +using System.Threading; using BepInEx; using BepInEx.Configuration; +using BepInEx.Logging; using HarmonyLib; using Studio; using UnityEngine; @@ -18,7 +20,7 @@ public sealed class StudioFrameStabilizerPlugin : BaseUnityPlugin { public const string PluginGuid = "codex.koikatu.studioframestabilizer"; public const string PluginName = "Studio Frame Stabilizer"; - public const string PluginVersion = "0.1.3"; + public const string PluginVersion = "0.2.0"; private const int FrameCapacity = 2048; private const int TimerResolutionMs = 1; @@ -29,6 +31,7 @@ public sealed class StudioFrameStabilizerPlugin : BaseUnityPlugin private ConfigEntry _enabled; private ConfigEntry _targetFrameRate; private ConfigEntry _vSyncCount; + private ConfigEntry _autoVSyncDivisor; private ConfigEntry _highResolutionTimer; private ConfigEntry _raiseProcessPriority; private ConfigEntry _lowerBackgroundLoadingPriority; @@ -38,12 +41,13 @@ public sealed class StudioFrameStabilizerPlugin : BaseUnityPlugin private ConfigEntry _asyncUploadBufferSize; private ConfigEntry _reportInterval; private ConfigEntry _hitchThresholdMs; + private ConfigEntry _logBurstThreshold; private ConfigEntry _logReports; private ConfigEntry _overlayKey; private Harmony _harmony; private ProcessPriorityClass _originalPriority; - private ThreadPriority _originalLoadingPriority; + private UnityEngine.ThreadPriority _originalLoadingPriority; private int _originalTargetFrameRate; private int _originalVSyncCount; private float _originalMaximumDeltaTime; @@ -57,7 +61,11 @@ public sealed class StudioFrameStabilizerPlugin : BaseUnityPlugin private int _hitchCount; private int _gcHitchCount; private int _activityHitchCount; + private int _logHitchCount; + private int _unknownHitchCount; + private int _reportLogEventCount; private float _worstFrameMs; + private string _lastHitchLogSource = "none"; private float _reportElapsed; private float _activityWindowUntil; private int _lastGc0; @@ -66,6 +74,7 @@ public sealed class StudioFrameStabilizerPlugin : BaseUnityPlugin private bool _overlayVisible; private string _overlayText = "Studio Frame Stabilizer: collecting..."; private GUIStyle _overlayStyle; + private readonly FrameLogListener _frameLogListener = new FrameLogListener(); private static StudioFrameStabilizerPlugin _instance; @@ -84,6 +93,7 @@ private void Awake() _lastGc2 = GC.CollectionCount(2); SceneManager.sceneLoaded += OnSceneLoaded; + BepInEx.Logging.Logger.Listeners.Add(_frameLogListener); _harmony = new Harmony(PluginGuid); _harmony.PatchAll(Assembly.GetExecutingAssembly()); @@ -91,7 +101,7 @@ private void Awake() ApplySettings(); _enabled.SettingChanged += OnEnabledChanged; - Logger.LogInfo("Studio Frame Stabilizer 0.1.3 loaded. F11 toggles the frame overlay."); + Logger.LogInfo("Studio Frame Stabilizer 0.2.0 loaded. F11 toggles the frame overlay."); } private void BindConfig() @@ -102,8 +112,10 @@ private void BindConfig() new ConfigDescription("Unity frame-rate cap. Set 0 to leave unchanged.", new AcceptableValueRange(0, 240), new object[0])); _vSyncCount = Config.Bind("Frame pacing", "VSync count", -1, - new ConfigDescription("-1 keeps the current setting; 0 disables VSync; 1 enables it.", - new AcceptableValueRange(-1, 1), new object[0])); + new ConfigDescription("-1 uses automatic/unchanged behavior; 0 disables VSync; positive values are the vertical-blank divisor.", + new AcceptableValueRange(-1, 4), new object[0])); + _autoVSyncDivisor = Config.Bind("Frame pacing", "Auto-select VSync divisor", true, + "When VSync is active, select a divisor that keeps the actual refresh rate near the target instead of ignoring the frame cap."); _maximumDeltaTime = Config.Bind("Frame pacing", "Maximum catch-up delta seconds", 0.10f, new ConfigDescription("Limits simulation catch-up after a long stall.", new AcceptableValueRange(0.05f, 0.3333333f), new object[0])); @@ -130,6 +142,9 @@ private void BindConfig() _hitchThresholdMs = Config.Bind("Diagnostics", "Hitch threshold ms", 50f, new ConfigDescription("Frames slower than this are counted as hitches.", new AcceptableValueRange(25f, 250f), new object[0])); + _logBurstThreshold = Config.Bind("Diagnostics", "Log burst events per frame", 8, + new ConfigDescription("A hitching frame with at least this many log events is classified as a log burst.", + new AcceptableValueRange(2, 100), new object[0])); _logReports = Config.Bind("Diagnostics", "Log frame reports", true, "Write one compact SFS_FRAME report per interval."); _overlayKey = Config.Bind("Diagnostics", "Overlay key", @@ -152,11 +167,19 @@ private void ApplySettings() Application.targetFrameRate = _targetFrameRate.Value; if (_vSyncCount.Value >= 0) QualitySettings.vSyncCount = _vSyncCount.Value; + else if (_autoVSyncDivisor.Value && _targetFrameRate.Value > 0 && + QualitySettings.vSyncCount > 0) + { + int refreshRate = Math.Max(1, Screen.currentResolution.refreshRate); + int divisor = Mathf.Clamp(Mathf.RoundToInt( + refreshRate / (float)_targetFrameRate.Value), 1, 4); + QualitySettings.vSyncCount = divisor; + } Time.maximumDeltaTime = _maximumDeltaTime.Value; Time.maximumParticleDeltaTime = _maximumParticleDeltaTime.Value; if (_lowerBackgroundLoadingPriority.Value) - Application.backgroundLoadingPriority = ThreadPriority.Low; + Application.backgroundLoadingPriority = UnityEngine.ThreadPriority.Low; TrySetQualityProperty("asyncUploadTimeSlice", _asyncUploadTimeSlice.Value); TrySetQualityProperty("asyncUploadBufferSize", _asyncUploadBufferSize.Value); @@ -182,6 +205,7 @@ private void ApplySettings() _settingsApplied = true; Logger.LogInfo("Frame settings applied: target=" + Application.targetFrameRate + ", vSync=" + QualitySettings.vSyncCount + + ", refresh=" + Screen.currentResolution.refreshRate + ", maxDelta=" + Time.maximumDeltaTime.ToString("F3") + ", loading=" + Application.backgroundLoadingPriority + "."); } @@ -242,6 +266,12 @@ private void Update() _frameCount++; float frameMs = delta * 1000f; + int frameLogEvents; + int frameWarningEvents; + string frameLogSource; + _frameLogListener.Consume(out frameLogEvents, out frameWarningEvents, + out frameLogSource); + _reportLogEventCount += frameLogEvents; int gc0 = GC.CollectionCount(0); int gc1 = GC.CollectionCount(1); int gc2 = GC.CollectionCount(2); @@ -255,8 +285,16 @@ private void Update() _hitchCount++; if (collected) _gcHitchCount++; - if (Time.realtimeSinceStartup <= _activityWindowUntil) + else if (Time.realtimeSinceStartup <= _activityWindowUntil) _activityHitchCount++; + else if (frameLogEvents >= _logBurstThreshold.Value || + frameWarningEvents >= 2) + { + _logHitchCount++; + _lastHitchLogSource = frameLogSource; + } + else + _unknownHitchCount++; if (frameMs > _worstFrameMs) _worstFrameMs = frameMs; } @@ -293,15 +331,18 @@ private void PublishReport() float p95Ms = _sortBuffer[p95Index] * 1000f; _overlayText = string.Format( - "SFS FPS {0:F1} | 1% low {1:F1} | P95 {2:F1} ms | hitches {3} | worst {4:F1} ms", - averageFps, onePercentLow, p95Ms, _hitchCount, _worstFrameMs); + "SFS FPS {0:F1} | 1% {1:F1} | P95 {2:F1} ms | hitch {3} | GC {4} | load {5} | log {6}", + averageFps, onePercentLow, p95Ms, _hitchCount, _gcHitchCount, + _activityHitchCount, _logHitchCount); if (_logReports.Value) { Logger.LogInfo(string.Format( - "SFS_FRAME fps={0:F1} low1={1:F1} p95_ms={2:F1} hitches={3} worst_ms={4:F1} gc_hitches={5} activity_hitches={6} mono_mb={7:F1}", + "SFS_FRAME fps={0:F1} low1={1:F1} p95_ms={2:F1} hitches={3} worst_ms={4:F1} gc_hitches={5} activity_hitches={6} log_hitches={7} unknown_hitches={8} log_events={9} last_log_source={10} mono_mb={11:F1}", averageFps, onePercentLow, p95Ms, _hitchCount, _worstFrameMs, - _gcHitchCount, _activityHitchCount, GC.GetTotalMemory(false) / 1048576d)); + _gcHitchCount, _activityHitchCount, _logHitchCount, + _unknownHitchCount, _reportLogEventCount, _lastHitchLogSource, + GC.GetTotalMemory(false) / 1048576d)); } _reportElapsed = 0f; @@ -310,6 +351,10 @@ private void PublishReport() _hitchCount = 0; _gcHitchCount = 0; _activityHitchCount = 0; + _logHitchCount = 0; + _unknownHitchCount = 0; + _reportLogEventCount = 0; + _lastHitchLogSource = "none"; _worstFrameMs = 0f; } @@ -362,6 +407,8 @@ private void TrySetQualityProperty(string propertyName, int value) private void OnDestroy() { SceneManager.sceneLoaded -= OnSceneLoaded; + BepInEx.Logging.Logger.Listeners.Remove(_frameLogListener); + _frameLogListener.Dispose(); if (_harmony != null) _harmony.UnpatchSelf(); RestoreSettings(); @@ -397,5 +444,32 @@ private static void Prefix() MarkActivity(12f); } } + + private sealed class FrameLogListener : ILogListener + { + private int _eventCount; + private int _warningCount; + private string _lastSource = "none"; + + public void LogEvent(object sender, LogEventArgs eventArgs) + { + Interlocked.Increment(ref _eventCount); + if ((eventArgs.Level & (LogLevel.Fatal | LogLevel.Error | LogLevel.Warning)) != 0) + Interlocked.Increment(ref _warningCount); + if (!ReferenceEquals(eventArgs.Source, null)) + _lastSource = eventArgs.Source.SourceName; + } + + internal void Consume(out int events, out int warnings, out string source) + { + events = Interlocked.Exchange(ref _eventCount, 0); + warnings = Interlocked.Exchange(ref _warningCount, 0); + source = _lastSource; + } + + public void Dispose() + { + } + } } } diff --git a/tools/Build-StudioFrameStabilizer.ps1 b/tools/Build-StudioFrameStabilizer.ps1 index d175b5b..b8e1fd9 100644 --- a/tools/Build-StudioFrameStabilizer.ps1 +++ b/tools/Build-StudioFrameStabilizer.ps1 @@ -1,6 +1,6 @@ param( [string]$GameRoot = 'Z:\Koikatu', - [string]$Version = '0.1.3', + [string]$Version = '0.2.0', [switch]$Force ) From 7784ca762baff6ba6550af047ed0065f6eae54f3 Mon Sep 17 00:00:00 2001 From: DevoLutioner <127433058+DevoLutioner@users.noreply.github.com> Date: Tue, 11 Aug 2026 19:38:50 +0800 Subject: [PATCH 5/6] feat: attribute hitches with Unity profiler markers --- CHANGELOG.md | 4 + README.md | 3 +- .../StudioFrameStabilizer.csproj | 6 +- .../StudioFrameStabilizerPlugin.cs | 107 +++++++++++++++++- tools/Build-StudioFrameStabilizer.ps1 | 2 +- 5 files changed, 114 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d8dced8..6bbc5ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.3.0 - 2026-08-11 + +- Capture built-in Unity profiler markers on hitching frames for main-thread stage attribution. + ## 0.2.0 - 2026-08-11 - Automatically choose a VSync divisor near the configured frame-rate target. diff --git a/README.md b/README.md index 721125a..e03b9b6 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ 面向 Koikatu CharaStudio 的低风险帧稳定与诊断插件。只在 `CharaStudio.exe` 加载。 -## 0.2.0 +## 0.3.0 - 使用 1 ms Windows 计时精度改善限帧节奏。 - 将 Studio 进程设为 AboveNormal,减少普通后台程序抢占。 @@ -12,6 +12,7 @@ - 高刷新率且 VSync 开启时自动选择分频,避免 60 FPS 目标被 Unity 忽略后持续跑到约 150 FPS。 - 每 10 秒输出 `SFS_FRAME`:平均 FPS、1% low、P95 帧时间、卡顿次数、最差帧、GC 卡顿及换人/换动作窗口卡顿。 - 将卡顿进一步分为 GC、场景/动作活动、日志突发和未知主线程停顿。 +- 使用 Unity 内置低开销 Recorder 统计脚本 Update、LateUpdate、物理、动画、蒙皮、渲染和等待阶段。 - `F11` 显示或隐藏帧统计浮层。 配置文件首次启动后生成: diff --git a/src/StudioFrameStabilizer/StudioFrameStabilizer.csproj b/src/StudioFrameStabilizer/StudioFrameStabilizer.csproj index f41815b..dd19e8f 100644 --- a/src/StudioFrameStabilizer/StudioFrameStabilizer.csproj +++ b/src/StudioFrameStabilizer/StudioFrameStabilizer.csproj @@ -5,9 +5,9 @@ disable StudioFrameStabilizer StudioFrameStabilizer - 0.2.0 - 0.2.0.0 - 0.2.0.0 + 0.3.0 + 0.3.0.0 + 0.3.0.0 Z:\Koikatu diff --git a/src/StudioFrameStabilizer/StudioFrameStabilizerPlugin.cs b/src/StudioFrameStabilizer/StudioFrameStabilizerPlugin.cs index 1a107e2..872fdaa 100644 --- a/src/StudioFrameStabilizer/StudioFrameStabilizerPlugin.cs +++ b/src/StudioFrameStabilizer/StudioFrameStabilizerPlugin.cs @@ -1,4 +1,6 @@ using System; +using System.Collections; +using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.InteropServices; @@ -9,6 +11,7 @@ using HarmonyLib; using Studio; using UnityEngine; +using UnityEngine.Profiling; using UnityEngine.SceneManagement; namespace StudioFrameStabilizer @@ -20,7 +23,7 @@ public sealed class StudioFrameStabilizerPlugin : BaseUnityPlugin { public const string PluginGuid = "codex.koikatu.studioframestabilizer"; public const string PluginName = "Studio Frame Stabilizer"; - public const string PluginVersion = "0.2.0"; + public const string PluginVersion = "0.3.0"; private const int FrameCapacity = 2048; private const int TimerResolutionMs = 1; @@ -75,6 +78,11 @@ public sealed class StudioFrameStabilizerPlugin : BaseUnityPlugin private string _overlayText = "Studio Frame Stabilizer: collecting..."; private GUIStyle _overlayStyle; private readonly FrameLogListener _frameLogListener = new FrameLogListener(); + private readonly List _profilerMarkers = + new List(); + private Coroutine _profilerCoroutine; + private readonly WaitForEndOfFrame _endOfFrame = new WaitForEndOfFrame(); + private bool _currentFrameHitch; private static StudioFrameStabilizerPlugin _instance; @@ -96,12 +104,14 @@ private void Awake() BepInEx.Logging.Logger.Listeners.Add(_frameLogListener); _harmony = new Harmony(PluginGuid); _harmony.PatchAll(Assembly.GetExecutingAssembly()); + InitializeProfilerRecorders(); + _profilerCoroutine = StartCoroutine(CaptureProfilerFrames()); if (_enabled.Value) ApplySettings(); _enabled.SettingChanged += OnEnabledChanged; - Logger.LogInfo("Studio Frame Stabilizer 0.2.0 loaded. F11 toggles the frame overlay."); + Logger.LogInfo("Studio Frame Stabilizer 0.3.0 loaded. F11 toggles the frame overlay."); } private void BindConfig() @@ -266,6 +276,7 @@ private void Update() _frameCount++; float frameMs = delta * 1000f; + _currentFrameHitch = frameMs >= _hitchThresholdMs.Value; int frameLogEvents; int frameWarningEvents; string frameLogSource; @@ -329,6 +340,26 @@ private void PublishReport() ? worstCount / worstOnePercentSum : 0d; float p95Ms = _sortBuffer[p95Index] * 1000f; + ProfilerMarkerStat topProfilerMarker = null; + double playerLoopHitchMs = 0d; + for (int i = 0; i < _profilerMarkers.Count; i++) + { + ProfilerMarkerStat marker = _profilerMarkers[i]; + if (string.Equals(marker.Name, "PlayerLoop", StringComparison.Ordinal)) + { + playerLoopHitchMs = marker.HitchMaxMs; + continue; + } + if (ReferenceEquals(topProfilerMarker, null) || + marker.HitchMaxMs > topProfilerMarker.HitchMaxMs) + topProfilerMarker = marker; + } + string topProfilerName = ReferenceEquals(topProfilerMarker, null) + ? "none" + : topProfilerMarker.Name; + double topProfilerMs = ReferenceEquals(topProfilerMarker, null) + ? 0d + : topProfilerMarker.HitchMaxMs; _overlayText = string.Format( "SFS FPS {0:F1} | 1% {1:F1} | P95 {2:F1} ms | hitch {3} | GC {4} | load {5} | log {6}", @@ -338,10 +369,11 @@ private void PublishReport() if (_logReports.Value) { Logger.LogInfo(string.Format( - "SFS_FRAME fps={0:F1} low1={1:F1} p95_ms={2:F1} hitches={3} worst_ms={4:F1} gc_hitches={5} activity_hitches={6} log_hitches={7} unknown_hitches={8} log_events={9} last_log_source={10} mono_mb={11:F1}", + "SFS_FRAME fps={0:F1} low1={1:F1} p95_ms={2:F1} hitches={3} worst_ms={4:F1} gc_hitches={5} activity_hitches={6} log_hitches={7} unknown_hitches={8} log_events={9} last_log_source={10} profiler_top={11} profiler_top_ms={12:F1} playerloop_ms={13:F1} mono_mb={14:F1}", averageFps, onePercentLow, p95Ms, _hitchCount, _worstFrameMs, _gcHitchCount, _activityHitchCount, _logHitchCount, _unknownHitchCount, _reportLogEventCount, _lastHitchLogSource, + topProfilerName, topProfilerMs, playerLoopHitchMs, GC.GetTotalMemory(false) / 1048576d)); } @@ -356,6 +388,41 @@ private void PublishReport() _reportLogEventCount = 0; _lastHitchLogSource = "none"; _worstFrameMs = 0f; + for (int i = 0; i < _profilerMarkers.Count; i++) + _profilerMarkers[i].ResetWindow(); + } + + private void InitializeProfilerRecorders() + { + string[] candidates = + { + "BehaviourUpdate", "LateBehaviourUpdate", "FixedBehaviourUpdate", + "Physics.Processing", "Physics.Simulate", "Animation.Update", + "Animator.Update", "MeshSkinning.Update", + "SkinnedMeshRenderer.UpdateSkinning", "Camera.Render", + "Render.OpaqueGeometry", "Render.TransparentGeometry", "GC.Collect", + "WaitForTargetFPS", "Gfx.WaitForPresent", "PlayerLoop" + }; + for (int i = 0; i < candidates.Length; i++) + { + Recorder recorder = Recorder.Get(candidates[i]); + if (!recorder.isValid) + continue; + recorder.enabled = true; + _profilerMarkers.Add(new ProfilerMarkerStat(candidates[i], recorder)); + } + Logger.LogInfo("Enabled " + _profilerMarkers.Count + + " built-in Unity profiler recorders for hitch attribution."); + } + + private IEnumerator CaptureProfilerFrames() + { + while (true) + { + yield return _endOfFrame; + for (int i = 0; i < _profilerMarkers.Count; i++) + _profilerMarkers[i].Capture(_currentFrameHitch); + } } private void OnGUI() @@ -411,6 +478,10 @@ private void OnDestroy() _frameLogListener.Dispose(); if (_harmony != null) _harmony.UnpatchSelf(); + if (!ReferenceEquals(_profilerCoroutine, null)) + StopCoroutine(_profilerCoroutine); + for (int i = 0; i < _profilerMarkers.Count; i++) + _profilerMarkers[i].Recorder.enabled = false; RestoreSettings(); _instance = null; } @@ -471,5 +542,35 @@ public void Dispose() { } } + + private sealed class ProfilerMarkerStat + { + internal readonly string Name; + internal readonly Recorder Recorder; + internal double HitchMaxMs; + internal double HitchSumMs; + + internal ProfilerMarkerStat(string name, Recorder recorder) + { + Name = name; + Recorder = recorder; + } + + internal void Capture(bool hitch) + { + if (!hitch) + return; + double elapsedMs = Recorder.elapsedNanoseconds / 1000000d; + HitchSumMs += elapsedMs; + if (elapsedMs > HitchMaxMs) + HitchMaxMs = elapsedMs; + } + + internal void ResetWindow() + { + HitchMaxMs = 0d; + HitchSumMs = 0d; + } + } } } diff --git a/tools/Build-StudioFrameStabilizer.ps1 b/tools/Build-StudioFrameStabilizer.ps1 index b8e1fd9..8bfc441 100644 --- a/tools/Build-StudioFrameStabilizer.ps1 +++ b/tools/Build-StudioFrameStabilizer.ps1 @@ -1,6 +1,6 @@ param( [string]$GameRoot = 'Z:\Koikatu', - [string]$Version = '0.2.0', + [string]$Version = '0.3.0', [switch]$Force ) From 0ae7c08dcde5e0c285f82b430de2ab79e7510a3a Mon Sep 17 00:00:00 2001 From: DevoLutioner <127433058+DevoLutioner@users.noreply.github.com> Date: Tue, 11 Aug 2026 19:42:47 +0800 Subject: [PATCH 6/6] fix: guard frame pacing from plugin overrides --- CHANGELOG.md | 5 +++ README.md | 3 +- .../StudioFrameStabilizer.csproj | 6 +-- .../StudioFrameStabilizerPlugin.cs | 37 +++++++++++++++++-- tools/Build-StudioFrameStabilizer.ps1 | 2 +- 5 files changed, 45 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bbc5ab..44f6d76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 0.3.1 - 2026-08-11 + +- Guard the selected frame cap and VSync divisor against later plugin overrides. +- Include live pacing settings and drift count in every frame report. + ## 0.3.0 - 2026-08-11 - Capture built-in Unity profiler markers on hitching frames for main-thread stage attribution. diff --git a/README.md b/README.md index e03b9b6..1a2e5f6 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ 面向 Koikatu CharaStudio 的低风险帧稳定与诊断插件。只在 `CharaStudio.exe` 加载。 -## 0.3.0 +## 0.3.1 - 使用 1 ms Windows 计时精度改善限帧节奏。 - 将 Studio 进程设为 AboveNormal,减少普通后台程序抢占。 @@ -10,6 +10,7 @@ - 将长暂停后的模拟追帧限制到 100 ms、粒子追帧限制到约 33 ms。 - 默认将 Studio 目标帧率设为 60,不强制修改 VSync。 - 高刷新率且 VSync 开启时自动选择分频,避免 60 FPS 目标被 Unity 忽略后持续跑到约 150 FPS。 +- 每两秒校验一次目标帧率和 VSync,仅在后加载插件覆盖设置时恢复,报告中记录漂移次数。 - 每 10 秒输出 `SFS_FRAME`:平均 FPS、1% low、P95 帧时间、卡顿次数、最差帧、GC 卡顿及换人/换动作窗口卡顿。 - 将卡顿进一步分为 GC、场景/动作活动、日志突发和未知主线程停顿。 - 使用 Unity 内置低开销 Recorder 统计脚本 Update、LateUpdate、物理、动画、蒙皮、渲染和等待阶段。 diff --git a/src/StudioFrameStabilizer/StudioFrameStabilizer.csproj b/src/StudioFrameStabilizer/StudioFrameStabilizer.csproj index dd19e8f..3ae5abd 100644 --- a/src/StudioFrameStabilizer/StudioFrameStabilizer.csproj +++ b/src/StudioFrameStabilizer/StudioFrameStabilizer.csproj @@ -5,9 +5,9 @@ disable StudioFrameStabilizer StudioFrameStabilizer - 0.3.0 - 0.3.0.0 - 0.3.0.0 + 0.3.1 + 0.3.1.0 + 0.3.1.0 Z:\Koikatu diff --git a/src/StudioFrameStabilizer/StudioFrameStabilizerPlugin.cs b/src/StudioFrameStabilizer/StudioFrameStabilizerPlugin.cs index 872fdaa..094dbc2 100644 --- a/src/StudioFrameStabilizer/StudioFrameStabilizerPlugin.cs +++ b/src/StudioFrameStabilizer/StudioFrameStabilizerPlugin.cs @@ -23,7 +23,7 @@ public sealed class StudioFrameStabilizerPlugin : BaseUnityPlugin { public const string PluginGuid = "codex.koikatu.studioframestabilizer"; public const string PluginName = "Studio Frame Stabilizer"; - public const string PluginVersion = "0.3.0"; + public const string PluginVersion = "0.3.1"; private const int FrameCapacity = 2048; private const int TimerResolutionMs = 1; @@ -58,6 +58,10 @@ public sealed class StudioFrameStabilizerPlugin : BaseUnityPlugin private bool _timerResolutionActive; private bool _priorityChanged; private bool _settingsApplied; + private int _desiredTargetFrameRate; + private int _desiredVSyncCount; + private float _pacingGuardElapsed; + private int _pacingDriftCount; private int _frameWriteIndex; private int _frameCount; @@ -111,7 +115,7 @@ private void Awake() ApplySettings(); _enabled.SettingChanged += OnEnabledChanged; - Logger.LogInfo("Studio Frame Stabilizer 0.3.0 loaded. F11 toggles the frame overlay."); + Logger.LogInfo("Studio Frame Stabilizer 0.3.1 loaded. F11 toggles the frame overlay."); } private void BindConfig() @@ -185,6 +189,8 @@ private void ApplySettings() refreshRate / (float)_targetFrameRate.Value), 1, 4); QualitySettings.vSyncCount = divisor; } + _desiredTargetFrameRate = Application.targetFrameRate; + _desiredVSyncCount = QualitySettings.vSyncCount; Time.maximumDeltaTime = _maximumDeltaTime.Value; Time.maximumParticleDeltaTime = _maximumParticleDeltaTime.Value; @@ -270,6 +276,28 @@ private void Update() if (delta <= 0f || delta > 5f) return; + if (_settingsApplied) + { + _pacingGuardElapsed += delta; + if (_pacingGuardElapsed >= 2f) + { + _pacingGuardElapsed = 0f; + bool drifted = false; + if (Application.targetFrameRate != _desiredTargetFrameRate) + { + Application.targetFrameRate = _desiredTargetFrameRate; + drifted = true; + } + if (QualitySettings.vSyncCount != _desiredVSyncCount) + { + QualitySettings.vSyncCount = _desiredVSyncCount; + drifted = true; + } + if (drifted) + _pacingDriftCount++; + } + } + _frameTimes[_frameWriteIndex] = delta; _frameWriteIndex = (_frameWriteIndex + 1) % FrameCapacity; if (_frameCount < FrameCapacity) @@ -369,11 +397,13 @@ private void PublishReport() if (_logReports.Value) { Logger.LogInfo(string.Format( - "SFS_FRAME fps={0:F1} low1={1:F1} p95_ms={2:F1} hitches={3} worst_ms={4:F1} gc_hitches={5} activity_hitches={6} log_hitches={7} unknown_hitches={8} log_events={9} last_log_source={10} profiler_top={11} profiler_top_ms={12:F1} playerloop_ms={13:F1} mono_mb={14:F1}", + "SFS_FRAME fps={0:F1} low1={1:F1} p95_ms={2:F1} hitches={3} worst_ms={4:F1} gc_hitches={5} activity_hitches={6} log_hitches={7} unknown_hitches={8} log_events={9} last_log_source={10} profiler_top={11} profiler_top_ms={12:F1} playerloop_ms={13:F1} target={14} vsync={15} pacing_drifts={16} mono_mb={17:F1}", averageFps, onePercentLow, p95Ms, _hitchCount, _worstFrameMs, _gcHitchCount, _activityHitchCount, _logHitchCount, _unknownHitchCount, _reportLogEventCount, _lastHitchLogSource, topProfilerName, topProfilerMs, playerLoopHitchMs, + Application.targetFrameRate, QualitySettings.vSyncCount, + _pacingDriftCount, GC.GetTotalMemory(false) / 1048576d)); } @@ -387,6 +417,7 @@ private void PublishReport() _unknownHitchCount = 0; _reportLogEventCount = 0; _lastHitchLogSource = "none"; + _pacingDriftCount = 0; _worstFrameMs = 0f; for (int i = 0; i < _profilerMarkers.Count; i++) _profilerMarkers[i].ResetWindow(); diff --git a/tools/Build-StudioFrameStabilizer.ps1 b/tools/Build-StudioFrameStabilizer.ps1 index 8bfc441..0122108 100644 --- a/tools/Build-StudioFrameStabilizer.ps1 +++ b/tools/Build-StudioFrameStabilizer.ps1 @@ -1,6 +1,6 @@ param( [string]$GameRoot = 'Z:\Koikatu', - [string]$Version = '0.3.0', + [string]$Version = '0.3.1', [switch]$Force )