ci(sports): gate the copy-forward rule that keeps the scoreboards in step - #478
Merged
Conversation
…frame has_live_content() runs on the display path -- once per *frame* in Vegas mode. UFC guarded only the False branch: `should_log and not ufc_live` sat at the bottom of the function, but a second INFO line lived inside the `if live_games:` block above it with no guard at all. Whenever a card was live that call fired on every frame, roughly 50 lines a second on a rig measured at 50 fps. Because it sat outside the guard, every earlier throttle fix missed it -- baseball 1.20.4, football, and #308 for hockey/basketball/lacrosse all inspected the guard rather than the whole function body. It has been there since the plugin landed in #24. Both branches now share one throttle in baseball-scoreboard's shape: log when the answer changes, and re-log an unchanged answer once per 60s so a steady state stays visible. test_live_content_log_throttle.py is ported from baseball's, plus a case for the exact state that triggered it -- a live card with favorites configured and none of them fighting, where ufc_live is False but live_games is not empty. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014RRtqXDCnvnY6EQwhT5CV9
…step The scoreboards ship copies of sports.py, manager.py, game_renderer.py and scroll_display.py rather than sharing them, which is deliberate (CLAUDE.md non-negotiable #7). The rule that makes it safe -- port a fix to its siblings in the same PR -- was enforced only by whoever remembered. It was missed repeatedly. The has_live_content() throttle went into baseball (1.20.4), then football, then hockey/basketball/lacrosse (#308), and ufc was still unfixed months later, logging once per display frame during a live card. Two checks, both baselined against the current tree so they start green and fail only on something new. 1. Shape drift. A function whose copies all AGREE today must not start disagreeing. Bodies compare as normalised ASTs: docstrings dropped, numeric constants collapsed, and sport tokens folded so mlb_live and nfl_live are the same shape. Folding handles CamelCase as well as snake_case, which is load-bearing -- each plugin names its manager class after its own sport, so without it nothing in manager.py is ever compared to its siblings, and has_live_content() lives in manager.py. 46 functions are unanimous across five or more lineages; those are what this protects. 140 already differ and are recorded and ignored: the goal is not uniformity, it is that a shared fix reaches every sibling. 2. Unthrottled display-path logging. An info/warning call in has_live_content(), display() or get_vegas_content() with no throttle guard above it. Shape comparison cannot catch this: has_live_content() legitimately differs between sports -- one league versus three -- so it is already divergent everywhere and similarity has nothing to compare. This is a property check instead, and it is the one that catches the ufc bug. Verified end to end rather than assumed: - clean tree -> exit 0 - revert the ufc fix -> exit 1, naming manager.py:863 - edit one lineage's copy of _coerce_rgb (shared by 8) -> exit 1, naming hockey as the odd one out test_check_sports_drift.py is the gate's own regression suite, following the pattern module-collisions.yml already uses. This check reports by absence, so "looked and found nothing" and "did not really look" both print OK -- the first version of it recursed into class bodies with a bare walk() instead of `yield from`, inspected 16 functions instead of 386, and reported a clean result on code it never opened. The suite asserts it still detects, and that it is still reading a plausible amount of code. Depends on the ufc-scoreboard throttle fix: the baseline is generated with that fix in place, so reintroducing the bug fails the gate. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014RRtqXDCnvnY6EQwhT5CV9
Contributor
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: 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. Comment |
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 128 |
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The scoreboards ship copies of
sports.py,manager.py,game_renderer.pyandscroll_display.pyrather than sharing them. That is deliberate —CLAUDE.mdnon-negotiable #7. The rule that makes it safe is "a fix in one lineage must be ported to its siblings in the same PR", and until now it was enforced only by whoever remembered.It was missed repeatedly. The
has_live_content()throttle went into baseball (1.20.4), then football, then hockey/basketball/lacrosse (#308) — and ufc was still unfixed months later, logging once per display frame during a live card.What it checks
Both checks are baselined against the current tree, so they start green and fail only on something new.
1. Shape drift
A function whose copies all agree today must not start disagreeing. Bodies compare as normalised ASTs — docstrings dropped, numeric constants collapsed, sport tokens folded so
mlb_liveandnfl_liveare the same shape.CamelCase folding is load-bearing, not a nicety: each plugin names its manager class after its own sport, so without it nothing in
manager.pyis ever compared to its siblings — andhas_live_content()lives inmanager.py.46 functions are unanimous across 5+ lineages; those are what this protects. 140 already differ and are recorded and ignored. The goal is not uniformity — it is that a shared fix reaches every sibling.
2. Unthrottled display-path logging
An
info/warningcall inhas_live_content(),display()orget_vegas_content()with no throttle guard above it. These run once per frame in Vegas mode.Shape comparison cannot catch this:
has_live_content()legitimately differs between sports (one league versus three), so it is already divergent everywhere and a similarity check has nothing to compare against. This is a property check instead, and it is the one that catches the ufc bug.Calls inside
except:blocks and aterrorlevel are excluded — something going wrong is not the per-frame happy path.Verified, not assumed
ufc-scoreboard/manager.py:863_coerce_rgb(shared by 8)The gate's own regression suite
scripts/test_check_sports_drift.py, following the patternmodule-collisions.ymlalready establishes.This check reports by absence, so "looked and found nothing wrong" and "did not really look" both print OK and exit 0. That is not hypothetical: the first version recursed into class bodies with a bare
walk(child)instead ofyield from walk(child), collected 16 functions instead of 386, and reported a clean confident result on 96% of the code it never opened.So the suite asserts the gate still detects — including the exact nesting shape of the ufc bug, where a guard exists in the function but the offending call sits outside it — and that it is still reading a plausible amount of code.
🤖 Generated with Claude Code
https://claude.ai/code/session_014RRtqXDCnvnY6EQwhT5CV9