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

Sports: date on recent games, and stop drawing 88 config controls that cannot affect anything - #527

Merged
ChuckBuilds merged 3 commits into
fix/baseball-update-yields-within-budgetfrom
feat/sports-recent-date-and-inert-config
Sep 24, 2026
Merged

ChuckBuilds merged 3 commits into
fix/baseball-update-yields-within-budgetfrom
feat/sports-recent-date-and-inert-config

Conversation

@ChuckBuilds

@ChuckBuilds ChuckBuilds commented Sep 24, 2026 •

Copy link
Copy Markdown
Owner

Started as "why does the MLB config show FBS and FCS settings that only apply to NCAA College sports?" — and the answer turned out to apply to every scoreboard.

Stacked on #523. Base is fix/baseball-update-yields-within-budget, so the diff here is only this work. GitHub will retarget to main when #523 merges. Merge #523 first — baseball goes 1.47.1 → 1.48.0.

The date on recent games (baseball)

The full-screen Recent scoreboard already drew one — but raw, as game_date, so it was the only date on the board that ignored every date-format setting. A panel set to abbrev read "Sep 23" on its upcoming screen and "9/23" here. On a 512×64 panel:

   Final              Final
    6-1        ->      6-1
   9/23              Sep 22

It now goes through switch_date_format, which changes nothing on an untouched panel (that key defaults to numeric, and numeric returns the raw text). scroll_card.switch_recent_show_date is the off switch that never existed.

The scroll/Vegas card drew no date at all. Five of the eight siblings already do, so this closes a drift gap — but their placement doesn't transfer: they centre the recent score and use the free bottom edge, while this card puts the score at display_height - 14, so the free strip is above it.

      Final
TB   Sep 22   NY      <- recent_show_date, position "auto"
96-61  6-1  90-67

recent_date_position is own_row / top_line / auto; auto measures the fonts and panel in use rather than assuming a size, and an explicit own_row with no room draws nothing rather than overprinting the score.

88 config controls that cannot affect anything

divisions ranked show_ranking scroll_delay layout.ranking
soccer 11 11 10 — —
baseball 3 3 3 3 1
basketball 4 2 2 4 1
hockey 3 1 1 3 —
afl / nrl 2 / 2 2 / 2 1 / 1 1 / 1 —
football 1 1 1 2 —
lacrosse 2 — — 2 —
ufc — — — 1 —

other_games_divisions is the FBS/FCS taxonomy: ESPN publishes those rosters for college football alone, so everywhere else the lookup resolves nothing and the filter fails open. Soccer offered the checkboxes on the Champions League and the World Cup.

show_ranking was worse than inert — the rank badge replaces the record, so on a pollless league ticking it erased the records show_records was drawing:

show_ranking=False show_records=True  -> '91-68'
show_ranking=True  show_records=True  -> ''

Each setting is now "x-display": "hidden" — still declared, so a config already carrying it keeps validating and nothing is lost on upgrade. Verified: all nine schemas are valid Draft-07 and every hidden key still validates inside a defaults-derived config.

Which leagues have a poll is measured, not inferred

The code's heuristic is "college" in league or "ncaa" in league. I probed ESPN's /rankings for every scoreboard league on 2026-09-24 and it has a blind spot:

college-football 200, 125 teams
mens/womens-college-basketball 200, 50 each
mens/womens-college-hockey 200, 4 / 5
mens/womens-college-lacrosse 200, 28 / 33
college-baseball 404
mlb, nba, wnba, nfl, nhl, afl, rugby-league 3, soccer eng.1 404

college-baseball's slug is right — /scoreboard and /standings answer 200 on it — and out of season isn't the explanation, since college lacrosse and hockey are equally out of season and answer 200 with real poll blocks. So baseball narrows the heuristic with _NO_POLL_COLLEGE_LEAGUES, and NCAA Baseball's ranked default (which made every enabled rig fetch hourly for a poll that can't arrive, while failing open and filtering nothing) becomes any.

Left visible where a poll genuinely exists: football ncaa_fb, basketball ncaam/ncaaw, hockey and lacrosse ncaa_mens/ncaa_womens. Asserted, not assumed.

