Skip to content

fix(football-scoreboard): give the possession ball room and let it scale - #337

Merged
ChuckBuilds merged 2 commits into
mainfrom
fix/football-possession-ball-room
Aug 30, 2026
Merged

fix(football-scoreboard): give the possession ball room and let it scale#337
ChuckBuilds merged 2 commits into
mainfrom
fix/football-possession-ball-room

Conversation

@ChuckBuilds

@ChuckBuilds ChuckBuilds commented Aug 29, 2026

Copy link
Copy Markdown
Owner

Summary

The possession football was placed a fixed 3px from the down & distance text and guarded only by ball_x_center > 0 — a check that a number had not come out negative, not that the ball fit anywhere in particular. On a 64px-wide panel it landed on the timeout bars; the guard could not see the right-hand edge at all, so home possession pushed it off a narrow panel; and on the adaptive 256×128 card it was drawn at x 262–274 of a 256px card, entirely off the edge.

The ball is now placed around the text rather than the other way round.

Type of change

  • Bug fix in an existing plugin

Plugin(s) affected

football-scoreboard

What changed

The ball's size is unchanged — a fixed 7×5 on the classic paths, px()-scaled radii on the adaptive card — and so is the wording beside it. Where it still fits, it lands on the same pixel it always did. Where a row genuinely cannot hold both (a 64×32 leaves ~30px between the timeout bars and the text alone wants 29) the down & distance wins and the ball is omitted, rather than the wording being shortened.

Fixed on all three paths that draw it: football.py's classic scorebug, GameRenderer's classic card (scroll mode), and its adaptive card.

Separately, the adaptive card drew the down & distance in PressStart2P, ~4× wider per character than the 4×6 face. On a 192×48 panel that rendered 3rd & 8 at KC 42 128px wide against the classic layout's 65px; on a 256×128 it reached the full 256px, clipping the line at both ends and leaving nowhere for the ball. That line alone now uses a dedicated ADAPTIVE_LADDER_DETAIL on the compact 4×6 face, both rungs verified crisp (measure_font_crispness == 0.0).

The clock, status band, records and dates are untouched — they keep ADAPTIVE_LADDER_TEXT and the face they had. Every recent and upcoming golden is pixel-identical to main; only the three live cards differ.

With the narrower face the yardage also fits where it previously did not. The long form was gated on display_width > 128 — a proxy for "is there room" that gets a 128×64 wrong, since the string is 65px in the 4×6 face and the free band there is wider. It is now preferred whenever it fits the space the timeout bars and records leave free, the same trade the classic layout already makes via _fit_text.

Shared-code note

ADAPTIVE_LADDER_TEXT and the possession helpers exist only in this plugin's game_renderer.py, so no lineage port is needed (docs/plugin-development/08-shared-sports-code.md). No shared-shape signature changed — that is why the down & distance is fitted inline rather than by adding a parameter to the shared _draw_bottom_center_adaptive.

Test plan

  • Safety harness: 24/24 across all eight default sizes × live/recent/upcoming.
  • Plugin suite: 26 passed, with the same two pre-existing environment failures as main (test_football_plugin.py wants a tty; test_timezone_resolution.py hits Windows cp1252 on a — both pass on CI).
  • check_module_collisions.py clean.
  • Adaptive goldens regenerated; recent/upcoming verified pixel-identical to origin/main.

New regression test: test_possession_ball_has_room.py

671 checks over three render paths × the harness's eight sizes × possession side × four down & distance strings (including the kickoff case) × records on and off. Each case renders twice — once capturing the ball's box, once with it suppressed — asserting the ball is on-panel, is exactly the size it always was, and covers no other element. Team logos are blanked, since the scorebug draws over them deliberately.

It pins the two properties that matter: the ball's size is unchanged, and the down & distance drawn is identical whether possession is home, away or unknown — so the ball can never be paid for out of the wording. Reintroducing a text reservation fails it.

Required for plugin changes

  • Bumped version (2.27.0 → 2.27.2), new entry at the top of versions
  • class_name / entry_point unchanged
  • No config keys changed
  • plugins.json regenerated with update_registry.py

Checklist

  • Commit message follows CONTRIBUTING.md
  • No secrets committed

Notes for reviewer

Rebased onto main after #336, which bumped the version and touched the same renderer.

Version coordination with #339: that PR takes football-scoreboard to 2.27.1, this one to 2.27.2, so they merge in either order without a re-bump. They touch different parts of game_renderer.py — imports there, the detail ladder here.

Known trade-off worth a second opinion: 128×64 and 128×96 now show the yardage but drop the ball. The limiting factor is the records — 10-5 in PressStart2P is 32px per corner, which eats the free band. Moving the records to the compact face would fit both, but that would change text this PR deliberately leaves alone, so I have not.

@coderabbitai

coderabbitai Bot commented Aug 29, 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: Pro Plus

Run ID: 15e519fb-0891-4c5e-91b8-97aafd02a036


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 Aug 29, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

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.

…dage

The possession football was placed a fixed 3px from the down & distance
text and guarded only by `ball_x_center > 0` -- a check that a number had
not come out negative, not that the ball fit anywhere in particular. On a
64px-wide panel it landed on the timeout bars; the guard could not see
the right-hand edge at all, so home possession pushed it off a narrow
panel; and on the adaptive 256x128 card it was drawn at x 262-274 of a
256px card, entirely off the edge.

The ball's size is unchanged -- a fixed 7x5 on the classic paths, px()
radii on the adaptive card -- and so is the wording beside it. The ball
is placed around the text now, never the other way round. Where a row
genuinely cannot hold both (a 64x32 leaves ~30px between the timeout
bars and the text alone wants 29) the down & distance wins and the ball
is omitted rather than the wording shortened.

