镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

feat(common): sports consolidation stage 4 -- the identical sweep (plugin host, live scroll, display rules, font path) - #705

Merged
ChuckBuilds merged 5 commits into
mainfrom
claude/sports-stage4
Oct 1, 2026
Merged

ChuckBuilds merged 5 commits into
mainfrom
claude/sports-stage4

Conversation

@ChuckBuilds

@ChuckBuilds ChuckBuilds commented Oct 1, 2026 •

Copy link
Copy Markdown
Owner

Sports consolidation stage 4, the identical sweep (docs/SPORTS_UNIFICATION.md, roadmap row 4): the method families that every scoreboard carrying them still has as an identical copy move into four new hardware-free src/common modules. Nothing to reconcile and no behaviour change; nothing in core uses them yet. The version is not bumped: the modules are recorded under Unreleased, and the plugins adopt them once a release containing this PR exists (the sunset rule).

What moved

Re-measured with scripts/sports_drift_report.py against ledmatrix-plugins 56c4f15 (origin/main, 2026-09-30; the scoreboards are unchanged at today's bb1db84): 58 families identical in every copy, as at 4327c2e. This PR takes the ones identical across the nine, or across eight with the ninth lacking the method.

Module What Carriers Lines in the plugins (all copies)
sports_plugin_host SportsPluginHostMixin -- manager.py: _dispatch_switch_refresh (+ _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 all nine 1,439
sports_live_scroll SportsLiveScrollMixin -- manager.py: _live_scroll_managers, _refresh_live_scroll_managers, _live_scroll_fields, _fingerprint_games, _live_scroll_fingerprint, _live_scroll_needs_rebuild, _note_live_scroll_built, _preserving_scroll_position (+ the two LIVE_SCROLL_REBUILD_* constants) eight (ufc has no live strip) 1,336
sports_display_rules SportsCardOptionsMixin (_card_option, _recent_date_text) and SportsGameRulesMixin (_filtered_or_all, _effective_live_duration) -- sports.py eight each (no ufc; _filtered_or_all: no football) 569
sports_font_path resolve_font_path -- the module-level _resolve_font_path in every sports.py and game_renderer.py 9 + 8 copies 697

About 4,000 lines across the copies; one copy of each is about 470 lines of core.

Design points:

  • Modules split by carrier set, so a plugin never gains an override it did not have: ufc inherits the host mixin but not the live-scroll one, and not SportsCardOptionsMixin (whose _card_option would otherwise replace the SportsCoreSharedMixin._card_option ufc uses today). SportsGameRulesMixin gives football an uncalled _filtered_or_all and ufc an uncalled _effective_live_duration; nothing in either plugin or in core calls them.
  • Base order matters twice, and both are documented, tested in core and enforced by the adoption's guard: the host mixin goes before BasePlugin (two of its methods override BasePlugin defaults), and SportsCardOptionsMixin before SportsCoreSharedMixin (it wraps that mixin's _card_option by name).
  • Constants: the three identical ones move; LIVE_VOLATILE_FIELDS differs (afl, nrl and soccer add period_text) and stays in each plugin as host contract.
  • No __init__, no class attributes beyond the moved constants; the host contract is annotations only, and each module docstring lists it. The parity-safe way to satisfy mypy was annotations on parameters and type comments on locals, since a local annotation changes the AST the drift report compares.
  • _resolve_font_path is not replaced by font_layout.resolve_asset_path, as the roadmap row planned: that one never looks in the cwd and the plugins' copy does first, so the swap would change which font a process started from another checkout loads. resolve_font_path is what the copy does on any core that ships it (cwd, then resolve_asset_path); its body differs from the copy (the copy's probe for older cores is dead there), so it is checked by behaviour, path for path, against all 17 copies.

Not promoted, though identical

  • _get_timezone, _extract_game_details, _fetch_data (nine): a per-plugin import and the abstract contract, as in stage 3.
  • _schema_font_size, _resolve_font_size (eight renderers): they read each plugin's own _SCHEMA_PATH, as in stage 3.
  • The 29 families carried by seven plugins or fewer: the afl/nrl/soccer lineage's own helpers (_swrr_advance, _refresh_switch_mode_managers, _initialize_logo_dir, ...), the multi-league plugins' (_resolve_managers_for_mode (5), _extract_mode_type (7), needs_high_fps (5)), _read_bdf_native_size (6), has_active_celebration and _score_to_int (5), the rest of the three- and four-plugin helpers, and eleven two-plugin ones. Each is one lineage's code; most go when family 13 or 14 reconciles the code around them. _odds_color (seven renderers) is already core's, in SportsHelpersMixin.

