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

ufc: adopt core 3.5.0's shared sports mixins; golden images first (sports consolidation stage 1b) - #564

Merged
ChuckBuilds merged 8 commits into
mainfrom
claude/stage1b-ufc-mixins
Sep 29, 2026
Merged

ChuckBuilds merged 8 commits into
mainfrom
claude/stage1b-ufc-mixins

Conversation

@ChuckBuilds

Copy link
Copy Markdown
Owner

Stage 1 of the sports-code consolidation, part b. It follows #563 and moves UFC onto core v3.5.0's shared sports code.

Changes

  • Golden images first: a harness fixture plus 16 golden images (8 sizes × recent/upcoming), committed before any code change. Time is frozen, the fixture seeds the caches, and the fighter data is invented. Changing a single outline colour makes all 16 fail, so they do catch changes.
  • ufc_espn_dates.py removed: the file (385 lines) is deleted and the imports are plain. It now matches core except for typing. ufc-scoreboard joins SUNSET_PLUGINS, which means none of the nine scoreboards bundles a copy any more.
  • SportsHelpersMixin: inherited from core, and UFC's 11 identical copies are deleted.
  • SportsCoreSharedMixin, SportsLiveSharedMixin, SportsRecentSharedMixin: adopted, and 18 methods are deleted.
    • 15 are identical to core's when compared as ASTs.
    • _get_weeks_data differs only by an unreachable date_str = "".
    • _round_robin_favorites has one variable renamed.
    • _idle_live_interval is the perf(football): keep the card renderer and card width between strip builds (3.18.3) #599 change, described below.
    • Three methods really do differ and stay on SportsCore, which comes first in every class hierarchy: _draw_scorebug_layout, _get_layout_offset and _draw_text_with_outline.
  • Version: 1.16.0, requiring core 3.5.0 (versions[] entry, CHANGELOG, plugins.json).
  • Docs and guards: stale ufc wording is fixed in the 08 guide, CLAUDE.md, check_sports_helpers_parity.py and check_selection_settings.py. The drift baseline drops one entry.

Net: 32 files, +557 / −1,091 (sports.py is 3,947 → 3,297 lines).

Behaviour change: the #599 idle wake-up

Until now, with nothing live, UFC's live check backed off to its 900 s maximum and could miss the first bout of a card. It now notes the earliest upcoming card start from the events it already downloads, and never waits past it. The wait never drops below the live interval (30 s by default). For 15 minutes after the start it polls at the live interval, and it makes no extra requests.

Driven through the real live manager with a card two hours away, the waits were:

Time relative to card start 2 h before 2 min before 5 min after 1 h after
Before this PR 900 s 900 s 900 s 900 s
After this PR 900 s 120 s 30 s 900 s

New cases in test_idle_league_backoff.py fail on the old code.

Verification

All runs used core v3.5.0, comparing origin/main against this branch.

  • Rendering: the 16 UFC harness PNGs are byte-identical, and the goldens pass on both trees.
  • Plugin tests: run_plugin_tests.py for ufc plus the 8 team scoreboards gives 322 passed and 2 failed on both trees, with the same result per test. The 2 failures are pre-existing: football's test_odds_placement and test_football_plugin, which asks for input.
  • Repo scripts: all 52 scripts/test_*.py and check scripts pass. check_version_bump --base origin/main ufc-scoreboard passes, and so does update_registry.py --check.
  • Attribute check: across all 12 UFC classes, no attribute is lost. Every method that now comes from core has the same body, apart from the cases listed above. All three managers, built through the plugin, have the same attributes and values.
  • On ledpi (Pi 4, core v3.5.0, 192x48): UFC 1.16.0 loaded and fetched real ESPN data (18 upcoming fights). It cycled through its live, recent and upcoming modes with no errors.
    • Two log lines showed up that already happen on main: the separator-icon warning, and "Showing UNK vs UNK", because UFC events have no abbreviations.
    • Afterwards I removed the test install from ledpi and restored its config.

