mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-05 06:45:09 +00:00
feat(scroll): show which scroll speeds are smooth on this panel (#710)
* feat(scroll): show which scroll speeds are smooth on this panel The Vegas Scroll Speed slider now says what the panel will do with the chosen speed and offers the nearest smooth ones to click. Backed by scroll_config.speed_advice() and GET /api/v3/config/scroll-speed-advice, which uses the refresh the display measured rather than the cap. Also stops the default 50 px/s snapping to a stepped 48 px/s (2px every 5 refreshes, 24fps) on a 120Hz panel: the low-fps penalty in solve_crisp() now loses to 60 or 40 px/s. 100Hz panels are unchanged. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * fix(scroll): hint threw before its timer variables existed; count 25-30fps as stepped The Vegas speed hint called refreshScrollSpeedHint() before the let declarations it uses, so it never rendered (found on ledpi). And the solver's low-fps penalty stopped at 25fps, which let a measured 125.7Hz panel keep a 25.1fps 2px-every-5-refreshes scroll. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * test: add the scroll-speed-advice route to the /api/v3 URL map snapshot Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
4ddc3a3620
commit
16b566e14f
Vendored
+9
@@ -192,6 +192,15 @@
|
||||
"POST"
|
||||
]
|
||||
],
|
||||
[
|
||||
"/api/v3/config/scroll-speed-advice",
|
||||
"api_v3.get_scroll_speed_advice",
|
||||
[
|
||||
"GET",
|
||||
"HEAD",
|
||||
"OPTIONS"
|
||||
]
|
||||
],
|
||||
[
|
||||
"/api/v3/config/secrets",
|
||||
"api_v3.get_secrets_config",
|
||||
|
||||
@@ -13,6 +13,7 @@ from src.common.scroll_config import ( # noqa: E402
|
||||
MAX_PIXELS_PER_FRAME,
|
||||
crisp_ladder,
|
||||
solve_crisp,
|
||||
speed_advice,
|
||||
MAX_PIXELS_PER_SECOND,
|
||||
MIN_PIXELS_PER_SECOND,
|
||||
ScrollSettings,
|
||||
@@ -465,3 +466,41 @@ class TestFrameHoldIsReportedNotApplied:
|
||||
display_manager=dm)
|
||||
assert dm.calls == [], "configure() must not apply the hold itself"
|
||||
assert settings.frame_hold == 4, "but it must report what to apply"
|
||||
|
||||
|
||||
class TestSpeedAdvice:
|
||||
def test_default_speed_on_a_120hz_panel_is_not_left_stepped(self):
|
||||
"""50 px/s used to snap to 48 (2px every 5 refreshes, 24fps)."""
|
||||
got = solve_crisp(50, 120)
|
||||
assert got.steppiness == "smooth"
|
||||
assert got.pixels_per_frame == 1
|
||||
|
||||
def test_unchanged_choices_on_a_100hz_panel(self):
|
||||
assert solve_crisp(50, 100).pixels_per_second == pytest.approx(50.0)
|
||||
assert solve_crisp(60, 100).pixels_per_second == pytest.approx(66.667, abs=0.01)
|
||||
|
||||
def test_smooth_exact_speed_needs_no_alternatives(self):
|
||||
advice = speed_advice(60, 120)
|
||||
assert advice["exact"] and advice["smooth"]
|
||||
assert advice["alternatives"] == []
|
||||
|
||||
def test_off_ladder_speed_offers_the_nearest_smooth_ones(self):
|
||||
advice = speed_advice(50, 120, 10, 200)
|
||||
assert advice["applied"]["steppiness"] == "smooth"
|
||||
offered = [a["pixels_per_second"] for a in advice["alternatives"]]
|
||||
assert offered == [40.0, 60.0]
|
||||
assert all(a["steppiness"] == "smooth" for a in advice["alternatives"])
|
||||
|
||||
def test_alternatives_stay_inside_the_requested_range(self):
|
||||
advice = speed_advice(50, 120, 45, 200)
|
||||
assert all(45 <= a["pixels_per_second"] <= 200 for a in advice["alternatives"])
|
||||
|
||||
def test_a_whole_number_near_the_panels_speed_counts_as_exact(self):
|
||||
"""The UI sends 63 for a 62.9 px/s panel."""
|
||||
assert speed_advice(63, 125.74)["exact"]
|
||||
|
||||
def test_a_measured_rate_does_not_let_a_stepped_speed_through(self):
|
||||
"""125.74Hz: 50.3px/s is 2px every 5 refreshes at 25.1fps."""
|
||||
got = solve_crisp(50, 125.74)
|
||||
assert got.steppiness == "smooth"
|
||||
assert got.pixels_per_frame == 1
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
"""GET /api/v3/config/scroll-speed-advice: what the panel does with a speed."""
|
||||
import json
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
from flask import Flask
|
||||
|
||||
from web_interface.blueprints.api_v3 import api_v3
|
||||
from web_interface.blueprints.api_v3 import config as config_routes
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client(monkeypatch, tmp_path):
|
||||
stats = tmp_path / "stats.json"
|
||||
monkeypatch.setattr("src.common.frame_timing.default_stats_path", lambda: str(stats))
|
||||
manager = MagicMock()
|
||||
manager.load_config.return_value = {
|
||||
"display": {"hardware": {"limit_refresh_rate_hz": 120}}}
|
||||
monkeypatch.setattr(api_v3, "config_manager", manager, raising=False)
|
||||
app = Flask(__name__)
|
||||
app.register_blueprint(api_v3, url_prefix="/api/v3")
|
||||
c = app.test_client()
|
||||
c.stats_path = stats
|
||||
return c
|
||||
|
||||
|
||||
def test_uses_the_configured_cap_without_a_measurement(client):
|
||||
body = client.get("/api/v3/config/scroll-speed-advice?speed=50").get_json()
|
||||
data = body["data"]
|
||||
assert data["refresh_source"] == "configured"
|
||||
assert data["refresh_hz"] == 120.0
|
||||
assert [a["pixels_per_second"] for a in data["alternatives"]] == [40.0, 60.0]
|
||||
|
||||
|
||||
def test_prefers_the_measured_refresh(client):
|
||||
client.stats_path.write_text(json.dumps({"measured_refresh_hz": 125.74}))
|
||||
data = client.get("/api/v3/config/scroll-speed-advice?speed=50").get_json()["data"]
|
||||
assert data["refresh_source"] == "measured"
|
||||
assert data["refresh_hz"] == 125.7
|
||||
|
||||
|
||||
def test_ignores_an_implausible_stale_measurement(client):
|
||||
client.stats_path.write_text(json.dumps({"measured_refresh_hz": 40.0}))
|
||||
data = client.get("/api/v3/config/scroll-speed-advice?speed=50").get_json()["data"]
|
||||
assert data["refresh_source"] == "configured"
|
||||
|
||||
|
||||
def test_rejects_a_non_numeric_speed(client):
|
||||
assert client.get("/api/v3/config/scroll-speed-advice?speed=fast").status_code == 400
|
||||
Reference in New Issue
Block a user