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>