Files
LEDMatrix/CHANGELOG.md
T
Claude 98a7c769ec fix(sports): harden the live game-over check and font/log init
Follow-up review findings on the promoted base-class methods.

_is_game_really_over:
- `period` present-but-None raised TypeError on `None >= FINAL_PERIOD`,
  taking down the whole live-update pass (_detect_stale_games has no
  try/except). Same failure shape as the null `period_text` already fixed.
- An expired clock spelled "00:00" normalizes to "0000", which matched
  none of the hand-listed literals, so a finished game with a two-digit
  minute clock stayed on the scoreboard forever. Compare numerically.

SportsCore:
- _load_fonts kept the cwd-relative "assets/fonts/..." literals the
  _font_root() seam exists to remove, so every scoreboard font degraded
  to PIL's default face outside the install root.
- _should_log read self._last_warning_time unguarded while only an
  unrelated method initialized it lazily; the first warning of a run
  raised AttributeError. Initialize it in __init__.

Also documents that game_update_timestamps is written by subclasses, not
by the base class, so the staleness branch is inert until B5 adoption.

14 new tests. Gates: 463 core unit, 60 plugin safety.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgbA8SMutQQpXkMG8LMmC4
2026-08-01 16:45:04 +00:00

5.0 KiB

Changelog

Notable changes to the LEDMatrix core. The version below is the value of src.__version__, which the plugin loader reports to compatibility checks and which plugin manifests reference via ledmatrix_min_version.

Why this file exists: the plugin monorepo bundles fallback copies of several core modules (see docs/plugin-development/08-shared-sports-code.md in ledmatrix-plugins). A plugin may delete its bundled copy only when its manifest floors on the first core release that ships the module — which requires module additions to be recorded here, against a version number. When you add a module plugins will import via src.*, note it in the Unreleased section and bump src/__init__.py in the release that ships it.

Unreleased

Added

  • src/element_style.py — per-element style resolver backing the x-style-elements config-schema extension. Already consumed (behind guarded imports with classic fallbacks) by the of-the-day, ledmatrix-music, and football-scoreboard plugins.

  • Core unit-test CI job enrolling the previously unenrolled suites (skin system, data sources, API extractors, scroll helper, adaptive layout, loader compatibility warning) plus new characterization tests for src/base_classes/sports.py ahead of the shared sports-code unification.

  • src/base_classes/sports/sports.py is now a package (core.py + modes.py). The import path is unchanged: from src.base_classes.sports import SportsCore still works.

  • Nine methods promoted onto the sports base classes from the plugins' bundled copies, plus the override points _favorite_key, _config_schema_path and _font_root and the class attributes FINAL_PERIOD / CLOCK_COUNTS_DOWN. See docs/SPORTS_UNIFICATION.md. A plugin may start calling these once its manifest floors ledmatrix_min_version at the release that ships them.

Changed

  • Live games are no longer dropped when the feed omits a game clock. SportsLive._is_game_really_over previously (in the baseball and UFC plugin lineages) coerced a missing or non-string clock to the literal "0:00" and then treated the game as finished once period >= 4. Baseball has no game clock and period is the inning, so live MLB games disappeared from the scoreboard from the 5th inning onward; UFC was affected the same way. The clock check is now skipped when the clock is unusable, and the period threshold is the per-sport FINAL_PERIOD (hockey ends in P3). Sports whose clocks count up — soccer, AFL, NRL — set CLOCK_COUNTS_DOWN = False and never run the check at all, since 0:00 there means kickoff rather than expiry.

Fixed

  • FontManager resolves assets/fonts against the core install root instead of the process working directory, so font loading works when the process starts elsewhere (e.g. the plugin safety harness on CI).
  • Hockey events whose competitors carry no statistics array are no longer discarded. The extractor read competitor["statistics"] unguarded, so a KeyError inside the generator dropped the entire event despite valid scores and status; shot counts now fall back to 0.
  • Live baseball events that populate status only at the competition level are no longer discarded. The extractor read the event top-level game_event["status"] for the inning; real ESPN events duplicate it, but MiLB events synthesized from the MLB Stats API do not, so the lookup raised a bare KeyError. It now reads the already-validated competition-level status.
  • SportsLive._is_game_really_over no longer crashes the live-update pass when a feed sends an explicit null period. None >= FINAL_PERIOD raised TypeError, and the only caller (_detect_stale_games) has no try/except — the same failure shape as the already-fixed null period_text.
  • An expired clock spelled "00:00" now ends the game. The check compared the colon-stripped clock against a hand-listed set of literals, which "0000" is not a member of, so a finished game with a two-digit-minute clock stayed on the scoreboard indefinitely. The comparison is now numeric.
  • SportsCore._load_fonts resolves assets/fonts through the _font_root() seam instead of the process working directory. Started outside the install root, every scoreboard font silently degraded to PIL's default bitmap face.
  • SportsCore._should_log no longer raises AttributeError on the first warning of a run; _last_warning_time is initialized in __init__ rather than lazily by an unrelated method.
  • SportsCore._resolve_project_path resolved relative logo directories against <root>/src instead of the repo root after sports.py became a package — the class bodies moved byte-identically but __file__ gained a directory. Both it and _font_root now derive from one _INSTALL_ROOT constant.

3.1.0

Baseline for this changelog. Highlights already shipped at this version: skin system for sports scoreboards (#419), Vegas continuous-scroll overhaul (#423), plugin update surfacing (#421).