Skip to content

odds-ticker: display_options.scroll_speed/scroll_delay are silently ignored; frame-based mode is unreachable #408

Description

@ChuckBuilds

Summary

In odds-ticker (v1.3.3), the documented and recommended display_options.scroll_speed / display_options.scroll_delay settings are silently ignored. The plugin always falls back to time-based scrolling at scroll_pixels_per_second, so frame-based mode is unreachable for every user of the current config format.

On a 100 Hz panel this produces visible judder: the default 50 px/s at 100 fps works out to 0.5 px per frame, integer truncation makes every second frame byte-identical, and the display manager's dirty-tracking then skips the panel swap on those frames. Motion happens in irregular 1–2 px hops at ~50 Hz instead of one clean pixel per refresh.

Two defects, both in manager.py

1. The priority-1 branch does not clear scroll_pixels_per_second

# manager.py:246-251  (Priority 1 - current/recommended format)
if display_options and ('scroll_speed' in display_options or 'scroll_delay' in display_options):
    self.scroll_speed = display_options.get('scroll_speed', 1.0)
    self.scroll_delay = display_options.get('scroll_delay', 0.02)
    self.scroll_pixels_per_second = display_options.get('scroll_pixels_per_second')   # <-- keeps the value
    self.logger.info(f"... (frame-based mode)")

# manager.py:252-257  (Priority 2 - deprecated format) -- this one gets it right
elif display_config and (...):
    ...
    self.scroll_pixels_per_second = None    # <-- correctly cleared

config_schema.json declares a default for this key:

properties.display_options.properties.scroll_pixels_per_second.default = 50.0

Because the plugin system deep-merges schema defaults into plugin config, display_options always contains scroll_pixels_per_second, so self.scroll_pixels_per_second is never None on this path. Removing the key from config.json does not help — the schema default is merged straight back in.

2. use_frame_based never looks at display_options

# manager.py:331-333
use_frame_based = (self.scroll_pixels_per_second is None and
                   display_config and
                   ('scroll_speed' in display_config or 'scroll_delay' in display_config))

This only consults display_config (the deprecated display block). Even with defect 1 fixed, a user on the recommended display_options format still gets use_frame_based == False.

Observed behaviour

The plugin logs its scroll configuration twice on startup — the first line reports frame-based, then 0.36 s later it reconfigures to time-based and that is what actually runs:

17:12:59.140 - INFO - Using display_options.scroll_speed=1.0 px/frame, display_options.scroll_delay=0.01s (frame-based mode)
17:12:59.499 - INFO - Using scroll_pixels_per_second: 50.0 px/s (time-based mode)

Config in use:

"odds-ticker": {
  "display_options": { "scroll_speed": 1.0, "scroll_delay": 0.01 }
}

Resulting frame-time distribution (128x64 x2 chain, limit_refresh_rate_hz: 100), bimodal rather than the expected flat 10 ms:

2.21ms   2.22ms   21.13ms  17.75ms  2.22ms  21.05ms  17.78ms  17.24ms

The ~2.2 ms frames are duplicates where nothing moved and the panel swap was skipped; the ~17–21 ms frames waited two vsyncs. Note that Avg FPS reports a healthy 100.0 throughout, because a 2.2 ms duplicate and a 21 ms double-wait average out to exactly 10 ms — the fault is invisible in the mean and only shows in the distribution.

For comparison, ledmatrix-leaderboard on the same panel holds a flat 10.00 ms and looks correct; it reads scroll_pixels_per_second only as a fallback and sets it to None when display.scroll_speed is present (manager.py:67).

Suggested fix

Clear the value on the priority-1 path so it matches priority 2:

# manager.py:250
self.scroll_pixels_per_second = None

and let use_frame_based accept either config shape:

# manager.py:331
use_frame_based = (
    self.scroll_pixels_per_second is None
    and (
        (display_options and ('scroll_speed' in display_options or 'scroll_delay' in display_options))
        or (display_config and ('scroll_speed' in display_config or 'scroll_delay' in display_config))
    )
)

Worth considering separately: dropping the default: 50.0 from config_schema.json for a key documented as deprecated. A schema default on a deprecated field means it is always present, which is what makes it override the current format rather than acting as a fallback.

stock-news reads the same key at manager.py:315 and may be worth checking, though its logic differs.

Workaround

Set the deprecated key explicitly to a value that yields a whole number of pixels per frame. At 100 fps that means 100 px/s:

"odds-ticker": {
  "display_options": {
    "scroll_speed": 1.0,
    "scroll_delay": 0.01,
    "scroll_pixels_per_second": 100.0
  }
}

Environment

  • odds-ticker 1.3.3
  • Raspberry Pi 4 Model B, Python 3.13.5, aarch64
  • Panel: 2x 128x64 chain, limit_refresh_rate_hz: 100, hardware_mapping: regular

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions