diff --git a/plugins.json b/plugins.json index 6ee646c5..8d13c30d 100644 --- a/plugins.json +++ b/plugins.json @@ -101,7 +101,7 @@ "last_updated": "2026-09-02", "verified": true, "screenshot": "", - "latest_version": "1.29.2" + "latest_version": "1.29.3" }, { "id": "calendar", @@ -760,7 +760,7 @@ "last_updated": "2026-09-02", "verified": true, "screenshot": "", - "latest_version": "2.24.2" + "latest_version": "2.24.3" }, { "id": "static-image", diff --git a/plugins/basketball-scoreboard/game_renderer.py b/plugins/basketball-scoreboard/game_renderer.py index ae9d6d11..cc84a099 100644 --- a/plugins/basketball-scoreboard/game_renderer.py +++ b/plugins/basketball-scoreboard/game_renderer.py @@ -259,9 +259,17 @@ def _load_custom_font(self, element_config: Dict[str, Any], default_size: int = try: if os.path.exists(default_font_path): return ImageFont.truetype(default_font_path, font_size) - except Exception: - pass + except Exception as e: + # Say so. Reaching PIL's built-in face means this element will + # not match the panel's pixel grid, which reads as a rendering + # bug rather than a missing font file. + self.logger.warning( + "Fallback font %s failed to load (%s: %s)", + default_font_path, type(e).__name__, e) + self.logger.warning( + "No usable font found; using PIL's built-in face, which will " + "not be pixel-crisp") return ImageFont.load_default() def preload_logos(self, games: list, logo_dir: Path) -> None: diff --git a/plugins/basketball-scoreboard/manifest.json b/plugins/basketball-scoreboard/manifest.json index fb65e93d..c47d2203 100644 --- a/plugins/basketball-scoreboard/manifest.json +++ b/plugins/basketball-scoreboard/manifest.json @@ -1,7 +1,7 @@ { "id": "basketball-scoreboard", "name": "Basketball Scoreboard", - "version": "1.29.2", + "version": "1.29.3", "update_interval": 60, "description": "Live, recent, and upcoming basketball games across NBA, NCAA Men's, NCAA Women's, and WNBA with real-time scores, schedules, and March Madness tournament support", "author": "ChuckBuilds", @@ -19,6 +19,13 @@ "branch": "main", "plugin_path": "plugins/basketball-scoreboard", "versions": [ + { + "version": "1.29.3", + "released": "2026-09-02", + "ledmatrix_min_version": "3.3.0", + "notes": "Say so when no usable font can be found. The last-ditch fallback silently dropped to PIL's built-in face, which does not match the panel's pixel grid -- so a missing or unreadable font file looked like a rendering bug with nothing in the log to explain it. It now warns, naming the font it failed to load. Nothing renders differently; this only makes an existing failure visible.", + "changelog": "Retry a team logo whose previous download failed, instead of showing a grey box forever. A failed download is cached by the core as a placeholder wearing the real logo's filename; the logo loader scans filename variations, found that stub, and so never called the downloader again. The loader now skips a placeholder that is stale enough to be worth retrying and lets the download run, which also picks up stubs already on disk. The retry is rate-limited by the core (6h), so this does not trade a permanent grey box for a request every frame. Needs a core carrying src.logo_downloader.is_placeholder_logo; against an older core the check is skipped and behaviour is unchanged. Ported byte-identically across every sports lineage." + }, { "version": "1.29.2", "released": "2026-09-02", diff --git a/plugins/soccer-scoreboard/game_renderer.py b/plugins/soccer-scoreboard/game_renderer.py index da663cb4..2e41e652 100644 --- a/plugins/soccer-scoreboard/game_renderer.py +++ b/plugins/soccer-scoreboard/game_renderer.py @@ -223,9 +223,17 @@ def _load_custom_font(self, element_config: Dict[str, Any], default_size: int = try: if os.path.exists(default_font_path): return ImageFont.truetype(default_font_path, font_size) - except Exception: - pass - + except Exception as e: + # Say so. Reaching PIL's built-in face means this element will + # not match the panel's pixel grid, which reads as a rendering + # bug rather than a missing font file. + self.logger.warning( + "Fallback font %s failed to load (%s: %s)", + default_font_path, type(e).__name__, e) + + self.logger.warning( + "No usable font found; using PIL's built-in face, which will " + "not be pixel-crisp") return ImageFont.load_default() def preload_logos(self, games: list, logo_dir: Path) -> None: diff --git a/plugins/soccer-scoreboard/manifest.json b/plugins/soccer-scoreboard/manifest.json index dc2acaf4..4f87d4a3 100644 --- a/plugins/soccer-scoreboard/manifest.json +++ b/plugins/soccer-scoreboard/manifest.json @@ -1,7 +1,7 @@ { "id": "soccer-scoreboard", "name": "Soccer Scoreboard", - "version": "2.24.2", + "version": "2.24.3", "author": "ChuckBuilds", "description": "Live, recent, and upcoming soccer games across multiple leagues including Premier League, La Liga, Bundesliga, Serie A, Ligue 1, MLS, Liga Portugal, Champions League, Europa League, and FIFA World Cup", "category": "sports", @@ -26,6 +26,13 @@ "soccer_upcoming" ], "versions": [ + { + "version": "2.24.3", + "released": "2026-09-02", + "ledmatrix_min_version": "3.3.0", + "notes": "Say so when no usable font can be found. The last-ditch fallback silently dropped to PIL's built-in face, which does not match the panel's pixel grid -- so a missing or unreadable font file looked like a rendering bug with nothing in the log to explain it. It now warns, naming the font it failed to load. Nothing renders differently; this only makes an existing failure visible.", + "changelog": "Retry a team logo whose previous download failed, instead of showing a grey box forever. A failed download is cached by the core as a placeholder wearing the real logo's filename; the logo loader scans filename variations, found that stub, and so never called the downloader again. The loader now skips a placeholder that is stale enough to be worth retrying and lets the download run, which also picks up stubs already on disk. The retry is rate-limited by the core (6h), so this does not trade a permanent grey box for a request every frame. Needs a core carrying src.logo_downloader.is_placeholder_logo; against an older core the check is skipped and behaviour is unchanged. Ported byte-identically across every sports lineage." + }, { "version": "2.24.2", "released": "2026-09-02",