Also: UFC has a real poll (24 blocks, 133 entries) the heuristic gates out — but its ranks are keyed on athlete, not team, so _fetch_team_rankings would return {} anyway. Correctly not offered; supporting it needs a new extractor, not a gate change.

The fetch, and one new setting

_league_has_rankings gated the quality-filter call site but not the show_ranking ones, so ticking Show Ranking on a pollless league sent two requests an hour to endpoints that cannot answer. The gate moved to the top of _fetch_team_rankings in all nine, covering every caller.

milb.sport_ids (Triple-A / Double-A / High-A / Single-A) is now declared and forwarded. BaseMiLBManager has read it since it was written, but _adapt_config_for_manager is a whitelist that never carried the key — which is also why the schema had never declared it.

Verification

afl 26 · baseball 48 · basketball 37 · football 62(+1 skip, 1 pre-existing fail)
hockey 39 · lacrosse 29 · nrl 21 · soccer 38 · ufc 20
31 repo scripts, 0 failing
drift OK · collisions OK · registry PASS · version bump PASS (all nine)
check_plugin 24/24 for baseball, in all three date positions

Two things worth knowing about that evidence:

  • check_plugin's mlb_recent render is empty (27 lit pixels — no finished game in its mock slate), so that PASS proves nothing about the recent card. I rendered the card directly at all eight harness sizes instead, bounds-checked with records and odds on, and confirmed both paths on a real 512×64 panel.
  • football's 1 failure is pre-existing. test_espn_date_ranges.py::test_the_lookback_window_recovers_from_a_rejected_range passes in isolation and reproduces identically (62/1/1) with this branch's football edits reverted. Not caused here.

scripts/test_inert_settings_are_hidden.py now carries the league→poll map and fails CI if any lineage regains a dead control — one edit when ESPN changes, instead of nine schemas.

Deliberately not done

  • The records-erasure. On a pollless league show_ranking erases the record rather than falling back, in every lineage except basketball (whose _get_team_annotation already returns the record). Hiding and gating covers fresh configs; one saved with show_ranking: true still hits it. Porting basketball's fallback would also change what a poll-backed college board draws for an unranked team — blank today, the record after — so it's a display decision, not cleanup. Documented in the new test file.
  • The recent-card date for hockey and lacrosse, and formatting/toggles for the five siblings that draw it raw and unconditional.

🤖 Generated with Claude Code

https://claude.ai/code/session_01ET8e5weDrb5Ju5QTKLU7zh

Summary by CodeRabbit

  • New Features
    • Recent baseball games can show their date on scroll/Vegas cards or the full-screen scoreboard, with configurable formatting and placement.
    • MiLB users can choose which minor-league levels to include; all levels remain selected by default.
  • Improvements
    • Configuration forms now hide controls that do not apply to a league, while keeping saved settings compatible.
    • Scoreboards no longer request rankings for leagues without ranking data, preventing empty ranking badges from replacing game records.

…hing (1.48.0)

Adds the date of a finished game to both Recent displays, and stops drawing
five settings that are inert in every baseball league.

THE DATE. The full-screen (switch) Recent scoreboard has drawn one along its
bottom edge since it was written, but it drew game_date raw -- the "9/23" the
extractor emits -- so it was the one date on the board that ignored every
date-format setting: a panel set to "abbrev" read "Sep 23" on its upcoming
screen and "9/23" here. It now goes through switch_date_format like the
upcoming screen, which changes nothing on an untouched panel because that key
defaults to "numeric" and numeric returns the raw text. New
scroll_card.switch_recent_show_date (default true) is the off switch that did
not exist.

The scroll and Vegas Recent card drew no date at all. Five of the eight sibling
scoreboards already draw one there, so this closes a drift gap -- but their
placement does not transfer: they centre the recent score and leave the bottom
edge free, while this card puts the score at display_height-14, so the free
strip is above it. scroll_card.recent_show_date (default false) adds the date,
formatted by the shared date_format; scroll_card.recent_date_position picks
own_row, top_line, or auto, which measures the fonts and panel in use rather
than assuming a size. An explicit own_row with no room draws nothing rather
than overprinting the score.

