feat(common): sports_plugin_host, sports_live_scroll, sports_display_rules and sports_font_path, promoted from the scoreboards (sports consolidation stage 4)

The identical sweep: method families every scoreboard carrying them has as
an identical copy (executable AST, docstrings stripped, decorators compared),
re-measured at ledmatrix-plugins 56c4f15. Four new hardware-free modules;
the bodies are the plugins', with type annotations and type comments for
the mypy ratchet.

- src/common/sports_plugin_host.py: SportsPluginHostMixin, ten helpers of
  the scoreboard plugin class (manager.py) identical in all nine:
  _dispatch_switch_refresh (with _SWITCH_REFRESH_MIN_GAP_SECONDS),
  get_vegas_priority_weight, _favorite_team_is_live, _favorite_scan_targets,
  _favorite_scan_games, _game_involves, get_vegas_content_type,
  _dynamic_feature_enabled, _get_total_games_for_manager,
  _build_manager_key. Listed before BasePlugin, whose defaults two of them
  override.
- src/common/sports_live_scroll.py: SportsLiveScrollMixin, the eight
  manager.py methods that rebuild a live scroll strip mid-cycle (all eight
  scoreboards with a strip; ufc has none), with the two rebuild-rate
  constants. LIVE_VOLATILE_FIELDS differs (afl/nrl/soccer add period_text)
  and stays in each plugin as host contract.
- src/common/sports_display_rules.py: SportsCardOptionsMixin (_card_option,
  _recent_date_text; the eight team scoreboards; must precede
  SportsCoreSharedMixin, whose _card_option it wraps) and
  SportsGameRulesMixin (_filtered_or_all, all but football;
  _effective_live_duration, all but ufc). Split by carrier so ufc does not
  gain a _card_option override it never had.
- src/common/sports_font_path.py: resolve_font_path, what the 17
  _resolve_font_path copies (nine sports.py, eight game_renderer.py) return
  on a core that ships it: the cwd first, then resolve_asset_path. Not
  resolve_asset_path alone, which skips the cwd.

Left in the plugins: _get_timezone, _extract_game_details and _fetch_data
(per-plugin import, abstract contract), _schema_font_size and
_resolve_font_size (each plugin's _SCHEMA_PATH), and the families carried by
seven plugins or fewer.

Tests: behaviour ported from the plugins' own tests against stub hosts with
exactly the documented contract, a host-contract test per mixin, and
test_sports_stage4_parity.py, which with LEDMATRIX_PLUGINS set compares
every promoted method and constant with every plugin copy using
scripts/sports_drift_report.py's normalisation plus decorators and values
(27 pass against 56c4f15; mutation-checked: a changed body, a swapped
decorator, a changed constant, a dropped @contextmanager and an unlisted
new method each fail it). test_sports_font_path.py runs both on the same
paths from a temporary cwd. All four modules are on the mypy ratchet, in
src/common/README.md, the CHANGELOG's Unreleased section (module entries
for the sunset floor; the version is not bumped) and SPORTS_UNIFICATION.
Nothing in core uses them yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-09-30 20:56:51 -04:00
co-authored by Claude Opus 5.5
parent 596809acc3
commit 11a7ca9671
13 changed files with 1989 additions and 1 deletions
+32
View File
@@ -416,6 +416,38 @@ read any of them:
in AP mode with no internet. They get a local `static/v3/plugin-frame.css`
with the v2 palette they were written against.
### New modules (sports consolidation stage 4)
A plugin may import these via `src.*` once it floors on the release that
ships them (the first release cut from this section). All four hold code the
scoreboard plugins carry as identical copies (checked at ledmatrix-plugins
`56c4f15`), moved without behaviour change under the plugins' own names;
each docstring lists what the host class must provide. Nothing in core uses
them yet. The plugins delete their copies when they floor on that release.
- `src/common/sports_plugin_host.py` — `SportsPluginHostMixin`, ten helpers
of the scoreboard plugin class (`manager.py`) identical in all nine:
`_dispatch_switch_refresh` (with `_SWITCH_REFRESH_MIN_GAP_SECONDS`),
`get_vegas_priority_weight`, `_favorite_team_is_live`,
`_favorite_scan_targets`, `_favorite_scan_games`, `_game_involves`,
`get_vegas_content_type`, `_dynamic_feature_enabled`,
`_get_total_games_for_manager` and `_build_manager_key`. List it before
`BasePlugin`: two of these override its defaults.
- `src/common/sports_live_scroll.py` — `SportsLiveScrollMixin`, the eight
`manager.py` methods that rebuild a live scroll strip mid-cycle without
moving the marquee (`_live_scroll_needs_rebuild`,
`_preserving_scroll_position`, ...), with `LIVE_SCROLL_REBUILD_MIN_SECONDS`
and `LIVE_SCROLL_REBUILD_DUTY_DIVISOR`; identical in the eight scoreboards
with a strip (not ufc). `LIVE_VOLATILE_FIELDS` stays in each plugin.
- `src/common/sports_display_rules.py` — `SportsCardOptionsMixin`
(`_card_option`, `_recent_date_text`; the eight team scoreboards; list it
before `SportsCoreSharedMixin`) and `SportsGameRulesMixin`
(`_filtered_or_all`, `_effective_live_duration`; all nine).
- `src/common/sports_font_path.py` — `resolve_font_path`, what every
scoreboard's `_resolve_font_path` (nine `sports.py`, eight
`game_renderer.py`) returns on a core that ships it: the path as given when
it exists, else `font_layout.resolve_asset_path`.
## 3.7.0
Sports consolidation stage 3 (#672). No behaviour change: nothing in core