Fixed on all three paths that draw it: football.py's classic scorebug,
GameRenderer's classic card (scroll mode), and its adaptive card.

Separately, the adaptive card drew the down & distance in PressStart2P,
~4x wider per character than the 4x6 face: on a 192x48 panel that
rendered "3rd & 8 at KC 42" 128px wide against the classic layout's
65px, and on a 256x128 it reached the full 256px, clipping the line at
both ends. That line alone now uses a dedicated ADAPTIVE_LADDER_DETAIL
on the compact 4x6 face, both rungs verified crisp
(measure_font_crispness == 0.0). The clock, status band, records and
dates keep ADAPTIVE_LADDER_TEXT and the face they had -- every recent
and upcoming golden is pixel-identical to main.

With the narrower face the yardage also fits where it previously did
not. The long form was gated on `display_width > 128`, a proxy for "is
there room" that got a 128x64 wrong: the string is 65px in the 4x6 face
and the free band there is wider. It is now preferred whenever it fits
the space the timeout bars and records leave free, the same trade the
classic layout already makes via _fit_text.

ADAPTIVE_LADDER_TEXT and the possession helpers exist only in this
plugin's game_renderer.py, so no lineage port is needed.

test_possession_ball_has_room.py pins the two properties that matter:
the ball is exactly the size it always was, and the down & distance
drawn is identical whether possession is home, away or unknown -- so the
ball can never be paid for out of the wording. 671 checks across three
render paths x eight harness sizes.

Harness 24/24. Plugin suite 26 passed with the same two pre-existing
environment failures as main.

Rebased onto main after #336, which bumped the version and touched the
same renderer; football goes to 2.27.2 since #339 takes 2.27.1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ChuckBuilds
ChuckBuilds force-pushed the fix/football-possession-ball-room branch from 59b9664 to ba0483f Compare August 30, 2026 03:59
Manifest resolved by splicing this branch's 2.27.2 release entry on top
of #335's 2.27.1; plugins.json regenerated by the pre-commit hook.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YF7Q48EYCCkU1Vs932uDY1
@ChuckBuilds
ChuckBuilds merged commit 6e8b863 into main Aug 30, 2026
4 checks passed
@ChuckBuilds
ChuckBuilds deleted the fix/football-possession-ball-room branch August 30, 2026 14:30
ChuckBuilds pushed a commit that referenced this pull request Aug 30, 2026
Football's manifest resolved by renumbering this branch's release entry
to 2.27.3, dated today, on top of main's 2.27.1 and 2.27.2 -- 2.27.1 was
claimed here before #335 took it. plugins.json regenerated by the hook.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YF7Q48EYCCkU1Vs932uDY1
ChuckBuilds added a commit that referenced this pull request Aug 30, 2026
main landed four commits that touch the same files as this branch:

  0855372 fix(scoreboards): import datetime, timezone and ZoneInfo in game_renderer (#339)
  6e8b863 fix(football-scoreboard): give the possession ball room (#337)
  85a5555 feat(football): favourites first, ranked others, and a rotation (#335)
  93bd554 feat(sports): apply the matchup separator settings to every mode (#336)

17 conflicts: eight sports.py, eight manifest.json, and the generated plugins.json.

sports.py -- every conflict was the same shape. #336 moved the upcoming card's
centre (matchup separator, or the date and time stacked, or nothing) out of the
inline scorebug code and into _draw_upcoming_center_switch, which is exactly
where this branch had scaled the stacked date/time offsets. Those edits are dead
weight now: SportsUpcoming sets _DRAWS_SCORE = False, so _time_font_size()
returns the un-grown 8 and max(7, 8-1) / max(9, 8+1) are the original 7 and 9.
Resolved to main's side throughout, then re-asserted _DRAWS_SCORE on the
SportsUpcoming that main's restructure left behind (football's #335 moved that
class's body, so the flag came away with the hunk).

Audited rather than assumed: diffing each resolved file against origin/main
leaves 200-300 added lines -- the helper block and its comments -- and 3 to 6
removed, each one a line this branch deliberately replaced (return fonts, the
1.5x max_width, score_y's -14 and -3, date_y's -7, football's two "00-00"
probes and its centre-gap return). No upcoming-card line is removed, so #336's
relocation is intact.

manifest.json -- main released the very version numbers this branch had claimed
and then some, so the branch entries could not be kept. Took main's manifest
whole, including its full versions[] history, and re-stacked this branch's entry
on top a minor above where main now sits: afl 1.16.0, baseball 1.34.0,
basketball 1.23.0, football 2.28.0, hockey 1.19.0, lacrosse 1.18.0, nrl 1.15.0,
soccer 2.18.0. CHANGELOG headings follow. plugins.json regenerated.

Verified against the merged origin/main across ten sizes (64x32, 128x32, 256x32,
64x64, 96x48, 192x48, 128x64, 256x64, 256x128, 384x96):

  * 240 harness renders, all PASS, no overflow or fill warnings.
  * All 80 upcoming renders byte-identical to main -- the screen draws no score
    and this branch leaves it alone.
  * Live and recent byte-identical at 64x32, 128x32 and 256x32 for every plugin,
    and football also at 64x64. Taller panels take the larger score.
  * Adaptive layout: only 192x48 live and recent move; the "VS" separator and
    every other size are byte-identical.
  * Plugin self-test failures identical to origin/main (11 pre-existing on this
    machine), including main's new test_switch_upcoming_center, which covers the
    region resolved to its side.
  * check_module_collisions clean across 43 plugins.

Co-Authored-By: Claude Opus 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