fix(scoreboards): announce the last-ditch font fallback - #412
Merged
Conversation
_load_custom_font's final fallback caught every exception, passed, and returned PIL's built-in face. That face does not match the panel's pixel grid, so a missing or unreadable font file rendered as fuzzy text with nothing in the log to explain it -- it reads as a rendering bug rather than a missing file. Both paths now warn: the one where the fallback font itself fails to load (naming the path and the exception), and the one that reaches PIL's built-in face regardless. Nothing renders differently. This only makes an existing failure visible. Scope: an audit of the font and odds paths across all eight scoreboards found 77 broad handlers, of which 74 already log. These two were the only ones that were both broad and silent. football/game_renderer.py keeps its silent BDF handler deliberately -- its comment states the warning is deferred to the downstream fallback, and one is emitted there.
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: Team 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 | 0 |
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
pushed a commit
that referenced
this pull request
Sep 4, 2026
Addresses three CodeRabbit findings on #417, all of them real. The first is the one that mattered: ledmatrix-flights had six anti-aliased draws this PR had already claimed to fix. None of its ten Draw sites calls .text() itself -- every renderer hands the Draw to _draw_centered()/_draw(), which do. The first version of this gate matched `<var>.text(` file-wide and caught them; tightening it to same-scope AST matching to cut false positives threw the real findings away and reported the file clean. The runtime probe missed them too, because the harness never renders those flight paths. So the gate now resolves, to a fixpoint, which functions draw text on a parameter, and treats a Draw handed to one of those as text-rendering. That sits between the file-wide regex (130 findings, mostly noise) and same-scope matching (missed real ones): an overlay Draw passed to a compositing helper is still ignored, while _draw_centered(draw, ...) counts. Two smaller gate defects, also reported and also real: * any `.fontmode` assignment satisfied the check, so `fontmode = "L"` -- the anti-aliasing default -- would have passed. Now only the constant "1". * ast.walk() descended into nested scopes and ignored statement order, so a fontmode set *before* its Draw() counted. Now scoped and ordered. Each is mutation-tested: removing a hand-off fontmode, setting it to "L", or moving it above its Draw() each make the gate fail. That found 40 further sites in 13 plugins, including overlay and celebration paths in all eight scoreboards that the harness never renders. Versions are picked above every number claimed by #409 and #412; football takes 3.4.3 so #424 keeps 3.5.0. Merge order: #409, #412, this, then #424. Verified: 246 passed / 2 skipped / 0 failed, 72-card scroll guard passes, all repo gates pass, and the runtime probe still reports 0 anti-aliased text draws across 31 plugins at every panel size. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014RRtqXDCnvnY6EQwhT5CV9
Both sides had bumped basketball to 1.29.2 and soccer to 2.24.2 -- main from the docs PRs that landed since, this branch from the font-fallback warning. Kept main's entries exactly as merged, including their changes arrays, and re-filed this branch's change as 1.29.3 and 2.24.3 above them. No released entry is rewritten and the version gate still sees a bump. plugins.json regenerated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ChuckBuilds
added a commit
that referenced
this pull request
Sep 4, 2026
Seventeen manifests conflicted. The docs pass has been landing on main one plugin at a time, and this branch bumped the same manifests for the 1-bit text fix, so most collided on the version number. Resolved from main's history in every case, with this branch's entry added above it. Matching by version number alone would have been wrong: on seven plugins -- baseball, elections, leaderboard, masters, mqtt, ufc and youtube-stats -- both sides had numbered a DIFFERENT release the same, so main's docs entry and this branch's text fix shared a number. Dropping either would have lost a release note for a change that shipped, so both are kept and ours takes the next free number. Four numbers were skipped because other open PRs already claim them: basketball 1.29.3 and soccer 2.24.3 (#412), football 3.5.0 (#424) and lacrosse 1.24.3 (#428). Those four can now merge in any order relative to this one instead of colliding again on whichever lands second. Every manifest re-checked: valid JSON, version matches versions[0], no duplicates, descending order. plugins.json regenerated and in sync with all 43 manifests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ChuckBuilds
added a commit
that referenced
this pull request
Sep 4, 2026
Second pass: #412, #424 and #428 merged since the last one, so the same manifests collided again. Four manifests resolved from main's history with this branch's entry above it. Football needed care -- main reached 3.5.0 from #424 while this branch sat at 3.4.3, so taking the next free number would have sorted our entry into the middle of the list rather than the top. Ours now takes the next number above whatever main has reached, which is 3.5.1. The module-collisions workflow conflicted because both sides added a guard: this branch's 1-bit text check and main's scroll-reachability check. They are independent, so both the path filters and both steps are kept. All 43 manifests re-validated -- valid JSON, version matches versions[0], no duplicates, descending order -- and plugins.json is in sync with every one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ChuckBuilds
added a commit
that referenced
this pull request
Sep 5, 2026
…417) * fix(plugins): draw text 1-bit, so glyphs stay crisp on the LED grid An LED panel has no partial brightness. PIL defaults ImageDraw's fontmode to "L", which anti-aliases TrueType glyphs into a grey fringe the panel can only round off -- a 4px glyph arrives smeared into 3px. Measured at draw time rather than grepped, because the source-level signal misleads in both directions. A missing fontmode is often harmless: birdnet-go and youtube-stats draw PressStart2P at its native 8px, which emits no partial pixels whatever the mode. A present one is not coverage: ledmatrix-flights set it at 3 of its 12 Draw() sites, and the one that actually blurred was not among them. pomodoro-timer looked like the worst offender and was already correct -- it masks, thresholds at >=128 and paints flat, so only its intermediate mask was ever anti-aliased. Hooking ImageDraw.text across 31 plugins x 8 panel sizes found 18 genuinely anti-aliased sites in 9 plugins, all of them 4x6-font.ttf at 6 (74% of lit pixels at partial coverage) or PressStart2P off its 8px grid. The other 52 sites here were clean only by luck: their faces happen to sit on the pixel grid at the size currently configured. Font sizes are user settings, so that is not a property to rely on -- typing 10 into the web UI reintroduces the blur. The setting belongs on every draw that renders text. Excluded: the offline asset generators (download_assets.py, logo_downloader.py and friends). They bake placeholder logos on a developer machine, not text on the panel, and logo resampling is a separately-decided question. Not render-verified: mqtt-notifications, nfl-draft, on-air and static-image render only with live data the harness has none of. They are fixed statically, on the same reasoning, but no render proves it. scripts/test_pixel_perfect_text.py keeps it from coming back, and is wired into the Plugin Structure workflow. It is AST-scoped, not a file-wide regex: a Draw() is reported only where that binding is used for .text() in the same function, which is the difference between 43 real findings and 130 mostly spurious ones. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014RRtqXDCnvnY6EQwhT5CV9 * fix(plugins): catch draws handed to a text-drawing helper Addresses three CodeRabbit findings on #417, all of them real. The first is the one that mattered: ledmatrix-flights had six anti-aliased draws this PR had already claimed to fix. None of its ten Draw sites calls .text() itself -- every renderer hands the Draw to _draw_centered()/_draw(), which do. The first version of this gate matched `<var>.text(` file-wide and caught them; tightening it to same-scope AST matching to cut false positives threw the real findings away and reported the file clean. The runtime probe missed them too, because the harness never renders those flight paths. So the gate now resolves, to a fixpoint, which functions draw text on a parameter, and treats a Draw handed to one of those as text-rendering. That sits between the file-wide regex (130 findings, mostly noise) and same-scope matching (missed real ones): an overlay Draw passed to a compositing helper is still ignored, while _draw_centered(draw, ...) counts. Two smaller gate defects, also reported and also real: * any `.fontmode` assignment satisfied the check, so `fontmode = "L"` -- the anti-aliasing default -- would have passed. Now only the constant "1". * ast.walk() descended into nested scopes and ignored statement order, so a fontmode set *before* its Draw() counted. Now scoped and ordered. Each is mutation-tested: removing a hand-off fontmode, setting it to "L", or moving it above its Draw() each make the gate fail. That found 40 further sites in 13 plugins, including overlay and celebration paths in all eight scoreboards that the harness never renders. Versions are picked above every number claimed by #409 and #412; football takes 3.4.3 so #424 keeps 3.5.0. Merge order: #409, #412, this, then #424. Verified: 246 passed / 2 skipped / 0 failed, 72-card scroll guard passes, all repo gates pass, and the runtime probe still reports 0 anti-aliased text draws across 31 plugins at every panel size. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014RRtqXDCnvnY6EQwhT5CV9 --------- Co-authored-by: Claude <noreply@anthropic.com>
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.
_load_custom_font's final fallback caught every exception,passed, and returned PIL's built-in face.That face doesn't match the panel's pixel grid, so a missing or unreadable font file rendered as fuzzy text with nothing in the log to explain it — it reads as a rendering bug rather than a missing file.
Both paths now warn: the one where the fallback font itself fails to load (naming the path and the exception), and the one that reaches PIL's built-in face regardless.
Nothing renders differently. This only makes an existing failure visible.
Scope
An audit of the font and odds paths across all eight scoreboards:
except Exception/ bare)football/game_renderer.pykeeps its silent BDF handler deliberately — its own comment states the warning is deferred to the downstream fallback, and one is emitted there. Changing it would add noise for an expected condition.Companion to core ChuckBuilds/LEDMatrix#520, which does the same for the schema lookup — the failure that shipped a 3px-wide glyph and was found by a user counting pixels rather than by any log line.
Fleet: 246 passed, 0 failed.