diff --git a/scripts/scroll_speeds.py b/scripts/scroll_speeds.py index ed4e9030..bdabf023 100644 --- a/scripts/scroll_speeds.py +++ b/scripts/scroll_speeds.py @@ -190,9 +190,38 @@ def print_ladder(hz, highlight=None): ) else "" print(" " + entry.describe() + mark) print("") - print("Set one in config.json as pixels per second, e.g.") - print(' "display_options": {{"scroll_pixels_per_second": {:.0f}}}'.format( - scroll_config.solve_crisp(highlight if highlight else hz / 2, hz).pixels_per_second)) + print_config_advice(scroll_config.solve_crisp(highlight if highlight else hz / 2, hz)) + + +def config_advice(choice): + """The config that selects ``choice``, in the keys the resolver honours. + + Tickers take a ``scroll_speed`` (px per step) + ``scroll_delay`` (seconds) + pair, and scroll_config ranks that pair ABOVE ``scroll_pixels_per_second`` + -- deliberately, because some plugins give the flat key a schema default. + Many schemas default the pair too, so a flat key added by hand is usually + ignored. Advise the pair: pixels_per_frame every frame_hold/refresh + seconds is exactly the crisp speed. + """ + pair = { + "scroll_speed": choice.pixels_per_frame, + "scroll_delay": round(choice.frame_hold / choice.refresh_hz, 6), + } + scoreboard = {"scroll_speed": round(choice.pixels_per_second, 2)} + return pair, scoreboard + + +def print_config_advice(choice): + pair, scoreboard = config_advice(choice) + print("To use {:.1f} px/s, set it where the plugin keeps its scroll speed.".format( + choice.pixels_per_second)) + print("Tickers take a scroll_speed (px per step) + scroll_delay (seconds) pair:") + print(' "display_options": {}'.format(json.dumps(pair))) + print("(some plugins keep the pair at the top level or under \"display\").") + print("The pair outranks scroll_pixels_per_second, which is ignored whenever the") + print("pair is present -- and schema defaults usually put it there.") + print("Sports scoreboards take pixels per second per league instead:") + print(' "scroll_settings": {}'.format(json.dumps(scoreboard))) def main(): diff --git a/test/test_scroll_speeds_script.py b/test/test_scroll_speeds_script.py index b101670e..6d3b68c9 100644 --- a/test/test_scroll_speeds_script.py +++ b/test/test_scroll_speeds_script.py @@ -96,3 +96,49 @@ def test_the_service_uses_the_shared_builder(test_config): finally: DisplayManager._instance = None shared.assert_called_once() + + +class TestSpeedAdvice: + """The advice must name keys the resolver actually honours.""" + + def _advice(self, script, capsys, hz, want): + script.print_ladder(hz, want) + return capsys.readouterr().out + + def test_advice_is_the_speed_delay_pair(self, script, capsys): + out = self._advice(script, capsys, 100.0, 60) + assert '"scroll_speed": 2' in out + assert '"scroll_delay": 0.03' in out + + @pytest.mark.parametrize("hz,want", [(100.0, 60), (100.0, 50), (120.0, 45), + (60.0, 30), (100.0, None)]) + def test_the_advised_pair_resolves_to_the_advised_speed(self, script, capsys, + hz, want): + """Apply the printed pair over a schema-default pair, as a saved config + would carry it, and the resolver must land on the advertised speed.""" + import json + import re + + from src.common import scroll_config + + out = self._advice(script, capsys, hz, want) + block = re.search(r'"display_options": (\{[^}]*\})', out) + assert block, out + advised = json.loads(block.group(1)) + config = {"display_options": {"scroll_speed": 1.0, "scroll_delay": 0.02, + "scroll_pixels_per_second": 999, + **advised}} + expected = scroll_config.solve_crisp(want if want else hz / 2, hz) + settings = scroll_config.configure( + _Helper(), plugin_config=config, refresh_hz=hz) + assert settings.pixels_per_second == pytest.approx(expected.pixels_per_second) + assert settings.frame_hold == expected.frame_hold + + def test_the_deprecated_key_is_not_recommended(self, script, capsys): + out = self._advice(script, capsys, 100.0, 60) + assert '"scroll_pixels_per_second":' not in out + + +class _Helper: + def set_scroll_speed(self, speed): + pass