None of the promoted families turned out to differ: decorators (which the report's normalisation drops) and the moved constants' values agree in every copy too.

Tests

  • test/test_sports_stage4_parity.py: with LEDMATRIX_PLUGINS set, every promoted method and constant against every plugin copy, using scripts/sports_drift_report.py's own normalisation, plus decorators and constant values; a copy that is gone counts as adopted; a plugin growing a copy it never had fails; a mixin method with no parity row fails; and the drift report must still call each family identical. 27 pass against the plugins; skipped without the variable (core CI). Mutation-checked: a changed body, @staticmethod -> @classmethod, a changed constant, a dropped @contextmanager and an unlisted new method each fail it.
  • test/test_sports_font_path.py: resolve_font_path and each of the 17 plugin copies (read from LEDMATRIX_PLUGINS, else a transcription) return the same string for cwd-only, both, install-root-only, absolute, missing and empty paths, from a temporary cwd.
  • test_sports_plugin_host.py, test_sports_live_scroll.py, test_sports_display_rules.py: behaviour ported from the plugins' own tests (Vegas weight across every plugin's data shape, the off-thread refresh and its rate limit, what counts as a strip change, the duty-cycle floor, the marquee position, the date/time rescue and its base-order requirement, the fail-open filter, the non-favourite dwell), against stub hosts carrying exactly the documented contract, plus a host-contract test per mixin that fails on an undocumented self. read.
  • All four modules are on the mypy ratchet; scripts/check_types.py: no issues in 82 modules.
  • Full suite vs a baseline worktree of origin/main (596809ac), Pillow 12.3.0 before and after: the same 61 failed / 6 errors (Windows-only, pre-existing), no failing test ID only on either side; 97 more passing, 26 more skipped (the parity rows without a plugins checkout).

Adoption (not in this PR)

Prepared on ledmatrix-plugins branch claude/sports-stage4-adoption, no PR: it floors the nine scoreboards on the release that ships these modules, so it waits for that release. There, all nine inherit the mixins (the base orders above), delete their copies (the scoreboards' tracked files lose 4,726 lines and gain 81; ufc's fight_renderer.py and headshot_downloader.py copies of _resolve_font_path go too), bump minor versions with a versions[] entry flooring on 3.8.0 (compatible_versions too), and gain scripts/test_stage4_mixin_copies.py, a sunset guard (mutation-checked) in the shape of stage 3's. Checked against this branch as core:

  • Pixels: the core harness's 192 screens (nine plugins x eight sizes), 72 scroll/Vegas cards and 135 celebration frames rendered before (plugins origin/main bb1db84) and after, alternating before/after/before: 399 frames, 0 differ (control before vs before2: 0 differ), compared pixel by pixel, Pillow 12.3.0 throughout. The after tree also passes its committed card and celebration goldens.
  • Plugin tests: run_plugin_tests.py on the nine, before and after: 321 pass on both sides, and the same two pre-existing failures (baseball test_odds_placement.py, football's interactive test_football_plugin.py). Every scripts/test_*.py guard gives the same result on both sides; check_sports_drift.py and the version-bump, manifest and registry checks pass. check_min_core_version.py reports every new import as "in no released core yet": that is its first commit listing the modules as unreleased, and it is the gate doing its job until the release is tagged.
  • In a fresh process each plugin's adopted names resolve to the core modules (ufc's _card_option still to sports_shared, as before).

Owner decision: the release. The adoption can merge only after a core release containing this PR is tagged; its floor says 3.8.0 and must be changed if that release is numbered otherwise. The alternative, adopting now behind guarded imports that fall back to bundled copies, keeps every copy (moved into a fallback module), gives users nothing (there is no behaviour change to deliver), and is the B5 shape the doc warns against: the fallback protects nobody from a break on the modern path while doubling what has to stay in step. Recommended: release, then merge the adoption as is.

One coupling to know about: once this PR is on core main, the monorepo's scripts/test_check_min_core_version.py (run against core main in its CI) fails on four core modules missing from MODULE_FIRST_VERSION until they are added there as None (unreleased). That four-line change is the first commit of the adoption branch and can go in on its own as soon as this merges.

CHANGELOG has the module entries under Unreleased; src/common/README.md and SPORTS_UNIFICATION (module table, a "Stage 4" section, roadmap row 4) are updated.

🤖 Generated with Claude Code

ChuckBuilds and others added 3 commits September 30, 2026 20:56
…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>
… weighting exists

The scoreboards' manager.py introduced get_vegas_priority_weight with a
section comment; the adoption drops it with the copies, so it moves here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 39 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 23881d1f-39a6-4a53-bdaa-b5437106330f