Verified at all eight harness sizes in all three positions. check_plugin's own
mlb_recent render is empty (27 lit pixels -- no finished game in its mock
slate), so that PASS proves nothing about this card; it was rendered directly
instead, bounds-checked with records and odds on. Both paths also confirmed on
a 512x64 panel.

INERT SETTINGS, now "x-display": "hidden" -- still declared, so a config
already carrying one keeps validating and nothing is lost on upgrade:

  * other_games_divisions (all three leagues) -- ESPN publishes FBS/FCS group
    rosters for college-football alone, so the lookup resolves nothing and the
    filter fails open.
  * other_games_min_quality and show_ranking (all three) -- no baseball league
    publishes a poll. NCAA Baseball included: measured 2026-09-24,
    baseball/college-baseball/rankings answers 404 while /scoreboard and
    /standings on the same slug answer 200. Out of season is not the
    explanation -- men's college lacrosse and hockey are equally out of season
    and both answer 200 with real poll blocks. show_ranking was worse than
    inert, because the rank badge replaces the record, so ticking it erased
    the records show_records was drawing.
  * scroll_delay -- its own description has read "ignored" for releases.
  * customization.layout.ranking -- no reader; layout.record positions the
    badge.

THE FETCH. _league_has_rankings gated the quality-filter call site but not the
two show_ranking ones. The gate moved to the top of _fetch_team_rankings,
covering every caller at once, and is narrowed by one measured exception
(_NO_POLL_COLLEGE_LEAGUES) so NCAA Baseball's old "ranked" default stops
requesting too. Its default is now "any" -- what every board already showed,
since "ranked" with no poll lets everything through.

MILB LEVELS. milb.sport_ids (Triple-A / Double-A / High-A / Single-A) is now
declared and forwarded. BaseMiLBManager has read it since it was written, but
_adapt_config_for_manager is a whitelist that never carried the key, so no
value a user could write ever arrived -- which is also why the schema had
never declared it. Empty or unusable input falls back to all four levels.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ET8e5weDrb5Ju5QTKLU7zh
The same audit that produced baseball 1.48.0, applied to the other eight
lineages. The sports scoreboards are copies of each other, and a schema is
easier to copy than to re-derive, so three settings were carried into plugins
whose leagues cannot use them.

HIDDEN -- "x-display": "hidden", so each stays declared and a config already
carrying it keeps validating; only the form control goes away:

  soccer 32, basketball 13, hockey 8, afl 6, nrl 6, football 5, lacrosse 4,
  ufc 1.

  * other_games_divisions (25) -- the FBS/FCS/Other checkboxes. ESPN publishes
    those group rosters for college football alone (_DIVISION_GROUPS_BY_LEAGUE,
    identical in all nine copies), so everywhere else the lookup resolves
    nothing and the filter fails open. Soccer offered them on the Champions
    League and the World Cup.
  * other_games_min_quality (19) -- "ranked" needs a poll; without one the
    check fails open and the setting has one meaningful value.
  * show_ranking (16) -- worse than inert. The rank badge REPLACES the record,
    so on a pollless league ticking it erased the records show_records was
    drawing.
  * scroll_delay (14) -- every copy's own description has read "Kept so saved
    configs still load; ignored" for releases. ufc reads it into a local and
    discards it, with a comment saying so.
  * customization.layout.ranking (1, basketball) -- no reader.

WHICH LEAGUES HAVE A POLL IS MEASURED, not inferred from the
"college"/"ncaa" heuristic. ESPN's /rankings was probed for every scoreboard
league on 2026-09-24:

  college-football 200 (125 teams)      mlb, nba, wnba, nfl, nhl    404
  mens/womens-college-basketball 200 (50 each)   minor-league-baseball  400
  mens/womens-college-hockey     200 (4 / 5)     afl, rugby-league 3    404
  mens/womens-college-lacrosse   200 (28 / 33)   soccer eng.1           404
  college-baseball               404  <-- the heuristic's blind spot

Left visible accordingly: football ncaa_fb, basketball ncaam/ncaaw, hockey and
lacrosse ncaa_mens/ncaa_womens. Asserted, not assumed -- see
scripts/test_inert_settings_are_hidden.py.