🤖 Generated with Claude Code

ChuckBuilds and others added 8 commits September 28, 2026 16:56
…e the mixin adoption

ufc-scoreboard had no test/harness.json, so CI's safety harness rendered it
from schema defaults against live ESPN, and no golden could catch a visual
change. This adds a deterministic fixture and 16 goldens (8 sizes x
ufc_recent / ufc_upcoming), generated from origin/main's plugin on core
v3.5.0, as the before-image for adopting core's shared sports mixins.

The fixture seeds the season cache key BaseUFCManager reads first
(ufc_scoreboard_schedule_2026) with cards either side of a frozen
2026-03-10 18:00 UTC: two finals and a postponed bout for Recent (the
postponed one must stay off it), two scheduled bouts for Upcoming, and one
card outside each window. Every bout has a seeded odds entry, so show_odds
stays on without an odds request and the upcoming card draws a line.

- display.use_short_date_format is on: the long date goes through the
  harness display manager's format_date_with_ordinal, whose %-d raises on
  Windows, and every bout then fails to parse.
- Fighters carry no ESPN id, so no headshot is fetched; cards render
  without headshots, as they do for a fighter ESPN has none for.
- Live is not rendered. The live manager is built and updated whatever
  show_live says, and _fetch_todays_games() is a direct ESPN call;
  test_mode plus a huge live_update_interval stops that fetch. The one
  request left is the headshot prefetch for test_mode's built-in fight
  (ESPN answers 404), which nothing rendered depends on: a run with DNS
  blocked produced byte-identical PNGs.

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

Core v3.5.0 (5b30052b) is tagged. Every module the table held as None
("in no tagged release") ships in it -- verified against
`git show v3.5.0:src/...` -- so each entry now names 3.5.0. The tag test
also found three modules missing from the table, split out of the store
manager in core #659 (store_install, store_registry, store_update); they
first ship in v3.5.0 too.

