perf(effects): keep the theme animation clock in double precision

The theme effects stored their animation time in a 32-bit float taken
from the ImGui clock. After a few days of uptime a float can no longer
resolve a single frame step, so every oscillation, drift and sweep began
to stutter, snapping forward a frame at a time instead of moving
smoothly.

- The clock is now held as a double, and the phase/oscillator maths runs
  through a small set of shared helpers that reduce the argument in
  double before returning a float. A float clock at 30 days steps by a
  quarter second per ULP; the double clock stays frame-accurate.
- Hue cycling, the rainbow and gradient borders, shimmer, specular
  glare, glow pulse, edge trace, embers, the sandstorm and the viewport
  wash all use the shared helpers, so their timing no longer drifts with
  uptime.

Tests: the helpers advance smoothly after seven days, pin the legacy
float breakdown at 30 days, and stay in range for negative offsets.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-10-13 13:30:20 -05:00
co-authored by Claude Opus 4.8
parent 944fafa1c7
commit fbc429f914
4 changed files with 82 additions and 28 deletions
+22
View File
@@ -69,6 +69,7 @@
#include "ui/material/scroll_edge_fade.h"
#include "ui/node_status_banner.h"
#include "ui/staleness_badge.h"
#include "ui/effects/effect_clock.h"
#include "ui/notifications.h"
#include "data/seed_migration_resume.h"
#include "data/pin_vault_policy.h"
@@ -13492,6 +13493,26 @@ void testPoolFeeParsing()
EXPECT_FALSE(ok); // string, not number
}
void testEffectClock() {
using namespace dragonx::ui::effects;
const double t7d = 7 * 86400.0, dt = 1.0 / 60.0;
// smooth after 7 days: consecutive frames advance by rate*dt (0.3/60 = 0.005 cycle)
for (int k = 0; k < 120; ++k) {
float a = cyclePhase(t7d + k * dt, 0.3), b = cyclePhase(t7d + (k + 1) * dt, 0.3);
float d = b - a; if (d < -0.5f) d += 1.0f; // wrap
EXPECT_NEAR(d, 0.005f, 1e-4f);
}
// legacy float clock pins the bug: at 30 days one float ULP is 0.25 s
const double t30 = 30 * 86400.0;
EXPECT_TRUE((float)(t30 + 0.5 / 60.0) == (float)(t30 + 1.5 / 60.0));
EXPECT_TRUE(cyclePhase(t30 + 0.5 / 60.0, 0.3) != cyclePhase(t30 + 1.5 / 60.0, 0.3));
// range and negative offsets
EXPECT_NEAR(cyclePhase(1.0, 0.0, -0.25), 0.75f, 1e-6f);
for (double t = 0; t < 1e7; t += 123457.7) { float p = cyclePhase(t, 0.37, -0.1); EXPECT_TRUE(p >= 0.0f && p < 1.0f); }
EXPECT_NEAR(cycleSin(t7d + 0.25, 1.0), 1.0f, 1e-5f);
EXPECT_NEAR(radSin(1e6, 0.0, 1.5707963267948966), 1.0f, 1e-6f);
}
// The effective pool list = official pools ∪ saved favorites ∪ current custom pool,
// endpoint-deduped, with synthetic user rows flagged official=false.
void testEffectivePools()
@@ -18227,6 +18248,7 @@ int main()
testPoolHashrateParsing();
testPoolFeeParsing();
testEffectivePools();
testEffectClock();
testFormatFeePercent();
testPoolWeightedSelection();
testCoalescingWriter();