THE FETCH. _league_has_rankings gated the quality-filter call site but not the
show_ranking ones, so ticking Show Ranking on a pollless league sent two
requests an hour to endpoints that cannot answer. The gate moved to the top of
_fetch_team_rankings in all eight, covering every caller and unable to drift
apart again. lacrosse and ufc serve no pollless league, so the port changes
nothing there; it is carried because sports.py is copied, not shared, and a fix
living in eight of nine copies is how the last one gets missed.

NOT DONE, deliberately: on a pollless league an empty rank table makes
show_ranking ERASE the record rather than fall back to it, in every lineage
except basketball, whose _get_team_annotation already returns the record.
Hiding and gating covers anyone configuring fresh, but a config saved with
show_ranking: true still hits it. Porting basketball's fallback would also
change what a poll-backed college board draws for an unranked team (blank
today, the record after), so it is a display decision rather than cleanup.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ET8e5weDrb5Ju5QTKLU7zh
scripts/test_inert_settings_are_hidden.py encodes the measured league->poll map
and fails if any of the nine lineages draws other_games_divisions outside
college football, or other_games_min_quality / show_ranking on a league with no
poll, or scroll_delay, or customization.layout.ranking. It also checks the
other half of the promise -- that the poll-backed blocks are still offered --
that every hidden property explains itself, and that _fetch_team_rankings
consults _league_has_rankings in every copy.

Encoding the map here rather than in nine schemas means the next league ESPN
adds or drops is one edit, and a schema that quietly regains a dead control
fails CI instead of shipping.

Two fixes it flushed out:

  * test_poll_choice.py pinned its probe to probe.league = <plugin id>, which
    is not a league name any lineage ever sees. Harmless while nothing
    consulted it; now that _fetch_team_rankings is gated, a probe claiming to
    be in league "baseball-scoreboard" short-circuits and the badge table comes
    back empty. It now uses a synthetic "ncaa-poll-probe" -- deliberately not a
    real slug, since "college-baseball" would also fail against baseball's
    measured exception.

  * sports_drift_baseline.json records SportsCore._league_has_rankings as
    intended divergence. baseball narrows the shared heuristic by one league;
    ufc carries an inline copy of the original. The difference is deliberate
    and documented at both sites.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ET8e5weDrb5Ju5QTKLU7zh
@coderabbitai

coderabbitai Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: f98b690f-0fea-4b9e-a641-3e6f51aa5f46

📥 Commits

Reviewing files that changed from the base of the PR and between 81c2f50 and 973a137.

📒 Files selected for processing (56)
  • plugins.json
  • plugins/afl-scoreboard/CHANGELOG.md
  • plugins/afl-scoreboard/README.md
  • plugins/afl-scoreboard/config_schema.json
  • plugins/afl-scoreboard/manifest.json
  • plugins/afl-scoreboard/sports.py
  • plugins/baseball-scoreboard/CHANGELOG.md
  • plugins/baseball-scoreboard/README.md
  • plugins/baseball-scoreboard/config_schema.json
  • plugins/baseball-scoreboard/game_renderer.py
  • plugins/baseball-scoreboard/manager.py
  • plugins/baseball-scoreboard/manifest.json
  • plugins/baseball-scoreboard/milb_managers.py
  • plugins/baseball-scoreboard/sports.py
  • plugins/baseball-scoreboard/test_empty_rankings_are_still_cached.py
  • plugins/baseball-scoreboard/test_inert_settings_are_hidden.py
  • plugins/baseball-scoreboard/test_milb_sport_ids.py
  • plugins/baseball-scoreboard/test_recent_game_date.py
  • plugins/basketball-scoreboard/CHANGELOG.md
  • plugins/basketball-scoreboard/README.md
  • plugins/basketball-scoreboard/config_schema.json
  • plugins/basketball-scoreboard/manifest.json
  • plugins/basketball-scoreboard/sports.py
  • plugins/football-scoreboard/CHANGELOG.md
  • plugins/football-scoreboard/README.md
  • plugins/football-scoreboard/config_schema.json
  • plugins/football-scoreboard/manifest.json
  • plugins/football-scoreboard/sports.py
  • plugins/hockey-scoreboard/CHANGELOG.md
  • plugins/hockey-scoreboard/README.md
  • plugins/hockey-scoreboard/config_schema.json
  • plugins/hockey-scoreboard/manifest.json
  • plugins/hockey-scoreboard/sports.py
  • plugins/lacrosse-scoreboard/CHANGELOG.md
  • plugins/lacrosse-scoreboard/README.md
  • plugins/lacrosse-scoreboard/config_schema.json
  • plugins/lacrosse-scoreboard/manifest.json
  • plugins/lacrosse-scoreboard/sports.py
  • plugins/nrl-scoreboard/CHANGELOG.md
  • plugins/nrl-scoreboard/README.md
  • plugins/nrl-scoreboard/config_schema.json
  • plugins/nrl-scoreboard/manifest.json
  • plugins/nrl-scoreboard/sports.py
  • plugins/soccer-scoreboard/CHANGELOG.md
  • plugins/soccer-scoreboard/README.md
  • plugins/soccer-scoreboard/config_schema.json
  • plugins/soccer-scoreboard/manifest.json
  • plugins/soccer-scoreboard/sports.py
  • plugins/ufc-scoreboard/CHANGELOG.md
  • plugins/ufc-scoreboard/README.md
  • plugins/ufc-scoreboard/config_schema.json
  • plugins/ufc-scoreboard/manifest.json
  • plugins/ufc-scoreboard/sports.py
  • scripts/sports_drift_baseline.json
  • scripts/test_inert_settings_are_hidden.py
  • scripts/test_poll_choice.py
 _________________________________________________________________________________________________________________