The test tracks v3.5.0 as a tag. Its "an unreleased module is reported"
case used sports_helpers, which is released now; it plants a fake None
entry instead, and two new cases pin a 3.5.0 module against 3.4.0 and
3.5.0 floors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 6c19db1)
versions[0].ledmatrix_min_version and compatible_versions rise from 3.4.0
to 3.5.0, the first core release that ships src.common.espn_dates and
src.common.sports_helpers. The following commits delete the bundled
ufc_espn_dates.py and adopt core's shared sports mixins; the versions[]
notes and the CHANGELOG entry describe the whole series, including its one
behaviour change (the #599 idle wake-up). A floor raise is a minor bump, as
for the eight team scoreboards in #563. plugins.json regenerated with
update_registry.py.

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

ufc bundled a copy of core's src/common/espn_dates.py behind a guarded
import, for cores that predate the module. It now floors on 3.5.0, which
ships it, so the copy can never run: deleted (385 lines), and the guard in
sports.py and data_sources.py collapses to a plain
`from src.common.espn_dates import ESPN_MAX_LIMIT, fetch_espn_scoreboard`.

No behaviour change. On a 3.5.0 core the guard already took the core
branch, so the functions the plugin calls are the ones it called before.
The deleted copy matches core v3.5.0 once type annotations, typing imports
and cast() wrappers are set aside (core #661's typing-only edits), the
comparison #563's test_espn_dates_copies.py makes.

scripts/test_espn_dates_copies.py required all nine copies. It gains a
SUNSET_PLUGINS set -- the name #563 uses, so the two merge as a union --
holding ufc-scoreboard: its copy must stay deleted and no module of it may
import a bundled *_espn_dates; its fetches are still scanned for a direct
`dates` request. Checked by planting a copy and a bare import (both fail).
The test's remaining failure against core v3.5.0 (the eight remaining
copies differ from core in typing) is on origin/main too; #563 fixes it.

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

ufc's sports.py carried private copies of the helpers core 3.5.0 ships in
src.common.sports_helpers:

- module level: _MIN_WINDOW_DAYS, _MAX_WINDOW_DAYS, _clamp_window,
  _clamp_seconds, _logo_needs_refresh;
- on SportsCore: _mode_customization, _setting_int,
  _DWELL_REENTRY_GAP_SECONDS, _reset_dwell_on_reentry,
  _spread_weighted_order, _next_switch_index.

All deleted (the same treatment #563 gives the eight team scoreboards).
SportsCore now lists SportsHelpersMixin before ABC. The module-level names
are imported under their old private spellings, so sports.py's callers and
the tests that call sports._clamp_seconds / sports._clamp_window are
unchanged.

No behaviour change:
- check_sports_helpers_parity.py against core v3.5.0 found all 11 of ufc's
  copies identical to core's (normalised AST) before the deletion; it now
  reports the 13 names absent and the core module imported;
- no UFC subclass (MMA*, UFC*Manager) defines any of these names, so every
  lookup that reached SportsCore's copy now reaches the mixin's;
- the mixin adds _favorite_key, _odds_color and
  _upcoming_date_and_time_text, which nothing in ufc calls, and a class
  default favorite_rotation_boost = 1, which SportsCore.__init__ always
  overrides per instance and the one reader reads with getattr(..., 1);
- all 16 harness renders are byte-identical and the 23 plugin tests pass.

test_logo_placeholder_refresh.py required a local _logo_needs_refresh in
every logo loader. A loader may now import core's under the private name
instead (ADOPTED_RE, the pattern #563 uses); the byte-identity check skips
those and still pins the copies that remain.

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

ufc was the one scoreboard whose sports.py did not use core's
src.common.sports_shared (3.3.0). SportsCore, SportsLive and SportsRecent
now inherit SportsCoreSharedMixin, SportsLiveSharedMixin and
SportsRecentSharedMixin, in the order the eight team scoreboards use, and
their private copies are deleted (-442 lines):

- identical to core's as a docstring-stripped AST (15): SportsCore
  _should_log, _custom_scorebug_layout, cleanup, _game_divisions,
  _league_has_rankings, _normalise_divisions, _normalise_quality,
  _check_ranking_coverage, _favorites_first, _compose_selection;
  SportsLive _detect_stale_games, _note_live_fetch; SportsRecent __init__,
  _get_zero_clock_duration, _clear_zero_clock_tracking;
- same behaviour, different text (2): _get_weeks_data differs only by a
  leading `date_str = ""`, unreachable for the reason #563's c8224ec gives
  (nothing before the mixin's own assignment can raise the
  RequestException its handler catches); _round_robin_favorites names its
  matcher `matcher` instead of `team_in` and tests `is None` instead of
  `callable()` on the same getattr -- the same two closures chosen by the
  same condition;
- _idle_live_interval (1): the one behaviour change, below.

Kept, because core's behave differently: SportsCore._draw_scorebug_layout,
_get_layout_offset (core's routes through src.element_style) and
_draw_text_with_outline (core's takes element= and resolves colour from
it). They are defined on SportsCore itself, which precedes the mixin in
every MRO. SportsLiveSharedMixin and SportsRecentSharedMixin sit before
SportsCore in SportsLive / SportsRecent, and SportsCore defines none of
their names except __init__, where the Recent mixin's __init__ is the
deleted SportsRecent.__init__ verbatim and calls on to SportsCore's.

Behaviour change (the only one): the #599 idle wake-up. Before, ufc's
_idle_live_interval was the pre-#599 body, and update()'s
getattr(self, "_note_scheduled_start_candidate", None) found nothing, so
an idle league sat at live_idle_max_interval_seconds (900s default) and
could sleep through the first bout of a card. Now each live fetch offers
every non-live event's start_time_utc (MMA._extract_game_details sets it
from the card's date) to _note_scheduled_start_candidate, which keeps the
earliest future start as _next_scheduled_start_ts; _idle_live_interval
computes the same back-off as before and then clamps it: before a known
start, to max(update_interval, seconds until it); within 15 minutes after
it, to update_interval (live_update_interval, 30s by default); otherwise
unchanged. No request is added. Driven end to end through the real
UFCLiveManager.update with a scheduled card two hours out and 40 empty
looks: before 900/900/900/900s at T-2h/T-2m/T+5m/T+1h, after
900/120/30/900s. test_idle_league_backoff.py gains those cases; they fail
on the previous commit.

Checked otherwise unchanged:
- every attribute of all 12 UFC classes (SportsCore .. UFC*Manager)
  before and after: none lost; the rest resolve to core only where the
  body is the same (or is one of the three cases above); the mixins add
  attributes nothing in ufc reads (grep of every attribute access and
  getattr/hasattr string), apart from _note_scheduled_start_candidate;
- the three managers built through the plugin from the harness config
  hold the same instance attributes with the same values;
- all 16 harness renders byte-identical; 23 plugin tests pass.

check_sports_drift.py reports SportsCore._league_has_rankings back in
sync (ufc's was a divergent copy); baseline regenerated, dropping that
one entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
08-shared-sports-code.md counted nine bundled <sport>_espn_dates.py copies
and said every scoreboard imports src.common.sports_shared, which was not
true of ufc until now. The table row drops to the eight team scoreboards and
names ufc's sunset; the sports_helpers section records that ufc (1.16.0,
floor 3.5.0) inherits core's four mixins, what its sports.py keeps, and that
the adoption brought it the #599 kickoff clamp. "Floors at 3.4.0" becomes
"3.4.0 or above", the wording #563 uses too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tage1b-ufc-mixins

#563 landed while this branch was open. Its scripts/check_min_core_version.py
change is the commit this branch cherry-picked (d2c274b), so it merged
cleanly. Three files conflicted:

- scripts/test_espn_dates_copies.py: #563's version, with ufc-scoreboard
  added to its SUNSET_PLUGINS. All nine scoreboards are now in the set, so
  no bundled copy remains to compare; the docstring says so instead of "ufc
  still bundles one".
- scripts/test_logo_placeholder_refresh.py: #563's version. It already
  accepts a loader that imports core's helper under the private name
  (ADOPTED_RE), and requires every loader to either define it or import it:
  swapping ufc's import for a local stub fails two tests. With no plugin
  copy left, the behaviour tests run against core's function. Docstring no
  longer says ufc carries a copy.
- docs/plugin-development/08-shared-sports-code.md: merged by hand. The
  espn_dates row reads 0 copies (all nine sunset, 1a and 1b); "Converged at
  3.5.0" covers all nine and says what ufc also adopted; the sports_helpers
  and sunset-history text name ufc as done instead of pending.

Also fixed the remaining stale wording: CLAUDE.md non-negotiable #7 (all
nine scoreboards import espn_dates and inherit the core mixins; no "ufc
still bundles both"), check_sports_helpers_parity.py's docstring (no
"ufc still carries its eleven"), and check_selection_settings.py's
NOT_WIRED comment (ufc's selection code is now inherited from core's mixin).

ufc's versions[0] is still 1.16.0 / floor 3.5.0 and update_registry.py
--check passes. Against origin/main, both on core v3.5.0: the 16 ufc
harness renders are byte-identical and match the goldens on both trees;
run_plugin_tests.py for ufc plus the eight team scoreboards gives the same
per-test results (322 passed; the same two football failures on both);
all 52 scripts/test_*.py and check scripts pass on both trees.

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

coderabbitai Bot commented Sep 28, 2026

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 7500f54c-072f-4779-8d2f-11e6ef7c0333


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

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 0 complexity

Metric Results
Complexity 0

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
ChuckBuilds merged commit e6544c5 into main Sep 29, 2026
5 checks passed
@ChuckBuilds
ChuckBuilds deleted the claude/stage1b-ufc-mixins branch September 29, 2026 12:17
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