From 4ac340d53d93f1a8057ec6c7d7589eb0a60762f6 Mon Sep 17 00:00:00 2001 From: Natsuki Date: Thu, 15 Oct 2026 15:50:55 +0900 Subject: [PATCH] Leave UltraSpeedBoost off at startup and boost Ultrafast playback per run The flag is process-wide, so enabling it at startup also changed speed 4 for normal play and for mods that mirror vanilla's boost branch. play_for and play_until_letter now enable it for Ultrafast (or forceRequestedSpeed) runs and restore the previous value afterwards. --- README.md | 8 +-- Source/LifecycleCapabilityModule.cs | 88 +++++++++++++++++------------ Source/RimBridgeStartup.cs | 5 +- Source/RimBridgeTools.cs | 18 +++--- docs/tool-reference.md | 12 ++-- 5 files changed, 73 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index 6c93a1a..b45813c 100644 --- a/README.md +++ b/README.md @@ -298,7 +298,7 @@ For the generated parameter-level reference pulled straight from the annotated s Lua authoring note: `rimbridge/run_lua` is intentionally a lowered Lua subset, not general-purpose Lua. Start with `rimbridge/get_lua_reference` and `rimbridge/compile_lua`; prefer `local` bindings, `rb.call`/`rb.poll`, static field access, and static one-based indexes such as `names[1]`. Dynamic indexing such as `names[i]`, arbitrary global assignment, and most broader Lua features are rejected in v1. -Playback default: RimBridgeServer enables RimWorld's private `TickManager.UltraSpeedBoost` process-wide at startup. Speed 4 (`Ultrafast`) therefore uses the boosted debug rate by default until `rimworld/set_time_speed` changes the flag; the next RimWorld startup enables it again. +Playback default: RimWorld's private `TickManager.UltraSpeedBoost` is left off, so having the mod installed does not change speed 4 (`Ultrafast`) for normal play. `rimworld/play_for` and `rimworld/play_until_letter` turn the boost on for the duration of an `Ultrafast` run (or a `forceRequestedSpeed` run) and restore the previous value afterwards; `rimworld/set_time_speed` with `ultraSpeedBoost` sets it explicitly for the rest of the session. @@ -339,9 +339,9 @@ Playback default: RimBridgeServer enables RimWorld's private `TickManager.UltraS ### Debug Actions And Mods - `rimworld/pause_game` - Pause or unpause the game -- `rimworld/set_time_speed` - Set RimWorld's current time speed directly; RimBridgeServer enables the boost for speed 4 (Ultrafast) at startup, and this tool can change that state -- `rimworld/play_for` - Unpause the current game at a requested time speed for a bounded real-time duration, then pause it again; speed 4 (Ultrafast) is boosted by default, and forced-normal-speed slowdown can optionally be suppressed during the run -- `rimworld/play_until_letter` - Unpause the current game at a requested time speed until a new right-side letter appears, then pause and return the new letter payload; speed 4 (Ultrafast) is boosted by default, and forced-normal-speed slowdown can optionally be suppressed during the run +- `rimworld/set_time_speed` - Set RimWorld's current time speed directly; the boost for speed 4 (Ultrafast) is off by default, and this tool can turn it on or off +- `rimworld/play_for` - Unpause the current game at a requested time speed for a bounded real-time duration, then pause it again; speed 4 (Ultrafast) is boosted for the duration of the run, and forced-normal-speed slowdown can optionally be suppressed during the run +- `rimworld/play_until_letter` - Unpause the current game at a requested time speed until a new right-side letter appears, then pause and return the new letter payload; speed 4 (Ultrafast) is boosted for the duration of the run, and forced-normal-speed slowdown can optionally be suppressed during the run - `rimworld/step_game_ticks` - Advance the paused game by an exact number of ticks, one tick per Unity update frame, mirroring RimWorld's Dev_TickOnce path while preserving render-frame boundaries - `rimworld/list_debug_action_roots` - List top-level RimWorld debug action roots using stable internal debug-action paths - `rimworld/list_debug_action_children` - List direct children of a RimWorld debug action path diff --git a/Source/LifecycleCapabilityModule.cs b/Source/LifecycleCapabilityModule.cs index 1ad0961..ff46986 100644 --- a/Source/LifecycleCapabilityModule.cs +++ b/Source/LifecycleCapabilityModule.cs @@ -182,8 +182,9 @@ public object PlayFor(int durationMs, string speed = "Normal", int pollIntervalM { previousNeverForceNormalSpeed = DebugViewSettings.neverForceNormalSpeed; DebugViewSettings.neverForceNormalSpeed = true; - previousUltraSpeedBoost = TrySetUltraSpeedBoost(true); } + if (ShouldBoostPlayback(parsedSpeed, forceRequestedSpeed) && previousUltraSpeedBoost == null) + previousUltraSpeedBoost = TrySetUltraSpeedBoost(true); stopwatch = Stopwatch.StartNew(); EnsurePlaybackRunning(parsedSpeed); }, timeoutMs: 5000), @@ -240,23 +241,11 @@ public object PlayFor(int durationMs, string speed = "Normal", int pollIntervalM } catch (Exception ex) { - if (forceRequestedSpeed && previousNeverForceNormalSpeed != null) - { - try - { - restoredNeverForceNormalSpeed = RimBridgeMainThread.Invoke( - () => RestoreNeverForceNormalSpeed(previousNeverForceNormalSpeed), - timeoutMs: 5000); - restoredUltraSpeedBoost = RimBridgeMainThread.Invoke( - () => RestoreUltraSpeedBoost(previousUltraSpeedBoost), - timeoutMs: 5000); - } - catch - { - restoredNeverForceNormalSpeed = null; - restoredUltraSpeedBoost = null; - } - } + RestorePlaybackOverridesAfterFailure( + previousNeverForceNormalSpeed, + previousUltraSpeedBoost, + ref restoredNeverForceNormalSpeed, + ref restoredUltraSpeedBoost); return new { @@ -382,8 +371,9 @@ public object PlayUntilLetter(int timeoutMs = 1800000, string speed = "Normal", { previousNeverForceNormalSpeed = DebugViewSettings.neverForceNormalSpeed; DebugViewSettings.neverForceNormalSpeed = true; - previousUltraSpeedBoost = TrySetUltraSpeedBoost(true); } + if (ShouldBoostPlayback(parsedSpeed, forceRequestedSpeed) && previousUltraSpeedBoost == null) + previousUltraSpeedBoost = TrySetUltraSpeedBoost(true); EnsurePlaybackRunning(parsedSpeed); }, timeoutMs: 5000); @@ -435,6 +425,9 @@ public object PlayUntilLetter(int timeoutMs = 1800000, string speed = "Normal", restoredNeverForceNormalSpeed = RimBridgeMainThread.Invoke( () => RestoreNeverForceNormalSpeed(previousNeverForceNormalSpeed), timeoutMs: 5000); + } + if (previousUltraSpeedBoost != null) + { restoredUltraSpeedBoost = RimBridgeMainThread.Invoke( () => RestoreUltraSpeedBoost(previousUltraSpeedBoost), timeoutMs: 5000); @@ -479,23 +472,11 @@ public object PlayUntilLetter(int timeoutMs = 1800000, string speed = "Normal", } catch (Exception ex) { - if (forceRequestedSpeed && previousNeverForceNormalSpeed != null) - { - try - { - restoredNeverForceNormalSpeed = RimBridgeMainThread.Invoke( - () => RestoreNeverForceNormalSpeed(previousNeverForceNormalSpeed), - timeoutMs: 5000); - restoredUltraSpeedBoost = RimBridgeMainThread.Invoke( - () => RestoreUltraSpeedBoost(previousUltraSpeedBoost), - timeoutMs: 5000); - } - catch - { - restoredNeverForceNormalSpeed = null; - restoredUltraSpeedBoost = null; - } - } + RestorePlaybackOverridesAfterFailure( + previousNeverForceNormalSpeed, + previousUltraSpeedBoost, + ref restoredNeverForceNormalSpeed, + ref restoredUltraSpeedBoost); try { @@ -1384,4 +1365,39 @@ private static void PauseIfNeeded() UltraSpeedBoostField?.SetValue(null, previousUltraSpeedBoost.Value); return TryReadUltraSpeedBoost(); } + + // The boost is process-wide and also affects normal play, so playback only holds it for the + // duration of a run that asks for Ultrafast (or forces the requested speed) and then restores it. + private static bool ShouldBoostPlayback(TimeSpeed speed, bool forceRequestedSpeed) + { + return forceRequestedSpeed || speed == TimeSpeed.Ultrafast; + } + + private static void RestorePlaybackOverridesAfterFailure( + bool? previousNeverForceNormalSpeed, + bool? previousUltraSpeedBoost, + ref bool? restoredNeverForceNormalSpeed, + ref bool? restoredUltraSpeedBoost) + { + try + { + if (previousNeverForceNormalSpeed != null) + { + restoredNeverForceNormalSpeed = RimBridgeMainThread.Invoke( + () => RestoreNeverForceNormalSpeed(previousNeverForceNormalSpeed), + timeoutMs: 5000); + } + if (previousUltraSpeedBoost != null) + { + restoredUltraSpeedBoost = RimBridgeMainThread.Invoke( + () => RestoreUltraSpeedBoost(previousUltraSpeedBoost), + timeoutMs: 5000); + } + } + catch + { + restoredNeverForceNormalSpeed = null; + restoredUltraSpeedBoost = null; + } + } } diff --git a/Source/RimBridgeStartup.cs b/Source/RimBridgeStartup.cs index 1508b88..a99b2ea 100644 --- a/Source/RimBridgeStartup.cs +++ b/Source/RimBridgeStartup.cs @@ -100,9 +100,8 @@ private static void TryStart() typeof(DebugViewSettings) .GetField("showTpsCounter", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) ?.SetValue(null, true); - typeof(TickManager) - .GetField("UltraSpeedBoost", BindingFlags.Static | BindingFlags.NonPublic) - ?.SetValue(null, true); + // TickManager.UltraSpeedBoost is left alone here: it changes speed 4 for normal play too + // (and for mods that mirror vanilla's boost branch). Playback tools enable it per run. } RimBridgeTools tools; diff --git a/Source/RimBridgeTools.cs b/Source/RimBridgeTools.cs index d642365..399d33e 100644 --- a/Source/RimBridgeTools.cs +++ b/Source/RimBridgeTools.cs @@ -249,33 +249,33 @@ public object PauseGame([ToolParameter(Description = "True to pause, false to un return InvokeAlias(Arguments((nameof(pause), pause))); } - [ReadmeTool("Debug Actions And Mods", "Set RimWorld's current time speed directly; RimBridgeServer enables the boost for speed 4 (Ultrafast) at startup, and this tool can change that state")] - [Tool("rimworld/set_time_speed", Description = "Set RimWorld's current time speed directly; RimBridgeServer enables the boost for speed 4 (Ultrafast) at startup, and this tool can change that state")] + [ReadmeTool("Debug Actions And Mods", "Set RimWorld's current time speed directly; the boost for speed 4 (Ultrafast) is off by default, and this tool can turn it on or off")] + [Tool("rimworld/set_time_speed", Description = "Set RimWorld's current time speed directly; the boost for speed 4 (Ultrafast) is off by default, and this tool can turn it on or off")] public object SetTimeSpeed( [ToolParameter(Description = "Desired time speed: Paused, Normal, Fast, Superfast, or Ultrafast")] string speed = "Normal", - [ToolParameter(Description = "When set, change RimWorld's private TickManager.UltraSpeedBoost flag. RimBridgeServer enables it at startup; leave null to preserve the current value.")] bool? ultraSpeedBoost = null) + [ToolParameter(Description = "When set, change RimWorld's private TickManager.UltraSpeedBoost flag. It is off by default and process-wide, so it also affects normal play until turned off; leave null to preserve the current value.")] bool? ultraSpeedBoost = null) { return InvokeAlias(Arguments((nameof(speed), speed), (nameof(ultraSpeedBoost), ultraSpeedBoost))); } - [ReadmeTool("Debug Actions And Mods", "Unpause the current game at a requested time speed for a bounded real-time duration, then pause it again; speed 4 (Ultrafast) is boosted by default, and forced-normal-speed slowdown can optionally be suppressed during the run")] - [Tool("rimworld/play_for", Description = "Unpause the current game at a requested time speed for a bounded real-time duration, then pause it again; speed 4 (Ultrafast) is boosted by default, and forced-normal-speed slowdown can optionally be suppressed during the run")] + [ReadmeTool("Debug Actions And Mods", "Unpause the current game at a requested time speed for a bounded real-time duration, then pause it again; speed 4 (Ultrafast) is boosted for the duration of the run, and forced-normal-speed slowdown can optionally be suppressed during the run")] + [Tool("rimworld/play_for", Description = "Unpause the current game at a requested time speed for a bounded real-time duration, then pause it again; speed 4 (Ultrafast) is boosted for the duration of the run, and forced-normal-speed slowdown can optionally be suppressed during the run")] public object PlayFor( [ToolParameter(Description = "Real-time duration in milliseconds to keep the game unpaused before pausing it again")] int durationMs, [ToolParameter(Description = "Desired play speed while the game is running: Normal, Fast, Superfast, or Ultrafast")] string speed = "Normal", [ToolParameter(Description = "How often to poll playback state while waiting to repause")] int pollIntervalMs = 25, - [ToolParameter(Description = "When true, temporarily suppress RimWorld's forced-normal-speed slowdown and ensure TickManager.UltraSpeedBoost is enabled while preserving the normal TickManager update path, then restore both prior values. The boost is enabled at bridge startup by default.")] bool forceRequestedSpeed = false) + [ToolParameter(Description = "When true, temporarily suppress RimWorld's forced-normal-speed slowdown and ensure TickManager.UltraSpeedBoost is enabled while preserving the normal TickManager update path, then restore both prior values. Ultrafast runs enable the boost for the run even without this flag.")] bool forceRequestedSpeed = false) { return InvokeAlias(Arguments((nameof(durationMs), durationMs), (nameof(speed), speed), (nameof(pollIntervalMs), pollIntervalMs), (nameof(forceRequestedSpeed), forceRequestedSpeed))); } - [ReadmeTool("Debug Actions And Mods", "Unpause the current game at a requested time speed until a new right-side letter appears, then pause and return the new letter payload; speed 4 (Ultrafast) is boosted by default, and forced-normal-speed slowdown can optionally be suppressed during the run")] - [Tool("rimworld/play_until_letter", Description = "Unpause the current game at a requested time speed until a new right-side letter appears, then pause and return the new letter payload; speed 4 (Ultrafast) is boosted by default, and forced-normal-speed slowdown can optionally be suppressed during the run")] + [ReadmeTool("Debug Actions And Mods", "Unpause the current game at a requested time speed until a new right-side letter appears, then pause and return the new letter payload; speed 4 (Ultrafast) is boosted for the duration of the run, and forced-normal-speed slowdown can optionally be suppressed during the run")] + [Tool("rimworld/play_until_letter", Description = "Unpause the current game at a requested time speed until a new right-side letter appears, then pause and return the new letter payload; speed 4 (Ultrafast) is boosted for the duration of the run, and forced-normal-speed slowdown can optionally be suppressed during the run")] public object PlayUntilLetter( [ToolParameter(Description = "Maximum real-time wait in milliseconds before pausing and timing out")] int timeoutMs = 1800000, [ToolParameter(Description = "Desired play speed while waiting: Normal, Fast, Superfast, or Ultrafast")] string speed = "Normal", [ToolParameter(Description = "How often to poll the letter stack while waiting")] int pollIntervalMs = 250, - [ToolParameter(Description = "When true, temporarily suppress RimWorld's forced-normal-speed slowdown and ensure TickManager.UltraSpeedBoost is enabled while preserving the normal TickManager update path, then restore both prior values. The boost is enabled at bridge startup by default.")] bool forceRequestedSpeed = false, + [ToolParameter(Description = "When true, temporarily suppress RimWorld's forced-normal-speed slowdown and ensure TickManager.UltraSpeedBoost is enabled while preserving the normal TickManager update path, then restore both prior values. Ultrafast runs enable the boost for the run even without this flag.")] bool forceRequestedSpeed = false, [ToolParameter(Description = "When false, letters already present when the wait starts are ignored and only newly added letters complete the wait")] bool includeExistingLetters = false) { return InvokeAlias(Arguments((nameof(timeoutMs), timeoutMs), (nameof(speed), speed), (nameof(pollIntervalMs), pollIntervalMs), (nameof(forceRequestedSpeed), forceRequestedSpeed), (nameof(includeExistingLetters), includeExistingLetters))); diff --git a/docs/tool-reference.md b/docs/tool-reference.md index 6e3bd56..5784bd5 100644 --- a/docs/tool-reference.md +++ b/docs/tool-reference.md @@ -228,31 +228,31 @@ Parameters: ### `rimworld/set_time_speed` -Set RimWorld's current time speed directly; RimBridgeServer enables the boost for speed 4 (Ultrafast) at startup, and this tool can change that state +Set RimWorld's current time speed directly; the boost for speed 4 (Ultrafast) is off by default, and this tool can turn it on or off Parameters: - `speed` (`string`, `optional`, default `"Normal"`): Desired time speed: Paused, Normal, Fast, Superfast, or Ultrafast -- `ultraSpeedBoost` (`bool?`, `optional`, default `null`): When set, change RimWorld's private TickManager.UltraSpeedBoost flag. RimBridgeServer enables it at startup; leave null to preserve the current value. +- `ultraSpeedBoost` (`bool?`, `optional`, default `null`): When set, change RimWorld's private TickManager.UltraSpeedBoost flag. It is off by default and process-wide, so it also affects normal play until turned off; leave null to preserve the current value. ### `rimworld/play_for` -Unpause the current game at a requested time speed for a bounded real-time duration, then pause it again; speed 4 (Ultrafast) is boosted by default, and forced-normal-speed slowdown can optionally be suppressed during the run +Unpause the current game at a requested time speed for a bounded real-time duration, then pause it again; speed 4 (Ultrafast) is boosted for the duration of the run, and forced-normal-speed slowdown can optionally be suppressed during the run Parameters: - `durationMs` (`int`, `required`): Real-time duration in milliseconds to keep the game unpaused before pausing it again - `speed` (`string`, `optional`, default `"Normal"`): Desired play speed while the game is running: Normal, Fast, Superfast, or Ultrafast - `pollIntervalMs` (`int`, `optional`, default `25`): How often to poll playback state while waiting to repause -- `forceRequestedSpeed` (`bool`, `optional`, default `false`): When true, temporarily suppress RimWorld's forced-normal-speed slowdown and ensure TickManager.UltraSpeedBoost is enabled while preserving the normal TickManager update path, then restore both prior values. The boost is enabled at bridge startup by default. +- `forceRequestedSpeed` (`bool`, `optional`, default `false`): When true, temporarily suppress RimWorld's forced-normal-speed slowdown and ensure TickManager.UltraSpeedBoost is enabled while preserving the normal TickManager update path, then restore both prior values. Ultrafast runs enable the boost for the run even without this flag. ### `rimworld/play_until_letter` -Unpause the current game at a requested time speed until a new right-side letter appears, then pause and return the new letter payload; speed 4 (Ultrafast) is boosted by default, and forced-normal-speed slowdown can optionally be suppressed during the run +Unpause the current game at a requested time speed until a new right-side letter appears, then pause and return the new letter payload; speed 4 (Ultrafast) is boosted for the duration of the run, and forced-normal-speed slowdown can optionally be suppressed during the run Parameters: - `timeoutMs` (`int`, `optional`, default `1800000`): Maximum real-time wait in milliseconds before pausing and timing out - `speed` (`string`, `optional`, default `"Normal"`): Desired play speed while waiting: Normal, Fast, Superfast, or Ultrafast - `pollIntervalMs` (`int`, `optional`, default `250`): How often to poll the letter stack while waiting -- `forceRequestedSpeed` (`bool`, `optional`, default `false`): When true, temporarily suppress RimWorld's forced-normal-speed slowdown and ensure TickManager.UltraSpeedBoost is enabled while preserving the normal TickManager update path, then restore both prior values. The boost is enabled at bridge startup by default. +- `forceRequestedSpeed` (`bool`, `optional`, default `false`): When true, temporarily suppress RimWorld's forced-normal-speed slowdown and ensure TickManager.UltraSpeedBoost is enabled while preserving the normal TickManager update path, then restore both prior values. Ultrafast runs enable the boost for the run even without this flag. - `includeExistingLetters` (`bool`, `optional`, default `false`): When false, letters already present when the wait starts are ignored and only newly added letters complete the wait ### `rimworld/step_game_ticks`