fix(app): advance logic timers by wall-clock time

Logic timers advanced by io.DeltaTime, which update() clamps to 0.25 s
so animations don't jump after a pause. That matched real time only
while the main loop rendered continuously: with frames seconds apart, a
timer fed the clamped delta runs up to 8x slow. Affected: the refresh
scheduler, the seed-migration poll, the chat fast scan, the encryption
check, the loading and shutdown timers, the lock-screen countdowns, the
Settings debug gate and the mining benchmark.

- App::logicDeltaTime(): steady-clock seconds since the previous
  update(), capped at 6 s so a sleep/resume fires each timer once
  instead of replaying the gap. Visual interpolation keeps the clamped
  io.DeltaTime.
- The refresh timers tick before the RPC results are drained, so a
  hold that a callback arms (the connect-retry backoff) starts fresh
  instead of being charged the gap before it existed.
- The shutdown timer is measured from shutdown_start_time_:
  beginShutdown runs from SDL event handling, so an accumulated delta
  would count the idle gap before the close request.
- The lock-screen error and lockout countdowns subtract only the time
  since they were armed: a failed unlock arms them from an RPC result,
  which can land on the frame that ends an idle gap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 19:51:02 -05:00
co-authored by Claude Opus 5.5
parent ffce7cad24
commit 402a3053dc
5 changed files with 51 additions and 13 deletions
+16
View File
@@ -114,6 +114,17 @@ public:
*/
void update();
/**
* @brief Wall-clock seconds since the previous update(), capped at kMaxLogicDeltaSeconds.
*
* Logic timers (refresh scheduler, poll intervals, countdowns) advance by this. Visual
* interpolation keeps io.DeltaTime, which update() clamps to 0.25 s so animations don't jump. The
* main loop idles between frames (one frame per 2 s wake when nothing is happening), so a timer
* fed the clamped delta would run up to 8x slow.
*/
float logicDeltaTime() const { return logic_dt_; }
static constexpr float kMaxLogicDeltaSeconds = 6.0f;
/**
* @brief Pre-frame tasks that must run BEFORE ImGui::NewFrame().
*
@@ -1521,6 +1532,7 @@ private:
float lock_error_timer_ = 0.0f;
int lock_attempts_ = 0;
float lock_lockout_timer_ = 0.0f;
std::chrono::steady_clock::time_point lock_timer_armed_at_{}; // when lock_error/lockout timers were last armed
bool lock_unlock_in_progress_ = false;
// Encrypt wallet dialog state
@@ -1562,6 +1574,10 @@ private:
// Auto-lock on idle
std::chrono::steady_clock::time_point last_interaction_ = std::chrono::steady_clock::now();
// Wall-clock delta for logic timers (see logicDeltaTime()).
std::chrono::steady_clock::time_point last_update_time_{};
float logic_dt_ = 0.0f;
// Mine-when-idle: auto-start/stop mining based on system idle state
bool idle_mining_active_ = false; // true when mining was auto-started by idle detection
bool idle_scaled_to_idle_ = false; // true when threads have been scaled up for idle