Skip to content

docs(lacrosse-scoreboard): document all 170 settings, with real renders - #428

Merged
ChuckBuilds merged 4 commits into
mainfrom
docs/lacrosse-readme
Sep 4, 2026
Merged

docs(lacrosse-scoreboard): document all 170 settings, with real renders#428
ChuckBuilds merged 4 commits into
mainfrom
docs/lacrosse-readme

Conversation

@ChuckBuilds

Copy link
Copy Markdown
Owner

The lacrosse README documented a fraction of the plugin's 170 settings and had no images. It now covers every schema leaf — verified with a token audit against config_schema.json — and shows the three men's display modes, both leagues side by side, the show_records toggle, and the live card at four panel sizes.

Structure

The ncaa_mens and ncaa_womens config blocks are identical in every setting except one: ncaa_mens defaults to enabled: true and ncaa_womens to false. Documenting a single <league> table rather than two byte-identical sets keeps 104 of the 170 leaves readable instead of duplicated, which is the point of the exercise.

What the old README did not say

  • display_options.show_records and show_ranking default to true per league, while defaults.show_records and defaults.show_ranking default to false — and the per-league copy wins (manager.py:795, resolve_value). Because the web UI writes schema defaults on save, a saved config already carries the per-league true, so changing the defaults copy appears to do nothing. Documented in the settings reference and again under troubleshooting, where a user would actually look.
  • The favorites key is teams.favorite_teams_only, not show_favorite_teams_only as in the single-league scoreboards, and it defaults to off here rather than on. Easy to carry the wrong key over from another scoreboard's config.
  • other_games_min_quality is live in this plugin. NCAA lacrosse has a national poll to rank against, so its ranked default really does restrict non-favorite games — unlike in nrl, where the same setting is inert. other_games_divisions stays inert; it is a college football taxonomy.

mode_durations and customization.favorite_result_colors are read by the core (src/common/sports_card.py), not by this plugin. The README says where, so the next person does not delete them as dead.

Renders

From docs/assets/lacrosse-scoreboard/shots.json. Lacrosse differs from nrl: it keeps one manager per league and mode as a plain attribute (ncaa_mens_live, ncaa_womens_recent, …) rather than in a _managers dict, and only constructs them when that league's enabled flag is set — so the fixture turns both leagues on. These are real ESPN crests (Yale, Dartmouth, Quinnipiac, Harvard), not the grey placeholders nrl had.

One fixture correction worth noting: the switch-mode recent card reads game.get("period_text", "Final"), a .get default rather than a falsy check, so an empty-string period_text draws nothing. Real data never hits that — lacrosse.py:174 sets "Final" for any post-state game — so the fixture now carries what the extractor actually produces rather than an unreachable state.