📥 Commits

Reviewing files that changed from the base of the PR and between c6701ac and bf1cb24.

📒 Files selected for processing (14)
  • CHANGELOG.md
  • docs/SPORTS_UNIFICATION.md
  • mypy-clean.txt
  • scripts/sports_drift_report.py
  • src/common/README.md
  • src/common/sports_display_rules.py
  • src/common/sports_font_path.py
  • src/common/sports_live_scroll.py
  • src/common/sports_plugin_host.py
  • test/test_sports_display_rules.py
  • test/test_sports_font_path.py
  • test/test_sports_live_scroll.py
  • test/test_sports_plugin_host.py
  • test/test_sports_stage4_parity.py
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codacy-production

codacy-production Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 119 complexity

Metric Results
Complexity 119

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

ChuckBuilds and others added 2 commits October 1, 2026 08:31
pyflakes (Codacy) does not read "# type:" comments, so it flagged the
Optional and List imports they use as unused (F401). Inline annotations say
the same to mypy. The drift-report normaliser, which the stage 4 parity test
shares, now treats "x: T = v" as "x = v" -- it already dropped annotations
elsewhere -- so the core copies still match the plugins' plain assignments.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ChuckBuilds
ChuckBuilds merged commit 3ad0438 into main Oct 1, 2026
13 checks passed
@ChuckBuilds
ChuckBuilds deleted the claude/sports-stage4 branch October 1, 2026 14:10
ChuckBuilds added a commit that referenced this pull request Oct 1, 2026
#703, #705, #706 and #693 merged after this branch was cut; their CHANGELOG
entries now sit under 3.8.0. The summary and New modules list name them
(sports consolidation stage 4's four modules, src/ipc, field_model), stage
4's section says to floor on 3.8.0, and src/common/README.md marks its four
modules 3.8.0 instead of Unreleased.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ChuckBuilds added a commit that referenced this pull request Oct 1, 2026
* chore(deprecation): remove the 35 APIs deprecated for 3.8.0

The usage scan (docs/DEPRECATIONS_3.8.md, regenerated 2026-10-01 and
committed here) finds no call or override of any of them in the 46
monorepo plugins or the 8 third-party plugins plugins.json lists; the
only core callers were other deprecated methods removed alongside.

- CacheManager: 13 methods, plus the private helpers only
  has_data_changed used (_has_*_changed, _is_market_open).
- DisplayManager: 7 methods, plus WEATHER_COLORS and the private
  _draw_sun/_cloud/_rain/_snow/_storm helpers only the icon methods used.
- FontManager: 14 methods, plus size_tokens, _save_overrides and
  _clear_plugin_font_cache. font_overrides and _load_overrides stay:
  resolve_font() still applies config/font_overrides.json.
  performance_stats stays: get_font() keeps it and tests read it.
- PluginManager.get_enabled_plugins.

test_deprecation.py pins only the two 3.9.0 markers now; the scanner
tests run against a stand-in core instead of the real markers. The
memory-tier tests read stats through log_memory_cache_stats() and the
component, and the test of the removed _clear_plugin_font_cache goes.
Docs drop the removed methods' reference entries; the Deprecated APIs
table becomes "Removed in 3.8.0". CHANGELOG gains a Removed section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(deprecation): drop the test harness's copies of the removed icon methods

VisualTestDisplayManager still drew weather icons that DisplayManager no
longer has, so a plugin's visual tests could pass on calls that raise
AttributeError on the real display. Its draw_sun/draw_cloud/draw_rain/
draw_snow/draw_weather_icon/draw_text_with_icons, WEATHER_COLORS and the
private helpers go, with the tests that exercised them. The CHANGELOG's
Deprecations entries no longer say nothing is removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: prepare the 3.8.0 release

Bumps src.__version__ to 3.8.0 and turns Unreleased into ## 3.8.0, with a
summary and a New modules list (vegas_elements, testing.vegas, sports_vegas;
display_watchdog, plugin_catalog, plugin_runtime) for plugins flooring on
3.8.0. Adds the CHANGELOG line #701's second commit lacked (blocks laid out
off the render thread).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: 3.8.0 also ships what landed on main since the prep

#703, #705, #706 and #693 merged after this branch was cut; their CHANGELOG
entries now sit under 3.8.0. The summary and New modules list name them
(sports consolidation stage 4's four modules, src/ipc, field_model), stage
4's section says to floor on 3.8.0, and src/common/README.md marks its four
modules 3.8.0 instead of Unreleased.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant