One live element per game, drawn only when what the card shows changes, so
a score changes on a card already crossing the panel. The shared part, so
each scoreboard adopts it in a few lines:
- src/common/sports_vegas.py: game_key, game_fingerprint (the whole game
dict, frozen: no drawn field can be missed), dedupe_games, VegasCardCache,
StickyOdds (odds a live poll left out stay drawn), finished_games /
with_finished_games (a game that just went final keeps its card, after its
league's live games; one a heuristic only judged over keeps its live
state, so a tied end of regulation never shows FINAL early).
- SportsScrollDisplay.make_vegas_renderer() is the override point;
build_vegas_elements() and SportsScrollDisplayManager
.get_vegas_elements_for() do the rest. A card's version includes its
teams' ranks, which the renderer draws from the rankings cache.
- SportsLiveSharedMixin._record_finished_game() / finished_games_snapshot():
held for FINISHED_GAME_TTL after it leaves the live list.
A sport that does not implement make_vegas_renderer keeps its ordinary Vegas
content, so no scoreboard changes until it opts in.
scripts/render_plugin.py --vegas renders a plugin's Vegas block as the
ticker lays it out, and --timeline stacks it at successive moments as
the ticker would update it in place; the join is now
render_pipeline.join_plugin_rows().
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Vegas bakes each plugin's pictures into one strip, so a card already on its
way across the panel keeps what it showed when it was drawn. This adds the
API and bookkeeping for content that can be updated in place; the worker
that redraws and swaps it follows separately. No shipped plugin implements
the hook yet, so nothing changes for users.
Plugin API (core 3.8.0), all no-ops by default:
- BasePlugin.get_vegas_elements() -> [VegasElement(key, image, version,
live, refresh_hz)]: named, fixed-width pieces of Vegas content.
- BasePlugin.redraw_vegas_element(key, width, height, at): a lock-free
redraw for content that changes with time.
- BasePlugin.notify_vegas_data_changed(): data that lands outside update().
- src/plugin_system/vegas_elements.py (VegasElement, re-exported from
base_plugin).
Core:
- PluginAdapter asks a plugin that implements the hook for elements on the
background fetch only (under its lock, on its own canvas); every other
path keeps get_vegas_content(). Live elements are pinned (padded with
content_padding, never trimmed), tagged with their key, digest and data
epoch in Image.info so the existing cache and group plumbing carry them
unchanged, and untagged if a width budget crops them.
- RenderPipeline records where each live element lands (ElementRecord), in
absolute strip columns a trim does not move; the block-start arithmetic
is shared with the STATIC markers.
- PluginManager update listeners (add/remove_update_listener,
notify_data_changed): told the moment update() completes, not at the
next ~4s Vegas poll. The coordinator uses one to move each plugin's data
epoch on.
- vegas_scroll.live_refresh (kill switch), live_max_hz, live_min_interval,
live_lead_screens; per-plugin core-owned vegas_live. Live elements are
off under multi-display sync, in swap mode and with offscreen_prefetch off.
- scripts/check_plugin.py checks the element contract
(src/plugin_system/testing/vegas.py); test/fixtures/plugins/vegas-live-stub
is a working example.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>