< Iterate the schedule with the code. Use experience you gain as you implement to refine the project time scales. >
 -----------------------------------------------------------------------------------------------------------------
  \
   \   \
        \ /\
        ( )
      .( o ).
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

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 153 complexity

Metric Results
Complexity 153

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 9e78866 into fix/baseball-update-yields-within-budget Sep 24, 2026
3 of 5 checks passed
ChuckBuilds added a commit that referenced this pull request Sep 25, 2026
…etch gated (9 plugins) (#523)

* fix(baseball): return update() inside the slot the core grants it

Found soaking on a 256x64 rig: every cold-cache start logged
"Plugin baseball-scoreboard update() timed out" at ERROR.

At startup the display controller hands each plugin whatever is left of a
shared deadline (display_controller._update_plugins), so the slot shrinks as
it works down the plugin list. Baseball's slot was 15.4s and 15.96s on two
consecutive boots -- the varying value is what gave it away as a share of a
deadline rather than a fixed timeout.

This plugin waited 25s on its six parallel managers. A 25s wait inside a
~15s slot can only ever end one way: the core kills the call and logs an
ERROR, and this method's own timeout branch -- the one that names *which*
managers are slow -- never runs. The plugin's timeout was longer than the
budget it was given, so it could never use it.

The wait is now 10s, in a named constant documented against the core's
budget, with a test asserting it stays under the smallest slot measured on
hardware and is not re-inlined at the call site.

Nothing was lost before and nothing is lost now, which is what makes
yielding early safe: shutdown(cancel_futures=True) only cancels managers
that have not started, and all six start immediately, so in-flight fetches
run to completion and populate their caches either way. A plugin cut off at
startup is also immediately due again, so the scheduled tick picks it up --
the core's own docstring says as much. The difference is that returning
under our own steam turns a core-level ERROR into a plugin WARNING naming
the slow managers.

The expensive part of a cold start is the NCAA baseball season fetch --
5,500 events, ~10s on the rig -- which the background service already runs
off-thread; this does not change that.

Verified on the rig: baseball 1.47.0 plus this change, no ERROR on a warm
restart, and the card path confirmed working end-to-end on a live MLB game
(headshot prefetch wrote four new 192x192 files, zero draw errors).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RRtqXDCnvnY6EQwhT5CV9

* Sports: date on recent games, and stop drawing 88 config controls that cannot affect anything (#527)

* feat(baseball): date on recent games; hide config that cannot do anything (1.48.0)

Adds the date of a finished game to both Recent displays, and stops drawing
five settings that are inert in every baseball league.

THE DATE. The full-screen (switch) Recent scoreboard has drawn one along its
bottom edge since it was written, but it drew game_date raw -- the "9/23" the
extractor emits -- so it was the one date on the board that ignored every
date-format setting: a panel set to "abbrev" read "Sep 23" on its upcoming
screen and "9/23" here. It now goes through switch_date_format like the
upcoming screen, which changes nothing on an untouched panel because that key
defaults to "numeric" and numeric returns the raw text. New
scroll_card.switch_recent_show_date (default true) is the off switch that did
not exist.

The scroll and Vegas Recent card drew no date at all. Five of the eight sibling
scoreboards already draw one there, so this closes a drift gap -- but their
placement does not transfer: they centre the recent score and leave the bottom
edge free, while this card puts the score at display_height-14, so the free
strip is above it. scroll_card.recent_show_date (default false) adds the date,
formatted by the shared date_format; scroll_card.recent_date_position picks
own_row, top_line, or auto, which measures the fonts and panel in use rather
than assuming a size. An explicit own_row with no room draws nothing rather
than overprinting the score.

Verified at all eight harness sizes in all three positions. check_plugin's own
mlb_recent render is empty (27 lit pixels -- no finished game in its mock
slate), so that PASS proves nothing about this card; it was rendered directly
instead, bounds-checked with records and odds on. Both paths also confirmed on
a 512x64 panel.

INERT SETTINGS, now "x-display": "hidden" -- still declared, so a config
already carrying one keeps validating and nothing is lost on upgrade:

  * other_games_divisions (all three leagues) -- ESPN publishes FBS/FCS group
    rosters for college-football alone, so the lookup resolves nothing and the
    filter fails open.
  * other_games_min_quality and show_ranking (all three) -- no baseball league
    publishes a poll. NCAA Baseball included: measured 2026-09-24,
    baseball/college-baseball/rankings answers 404 while /scoreboard and
    /standings on the same slug answer 200. Out of season is not the
    explanation -- men's college lacrosse and hockey are equally out of season
    and both answer 200 with real poll blocks. show_ranking was worse than
    inert, because the rank badge replaces the record, so ticking it erased
    the records show_records was drawing.
  * scroll_delay -- its own description has read "ignored" for releases.
  * customization.layout.ranking -- no reader; layout.record positions the
    badge.

THE FETCH. _league_has_rankings gated the quality-filter call site but not the
two show_ranking ones. The gate moved to the top of _fetch_team_rankings,
covering every caller at once, and is narrowed by one measured exception
(_NO_POLL_COLLEGE_LEAGUES) so NCAA Baseball's old "ranked" default stops
requesting too. Its default is now "any" -- what every board already showed,
since "ranked" with no poll lets everything through.

MILB LEVELS. milb.sport_ids (Triple-A / Double-A / High-A / Single-A) is now
declared and forwarded. BaseMiLBManager has read it since it was written, but
_adapt_config_for_manager is a whitelist that never carried the key, so no
value a user could write ever arrived -- which is also why the schema had
never declared it. Empty or unusable input falls back to all four levels.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ET8e5weDrb5Ju5QTKLU7zh

* fix(sports): stop drawing 75 config controls that cannot affect anything

The same audit that produced baseball 1.48.0, applied to the other eight
lineages. The sports scoreboards are copies of each other, and a schema is
easier to copy than to re-derive, so three settings were carried into plugins
whose leagues cannot use them.

HIDDEN -- "x-display": "hidden", so each stays declared and a config already
carrying it keeps validating; only the form control goes away:

  soccer 32, basketball 13, hockey 8, afl 6, nrl 6, football 5, lacrosse 4,
  ufc 1.

  * other_games_divisions (25) -- the FBS/FCS/Other checkboxes. ESPN publishes
    those group rosters for college football alone (_DIVISION_GROUPS_BY_LEAGUE,
    identical in all nine copies), so everywhere else the lookup resolves
    nothing and the filter fails open. Soccer offered them on the Champions
    League and the World Cup.
  * other_games_min_quality (19) -- "ranked" needs a poll; without one the
    check fails open and the setting has one meaningful value.
  * show_ranking (16) -- worse than inert. The rank badge REPLACES the record,
    so on a pollless league ticking it erased the records show_records was
    drawing.
  * scroll_delay (14) -- every copy's own description has read "Kept so saved
    configs still load; ignored" for releases. ufc reads it into a local and
    discards it, with a comment saying so.
  * customization.layout.ranking (1, basketball) -- no reader.

WHICH LEAGUES HAVE A POLL IS MEASURED, not inferred from the
"college"/"ncaa" heuristic. ESPN's /rankings was probed for every scoreboard
league on 2026-09-24:

  college-football 200 (125 teams)      mlb, nba, wnba, nfl, nhl    404
  mens/womens-college-basketball 200 (50 each)   minor-league-baseball  400
  mens/womens-college-hockey     200 (4 / 5)     afl, rugby-league 3    404
  mens/womens-college-lacrosse   200 (28 / 33)   soccer eng.1           404
  college-baseball               404  <-- the heuristic's blind spot

Left visible accordingly: football ncaa_fb, basketball ncaam/ncaaw, hockey and
lacrosse ncaa_mens/ncaa_womens. Asserted, not assumed -- see
scripts/test_inert_settings_are_hidden.py.

THE FETCH. _league_has_rankings gated the quality-filter call site but not the
show_ranking ones, so ticking Show Ranking on a pollless league sent two
requests an hour to endpoints that cannot answer. The gate moved to the top of
_fetch_team_rankings in all eight, covering every caller and unable to drift
apart again. lacrosse and ufc serve no pollless league, so the port changes
nothing there; it is carried because sports.py is copied, not shared, and a fix
living in eight of nine copies is how the last one gets missed.

NOT DONE, deliberately: on a pollless league an empty rank table makes
show_ranking ERASE the record rather than fall back to it, in every lineage
except basketball, whose _get_team_annotation already returns the record.
Hiding and gating covers anyone configuring fresh, but a config saved with
show_ranking: true still hits it. Porting basketball's fallback would also
change what a poll-backed college board draws for an unranked team (blank
today, the record after), so it is a display decision rather than cleanup.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ET8e5weDrb5Ju5QTKLU7zh

* test(sports): enforce that no scoreboard draws an inert control

scripts/test_inert_settings_are_hidden.py encodes the measured league->poll map
and fails if any of the nine lineages draws other_games_divisions outside
college football, or other_games_min_quality / show_ranking on a league with no
poll, or scroll_delay, or customization.layout.ranking. It also checks the
other half of the promise -- that the poll-backed blocks are still offered --
that every hidden property explains itself, and that _fetch_team_rankings
consults _league_has_rankings in every copy.

Encoding the map here rather than in nine schemas means the next league ESPN
adds or drops is one edit, and a schema that quietly regains a dead control
fails CI instead of shipping.

Two fixes it flushed out:

  * test_poll_choice.py pinned its probe to probe.league = <plugin id>, which
    is not a league name any lineage ever sees. Harmless while nothing
    consulted it; now that _fetch_team_rankings is gated, a probe claiming to
    be in league "baseball-scoreboard" short-circuits and the badge table comes
    back empty. It now uses a synthetic "ncaa-poll-probe" -- deliberately not a
    real slug, since "college-baseball" would also fail against baseball's
    measured exception.

  * sports_drift_baseline.json records SportsCore._league_has_rankings as
    intended divergence. baseball narrows the shared heuristic by one league;
    ufc carries an inline copy of the original. The difference is deliberate
    and documented at both sites.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ET8e5weDrb5Ju5QTKLU7zh

---------

Co-authored-by: Claude <noreply@anthropic.com>

* Keep the scoreboard manifests pure ASCII

Five manifests picked up raw UTF-8 (em dashes, the ▲/▼ indicator) in
older release notes when this branch re-serialised them; main stores
those characters as \u escapes. The core's load_manifest() opens the file
in the platform encoding, which is cp1252 on Windows, so a raw UTF-8
byte aborts a harness run there. Escaped back; the parsed JSON is
unchanged.

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

* baseball test: stop shadowing the built-in TimeoutError (Codacy)

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
@ChuckBuilds
ChuckBuilds deleted the feat/sports-recent-date-and-inert-config branch September 29, 2026 16:46
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.

2 participants