Skip to content

Commit e45fa76

Browse files
fix: current speed stuck at 0 B/s due to unstable spring animation (#30)
animate.Spring multiplied velocity by 1 - damping*dt. At the UI tick interval (130ms) this evaluates to 1 - 12*0.13 = -0.56, a negative damping factor that flips the velocity sign and grows its magnitude every tick. The animated throughput value collapsed to deep negative values, which FormatBpsExt clamps to zero — so the current download/ upload readout stayed at 0 B/s while the sparkline and peak (which read raw history samples) showed real traffic. Damping now uses exp(-damping*dt), which stays positive for any step size, keeping the spring stable and convergent. Adds a regression test at the UI tick interval. Bump VERSION to 0.2.2. Closes #29
1 parent e263838 commit e45fa76

5 files changed

Lines changed: 30 additions & 3 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
## [0.2.2] - 2026-08-18
2+
3+
### Fixed
4+
- Current download/upload speed always displaying `0 B/s` — the spring animation in `animate.Spring` was numerically unstable at the UI tick interval (130ms), since `1 - damping*dt` evaluated to a negative damping factor. Velocity flipped sign and grew every tick, driving the animated value deeply negative, where `FormatBpsExt` clamped it to `0 B/s`. Damping now uses an exponential factor (`exp(-damping*dt)`), keeping the spring stable and convergent at any step size. The sparkline and peak values were unaffected because they read raw history samples directly.
5+
6+
### Changed
7+
- VERSION bumped to 0.2.2.
8+
19
## [0.2.1] - 2026-07-23
210

311
### Added

‎VERSION‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.2.1
1+
0.2.2

‎cmd/flow/main.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616
"github.com/programmersd21/flow/internal/ui"
1717
)
1818

19-
var version = "0.2.1"
19+
var version = "0.2.2"
2020

2121
func main() {
2222
flagTiny := flag.Bool("tiny", false, "single-line mode for tmux/status bars")

‎internal/animate/ease.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ func Clamp01(t float64) float64 {
3232
func Spring(current, target float64, velocity *float64, dt float64) float64 {
3333
force := stiffness * (target - current)
3434
*velocity += force * dt
35-
*velocity *= 1.0 - damping*dt
35+
*velocity *= math.Exp(-damping * dt)
3636
if *velocity < 0.0001 && *velocity > -0.0001 {
3737
*velocity = 0
3838
}

‎internal/animate/ease_test.go‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,22 @@ func TestSpringConverges(t *testing.T) {
4848
t.Errorf("Spring did not converge: %f (want ~100)", val)
4949
}
5050
}
51+
52+
func TestSpringStableAtUITickInterval(t *testing.T) {
53+
const target = 34603008.0
54+
var vel float64
55+
val := 0.0
56+
minVal := val
57+
for i := 0; i < 1000; i++ {
58+
val = Spring(val, target, &vel, 0.13)
59+
if val < minVal {
60+
minVal = val
61+
}
62+
}
63+
if math.Abs(val-target) > target*0.01 {
64+
t.Errorf("Spring did not converge at dt=0.13: %f (want ~%f)", val, target)
65+
}
66+
if minVal < 0 {
67+
t.Errorf("Spring oscillated negative at dt=0.13 (min %f) — value would render as 0 B/s", minVal)
68+
}
69+
}

0 commit comments

Comments
 (0)