Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/lacrosse-scoreboard/hero.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/lacrosse-scoreboard/leagues.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/lacrosse-scoreboard/panel-sizes.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
919 changes: 919 additions & 0 deletions docs/assets/lacrosse-scoreboard/shots.json

Large diffs are not rendered by default.

Binary file added docs/assets/lacrosse-scoreboard/show-records.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions plugins.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"version": "1.0.0",
"last_updated": "2026-09-03",
"last_updated": "2026-09-04",
"plugins": [
{
"id": "cricket-scoreboard",
Expand Down Expand Up @@ -359,7 +359,7 @@
"last_updated": "2026-09-02",
"verified": true,
"screenshot": "",
"latest_version": "1.24.1",
"latest_version": "1.24.2",
"icon": "fas fa-baseball-ball"
},
{
Expand Down
864 changes: 514 additions & 350 deletions plugins/lacrosse-scoreboard/README.md

Large diffs are not rendered by default.

14 changes: 12 additions & 2 deletions plugins/lacrosse-scoreboard/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "lacrosse-scoreboard",
"name": "Lacrosse Scoreboard",
"version": "1.24.1",
"version": "1.24.2",
"author": "ChuckBuilds",
"description": "Live, recent, and upcoming NCAA men's and women's lacrosse games with real-time scores and schedules",
"homepage": "https://github.com/ChuckBuilds/ledmatrix-plugins/tree/main/plugins/lacrosse-scoreboard",
Expand Down Expand Up @@ -50,6 +50,16 @@
}
],
"versions": [
{
"version": "1.24.2",
"released": "2026-09-02",
"ledmatrix_min_version": "3.3.0",
"notes": "Fix every grid-snapped font rendering a pixel narrow. The shared code reads this plugin's config_schema.json to tell a default font size from one the user chose; a default gets snapped to the font's pixel grid, a choice is left alone. It located the schema by inspecting loaded modules, which fails under the real plugin loader -- the loader renames a plugin's modules and removes their original names, so nothing was left to inspect. The lookup returned nothing, every size then looked user-chosen, and the snap was skipped: 4x6-font.ttf drew at 6 instead of 7, which is 3-pixel-wide glyphs instead of 4. On a 256x64 panel the betting odds, team records and the date row were hard to read. The plugin now tells the shared code where it lives instead of leaving it to guess.",
"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.",
"changes": [
"Rewrote the README as a complete settings reference covering all 170 settings, with rendered examples of every display mode and both leagues."
]
},
{
"version": "1.24.1",
"released": "2026-09-02",
Expand Down Expand Up @@ -337,7 +347,7 @@
{
"released": "2026-07-02",
"version": "1.3.0",
"notes": "Add exclude_teams (hide specific teams from the live rotation and recent/final scores spoiler protection, takes precedence over favorite_teams/show_all_live) and favorite_live_boost (tune how many more turns your favorite's live game gets in the rotation vs other live games, 1 = even rotation, default 2).",
"notes": "Add exclude_teams (hide specific teams from the live rotation and recent/final scores \u2014 spoiler protection, takes precedence over favorite_teams/show_all_live) and favorite_live_boost (tune how many more turns your favorite's live game gets in the rotation vs other live games, 1 = even rotation, default 2).",
"ledmatrix_min": "2.0.0"
},
{
Expand Down
55 changes: 51 additions & 4 deletions scripts/docs_render_support/sitecustomize.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,59 @@ def advance(seconds):
_WANTED = _json.loads(_ATTRS)
_TARGET = "src.plugin_system.plugin_loader"

def _resolve(instance, path):
"""Walk a dotted path to the object that owns the final attribute.

The sports scoreboards keep their per-mode state on sub-managers rather
than on the plugin -- self._managers["live"].current_game -- so a shot
that can only set top-level attributes cannot reach the game it wants
to draw. A segment is tried as a mapping key first, then as an
attribute, so both self._managers["live"] and self.live_manager work.
"""
parts = path.split(".")
target = instance
for part in parts[:-1]:
if hasattr(target, "get") and not hasattr(target, part):
nxt = target.get(part)
else:
nxt = getattr(target, part, None)
if nxt is None and hasattr(target, "get"):
nxt = target.get(part)
if nxt is None:
return None, None
target = nxt
return target, parts[-1]

def _coerce(name, value):
"""Turn JSON into the shapes the plugins actually hold.

A shots file can only carry JSON, but plugin state is not all strings
and lists: colours are tuples throughout the core, and a logo field is
a pathlib.Path -- the sports renderers call logo_path.parent, so a
string there raises AttributeError and the card silently fails to draw.
"""
if isinstance(value, list) and len(value) == 3 and all(
isinstance(v, int) for v in value):
return tuple(value) # colours are tuples everywhere in the core
if isinstance(value, str) and name.endswith("_path") and value:
import pathlib as _pathlib
return _pathlib.Path(value)
if isinstance(value, dict):
return {k: _coerce(k, v) for k, v in value.items()}
if isinstance(value, list):
return [_coerce(name, v) for v in value]
return value

def _apply(instance):
for name, value in _WANTED.items():
if isinstance(value, list) and len(value) == 3 and all(
isinstance(v, int) for v in value):
value = tuple(value) # colours are tuples everywhere in the core
setattr(instance, name, value)
value = _coerce(name.rsplit(".", 1)[-1], value)
owner, attr = _resolve(instance, name)
if owner is None:
continue # the path does not exist on this plugin; leave it alone
if hasattr(owner, "__setitem__") and not hasattr(owner, attr):
owner[attr] = value
else:
setattr(owner, attr, value)
return instance

class _PatchingLoader:
Expand Down
3 changes: 3 additions & 0 deletions scripts/render_docs_assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,9 @@ def render_shot(
mock_path = resolve_mock_data(mock_spec, shot_list_dir, tmpdir, name)
if mock_path:
cmd += ["--mock-data", str(mock_path)]
display_mode = shot.get("display_mode", defaults.get("display_mode"))
if display_mode:
cmd += ["--display-mode", str(display_mode)]
if shot.get("skip_update", defaults.get("skip_update", False)):
cmd.append("--skip-update")

Expand Down
Loading