Files
LEDMatrix/config/config.template.json
T
ChuckBuildsandClaude 8d57a748a7 Vegas mode: reclaim dead space and pace the rotation
On a wide panel Vegas mode spent much of its time showing black. At 50px/s
on a 512px display, one display width of blank is 10.2 seconds, which makes
several long-standing behaviours expensive:

- ScrollHelper prepended a full display width of black as an "initial gap",
  charged once per cycle — 10.2s of black at the start of every rotation.
- Plugins without get_vegas_content() are captured off a full-display canvas,
  so their blank margins entered the ticker too. Measured: of-the-day drew
  35px of "No Data" on a 512px canvas (92% blank), youtube-stats 142px of
  content with 185px of black either side. Only the scroll_helper path had
  any trimming.
- Cycle transitions deliberately pushed a blank frame and then recomposed
  synchronously: 84ms at best, 4.8s at worst, every millisecond of it black.
- buffer_ahead doubled as the cycle size, so a 21-plugin install showed 3
  plugins per cycle and took ~7 cycles to come around.
- separator_width was applied between every image rather than at plugin
  boundaries, so a per-row ticker like the F1 scoreboard (116 images, which
  it renders 4px apart internally) got a 32px chasm between each row — and
  the width budget didn't count those gaps, so the plugin quietly occupied
  far more of the panel than intended.

Changes:

- src/vegas_mode/geometry.py: numpy column-ink primitives shared by the
  trimmer and the audit tool, so the number reported is the number acted on.
  A Python per-column loop over a 17,000px strip is far too slow for the
  render path.
- PluginAdapter trims every content path, not just scroll_helper. Only outer
  edges are cropped: interior blank columns are the plugin's own layout
  (logo left, score right) and closing them would corrupt the design. A
  plugin on a non-black background is inherently unaffected.
- ScrollHelper.create_scrolling_image takes an explicit lead_gap, still
  defaulting to display_width so the many standalone-ticker callers are
  unchanged. Vegas passes lead_in_width (default 0).
- Cycle end holds the last rendered frame instead of blanking, turning the
  recompose into a brief freeze rather than the panel switching off.
- plugins_per_cycle (default 6) is split from buffer_ahead, which goes back
  to being only a prefetch low-water mark.
- max_plugin_width_ratio (default 3x display width) caps one plugin's share
  of a cycle. Overflow is deferred, not discarded: a rotation offset advances
  each fetch so later rows appear on subsequent cycles. Single oversized
  images are cropped at a blank column so the cut misses glyphs.
- Composition groups images by plugin: rows are joined by intra_plugin_gap
  (default 8) and separator_width applies only between plugins. The width
  budget now counts those gaps.
- Plugin data updates no longer run on the Vegas render path.

All new settings are user-configurable in Display -> Vegas Scroll, including
min/max cycle duration and dynamic duration, which previously existed in code
but were reachable only by hand-editing config.json.

Measured with scripts/dev/vegas_audit.py on a 512x64 panel:

  mean ink coverage    42.7% -> 69.4%
  fully blank           5.9% -> 0%
  reads as empty        13.6% -> 0%
  worst blank stretch    4.8s -> 0s
  full rotation          414s -> 123s
  plugins per cycle         3 -> 6

Note the metric choice: a "fully blank" scan (>=95% black viewport) reported
only 0.4% and badly understated the problem, because two full-width segments
with mid-canvas content never fully blank the viewport — they hold it at ~28%.
window_coverage_stats grades every viewport position by how much ink it
carries, which is what tracks perceived dead time.

Known remaining: cycle transitions still freeze ~3.5s while the next cycle is
fetched. Fixing that needs background prefetch, which is deferred because the
fallback-capture path mutates the shared display_manager.image and racing it
against the render loop risks torn frames.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ
2026-07-28 20:18:51 -04:00

162 lines
4.4 KiB
JSON

{
"web_display_autostart": true,
"schedule": {
"enabled": false,
"mode": "per-day",
"start_time": "07:00",
"end_time": "23:00",
"days": {
"monday": {
"enabled": false,
"start_time": "07:00",
"end_time": "23:00"
},
"tuesday": {
"enabled": false,
"start_time": "07:00",
"end_time": "23:00"
},
"wednesday": {
"enabled": false,
"start_time": "07:00",
"end_time": "23:00"
},
"thursday": {
"enabled": false,
"start_time": "07:00",
"end_time": "23:00"
},
"friday": {
"enabled": false,
"start_time": "07:00",
"end_time": "23:00"
},
"saturday": {
"enabled": false,
"start_time": "07:00",
"end_time": "23:00"
},
"sunday": {
"enabled": false,
"start_time": "07:00",
"end_time": "23:00"
}
}
},
"dim_schedule": {
"enabled": false,
"dim_brightness": 30,
"mode": "global",
"start_time": "20:00",
"end_time": "07:00",
"days": {
"monday": {
"enabled": false,
"start_time": "20:00",
"end_time": "07:00"
},
"tuesday": {
"enabled": false,
"start_time": "20:00",
"end_time": "07:00"
},
"wednesday": {
"enabled": false,
"start_time": "20:00",
"end_time": "07:00"
},
"thursday": {
"enabled": false,
"start_time": "20:00",
"end_time": "07:00"
},
"friday": {
"enabled": false,
"start_time": "20:00",
"end_time": "07:00"
},
"saturday": {
"enabled": false,
"start_time": "20:00",
"end_time": "07:00"
},
"sunday": {
"enabled": false,
"start_time": "20:00",
"end_time": "07:00"
}
}
},
"timezone": "America/New_York",
"location": {
"city": "Tampa",
"state": "Florida",
"country": "US"
},
"display": {
"hardware": {
"rows": 32,
"cols": 64,
"chain_length": 2,
"parallel": 1,
"brightness": 90,
"hardware_mapping": "adafruit-hat",
"scan_mode": 0,
"pwm_bits": 9,
"pwm_dither_bits": 1,
"pwm_lsb_nanoseconds": 130,
"disable_hardware_pulsing": false,
"inverse_colors": false,
"show_refresh_rate": false,
"led_rgb_sequence": "RGB",
"limit_refresh_rate_hz": 100
},
"runtime": {
"gpio_slowdown": 3,
"rp1_rio": 0
},
"double_sided": {
"enabled": false,
"copies": 2,
"axis": "horizontal"
},
"display_durations": {},
"plugin_rotation_order": [],
"use_short_date_format": true,
"vegas_scroll": {
"enabled": false,
"scroll_speed": 50,
"separator_width": 32,
"plugin_order": [],
"excluded_plugins": [],
"target_fps": 125,
"buffer_ahead": 2,
"intra_plugin_gap": 8,
"auto_trim": true,
"trim_threshold": 10,
"content_padding": 8,
"min_plugin_width": 8,
"lead_in_width": 0,
"plugins_per_cycle": 6,
"max_plugin_width_ratio": 3.0,
"dynamic_duration_enabled": true,
"min_cycle_duration": 60,
"max_cycle_duration": 240
}
},
"sync": {
"role": "standalone",
"port": 5765,
"follower_position": "left"
},
"plugin_system": {
"plugins_directory": "plugin-repos",
"auto_discover": true,
"auto_load_enabled": true
},
"web-ui-info": {
"enabled": true,
"display_duration": 10
}
}