Checks

  • check_plugin.py: zero FAIL (16 checks; the harness exercises the men's recent and upcoming modes)
  • Config-token audit: 170 schema leaves, 0 undocumented
  • Every example config validated against the schema, including nesting depth and additionalProperties
  • Every documented default read back from config_schema.json
  • Manifest bumped 1.24.1 -> 1.24.2 (PATCH, docs-only), plugins.json regenerated by update_registry.py

Does not touch the shared sports.py lineage.

Dependency

Carries the docs-tooling commits from #423 so this branch renders standalone; identical in both branches and they collapse on merge.

🤖 Generated with Claude Code

The README documented a fraction of the plugin's 170 settings and had no
images. It now covers every schema leaf -- verified by a token audit --
and shows the three men's display modes, both leagues side by side, the
show_records toggle, and the card at four panel sizes.

The two league blocks are identical in every setting except one:
ncaa_mens defaults to enabled and ncaa_womens does not. Documenting one
<league> table rather than two identical sets keeps 104 of the 170
leaves readable instead of duplicated.

Two things the old README did not say:

- display_options.show_records and show_ranking default to true per
  league, while defaults.show_records and defaults.show_ranking default
  to false, and the per-league copy wins. Because the UI writes schema
  defaults on save, changing the defaults copy appears to do nothing.
  Both the settings reference and troubleshooting now say so.
- The favorites key here is teams.favorite_teams_only, not
  show_favorite_teams_only as in the single-league scoreboards, and it
  defaults to off rather than on.

other_games_min_quality is live in this plugin -- NCAA lacrosse has a
national poll to rank against -- unlike in nrl, where it is inert.
other_games_divisions stays inert; it is a college football taxonomy.

Renders come from docs/assets/lacrosse-scoreboard/shots.json. Lacrosse
keeps one manager per league and mode as a plain attribute rather than
in a dict, and only builds them when that league's enabled flag is set,
so the fixture turns both leagues on. Real ESPN crests, not placeholders.

check_plugin.py passes with no FAIL.

Carries the docs-tooling changes from #423, which this render depends on.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 4, 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: Team

Run ID: 919e6916-6526-440b-952a-abb1ca521d67


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 Sep 4, 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.

The rewrite dropped the breaking-change notice from 1.1.0, when the
display modes gained a lax_ prefix to stop colliding with the NCAA
hockey modes. Anyone still holding an old pin in display_durations or
rotation_order needs it, and the CHANGELOG mapping is the only other
place it is written down.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ChuckBuilds and others added 2 commits September 4, 2026 16:25
The merge from main left the 1.24.2 entry holding both sides bodies --
two notes keys, two changelog keys, and a missing comma between them --
so manifest.json was not valid JSON and the version gate could not read
it.

Both sides had added a 1.24.2: main from the font fix in #409, this
branch from the README rewrite. Rebuilt from main manifest, which is
intact, with the docs change moved to a new 1.24.3 entry above it. That
keeps main 1.24.2 exactly as merged and follows the shape the other docs
PRs used on main -- the new entry carries the previous entry notes and
changelog plus its own changes array.

plugins.json regenerated: latest_version 1.24.3.

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
ChuckBuilds merged commit b3aa603 into main Sep 4, 2026
3 of 4 checks passed
@ChuckBuilds
ChuckBuilds deleted the docs/lacrosse-readme branch September 4, 2026 20:55
ChuckBuilds added a commit that referenced this pull request Sep 4, 2026
scripts/test_scroll_card_renders.py has been red on main since #409,
which made the safety check fail on every open PR regardless of what
that PR touched -- #428 fails it while changing only one plugin's README
and manifest. The signal was useless: a real render regression would
have looked identical to this noise.

#409 deliberately changed which font several text elements draw in, so
the goldens committed on 2026-08-02 describe the old, wrong rendering.
Measured: at the parent of #409 the guard reports 3 problems, on main it
reports 42. These 39 are that difference.

Checked rather than assumed. Every regenerated card was compared against
its predecessor pixel by pixel, and the four sampled visually: the text
content is identical and only the glyph shapes change, which is what a
font swap should look like. --update also rewrote 24 cards whose pixels
had not changed at all, presumably encoder metadata; those are reverted
so the diff is only the 39 that actually differ.

Baseball's three upcoming cards are deliberately NOT refreshed. They are
the 3 that were already failing before #409, and they fail for an
unrelated reason: game_renderer.py formats the date with %-m/%-d and
%-I:%M%p, which are glibc extensions. Off glibc they raise, the except
falls back to a raw ISO string, and the card loses its start time and
shows 2026-09-19 instead of Sep 19. The committed goldens are correct;
regenerating them anywhere but glibc would bake the broken output in.
Filed separately.

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
scripts/test_scroll_card_renders.py has been red on main since #409,
which made the safety check fail on every open PR regardless of what
that PR touched -- #428 fails it while changing only one plugin's README
and manifest. The signal was useless: a real render regression would
have looked identical to this noise.

#409 deliberately changed which font several text elements draw in, so
the goldens committed on 2026-08-02 describe the old, wrong rendering.
Measured: at the parent of #409 the guard reports 3 problems, on main it
reports 42. These 39 are that difference.

Checked rather than assumed. Every regenerated card was compared against
its predecessor pixel by pixel, and the four sampled visually: the text
content is identical and only the glyph shapes change, which is what a
font swap should look like. --update also rewrote 24 cards whose pixels
had not changed at all, presumably encoder metadata; those are reverted
so the diff is only the 39 that actually differ.

Baseball's three upcoming cards are deliberately NOT refreshed. They are
the 3 that were already failing before #409, and they fail for an
unrelated reason: game_renderer.py formats the date with %-m/%-d and
%-I:%M%p, which are glibc extensions. Off glibc they raise, the except
falls back to a raw ISO string, and the card loses its start time and
shows 2026-09-19 instead of Sep 19. The committed goldens are correct;
regenerating them anywhere but glibc would bake the broken output in.
Filed separately.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
ChuckBuilds added a commit that referenced this pull request Sep 7, 2026
…olver (#462)

* test(scroll-cards): refresh the goldens #409 made stale

scripts/test_scroll_card_renders.py has been red on main since #409,
which made the safety check fail on every open PR regardless of what
that PR touched -- #428 fails it while changing only one plugin's README
and manifest. The signal was useless: a real render regression would
have looked identical to this noise.

#409 deliberately changed which font several text elements draw in, so
the goldens committed on 2026-08-02 describe the old, wrong rendering.
Measured: at the parent of #409 the guard reports 3 problems, on main it
reports 42. These 39 are that difference.

Checked rather than assumed. Every regenerated card was compared against
its predecessor pixel by pixel, and the four sampled visually: the text
content is identical and only the glyph shapes change, which is what a
font swap should look like. --update also rewrote 24 cards whose pixels
had not changed at all, presumably encoder metadata; those are reverted
so the diff is only the 39 that actually differ.

Baseball's three upcoming cards are deliberately NOT refreshed. They are
the 3 that were already failing before #409, and they fail for an
unrelated reason: game_renderer.py formats the date with %-m/%-d and
%-I:%M%p, which are glibc extensions. Off glibc they raise, the except
falls back to a raw ISO string, and the card loses its start time and
shows 2026-09-19 instead of Sep 19. The committed goldens are correct;
regenerating them anywhere but glibc would bake the broken output in.
Filed separately.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* refactor(scroll): route every scrolling plugin through the shared resolver

Eleven plugins each hand-rolled scroll configuration and disagreed with one
another about what identical config meant. odds-ticker ranked the deprecated
scroll_pixels_per_second ABOVE the documented scroll_speed/scroll_delay pair,
and because that key carries a schema default the documented settings were
dead for every user (#408). ledmatrix-leaderboard read the same key only as a
fallback, so the same config produced different speeds in the two plugins.
stock-news derived px/frame through its own arithmetic. Nobody was wrong
locally; they were just eleven answers to one question.

All of them now call src.common.scroll_config.configure(), which resolves
every supported config shape in one place, snaps the speed to one the panel
can render in whole pixels, and reports the frame hold needed to keep slow
speeds crisp. Identical config now means an identical speed everywhere.

Each plugin keeps its original logic as a fallback behind

    try:
        from src.common import scroll_config as _scroll_config
    except ImportError:
        _scroll_config = None

because plugins update independently of the core and must keep working
against one that predates the helper. Where the legacy block sits inline in
__init__ the shared call runs after it and wins, rather than re-indenting
logic other config shapes still depend on; those plugins log a line saying so,
because two scroll speeds in the journal with no indication which took effect
is exactly the confusion this change exists to remove.

Plugins pass settings.frame_hold to set_scrolling_state(True, ...) when they
begin scrolling. The hold cannot be applied once at construction: plugins
share one display manager, and it is reset whenever any other plugin finishes
its scroll.

Verified on hardware (Pi 4, 2x128x64, 100Hz). Four enabled plugins resolve
through the helper with no tracebacks, and ledmatrix-stocks at 50 px/s
measures 50.0 fps, median 20.00ms -- exactly two refreshes per frame, with the
panel still refreshing at 100Hz so flicker is unchanged.

Requires core support: ChuckBuilds/LEDMatrix#523. Without it every plugin
takes the fallback path and behaves exactly as before.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(odds-ticker): make the documented scroll settings reachable (#408)

Two defects meant display_options.scroll_speed / scroll_delay -- the format
the plugin documents and recommends -- could never take effect.

The priority-1 branch read scroll_pixels_per_second into self rather than
clearing it the way the display_config branch does. config_schema.json gives
that deprecated key a default of 50.0 and schema defaults are merged into
plugin config, so it was never None and use_frame_based() below was never
True. Separately, use_frame_based() only ever inspected display_config, so
even with that fixed the recommended shape still could not select frame-based
mode. Both halves were needed.

The visible symptom was the plugin logging its scroll configuration twice on
startup, ~0.36s apart, the second line quietly overriding the first:

    Using display_options.scroll_speed=1.0 px/frame ... (frame-based mode)
    Using scroll_pixels_per_second: 50.0 px/s (time-based mode)

with the second one being what actually ran. On a 100Hz panel that meant
0.5px per frame, so every second frame rendered identically, dirty tracking
skipped the panel swap, and the ticker juddered. Editing scroll_speed had no
effect, and deleting scroll_pixels_per_second only restored the schema
default.

Also takes the live-game check off the render path. display() called
_get_current_update_interval() every frame, and its slow path reads the
scoreboard cache from disk and parses JSON per enabled league -- producing a
single ~15ms frame every few minutes, measurable as a stall mid-scroll. The
interval is now memoised for 15s; the scoreboard re-check underneath is rate
limited to 300s regardless, so live detection is unaffected. The neighbouring
debug line used an f-string, so it called _has_live_games() on every skipped
update even with debug logging off; it now uses %s args.

Verified on hardware: odds-ticker selects frame-based mode with no time-based
line following it, and resolves to 100.0 px/s at 1px per refresh.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: make the frame-hold call safe for the test doubles, and update to main

Three display-manager doubles still declared set_scrolling_state with a
single argument, so the frame_hold this PR passes raised TypeError
mid-render. display() swallows that in its own except Exception, leaving
only "no frame reached the display" -- odds-ticker's cache-invalidation
test failed for a reason that had nothing to do with cache invalidation.
CI caught one of the three; the other two never reach the scrolling
branch. scripts/test_scroll_state_doubles.py checks the invariant
instead, and catches all three (including the *args one, which looks
tolerant but cannot take a keyword).

odds-ticker's double also needed _scroll_frame_hold itself: it borrows
only the methods under test, and display() now calls that one.

Fixes a continuation line in odds-ticker/manager.py that was indented
back to column 12 inside the call -- legal, but not what anyone meant.

Merges main. This branch was two commits behind, which made its diff
look like it deleted versions[] entries main had gained. Six plugins
then needed version bumps they did not need before, because main had
bumped past them.

ledmatrix-leaderboard and nfl-draft sit one patch above #466's numbers
rather than duplicating them -- two PRs must never publish the same
version, and a user could not tell which build they have. That pins the
merge order these two already needed.

Verified against a core with #523 and #534 merged: safety harness clean
on all 11 changed plugins, 23 unit tests pass, version and collision
gates pass. The one failing repo guard, test_odds_centre_collision.py,
fails identically on main and is untouched here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RRtqXDCnvnY6EQwhT5CV9

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: t <t@l>
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