mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-08-10 13:18:04 +00:00
fix(vegas): apply the runt floor to the multi-row rotation too
The floor only guarded the single-image path. I had reasoned the row
path could not produce a runt because it wraps, which is wrong: wrapping
only helps when the row wrapped to actually fits. Rows of 450, 450 and
100 against a 512px budget give the 100 a pass of its own -- two seconds
against nine, which is the symptom this branch exists to remove.
Reproduced before changing anything:
pass 1: 450px pass 2: 450px pass 3: 100px
A window may now overrun the budget while it is still shorter than the
floor, bounded at the same 1.5 budgets the single-image path allows, so
the short row is carried with its neighbour instead of standing alone.
pass 1: 450px pass 2: 450px pass 3: 550px
A next row too wide to absorb within that cap still leaves a short
window standing -- rows of 900 and 100 keep alternating. Merging them
would mean a window of nearly two budgets, and the rule that always
shows an oversized first row already makes the same trade.
Three regression tests: the reported shape, that the overrun stays
bounded when a row cannot be absorbed, and that absorbing never drops a
row from the rotation. The single-image path is untouched -- the four
plugins that actually hit the cap on a live panel replay identically.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Udr6MfaFLUPhX5Fgo67Jf5
This commit is contained in:
co-authored by
Claude Opus 5
parent
896ef4cb2e
commit
acf82ed76c
@@ -552,13 +552,28 @@ class PluginAdapter:
|
||||
|
||||
# Walk forward from the rotation offset, taking whole items only, so a
|
||||
# cut never lands in the middle of one.
|
||||
#
|
||||
# A window may overrun the budget while it is still shorter than the
|
||||
# runt floor, for the same reason _merge_trailing_runt exists on the
|
||||
# single-image path: a pass far shorter than its neighbours reads as
|
||||
# the display failing rather than as a rotation. Rows of 450, 450 and
|
||||
# 100 against a 512px budget used to give the 100 a pass of its own --
|
||||
# two seconds against nine. Wrapping does not prevent that, because it
|
||||
# only helps when the row wrapped to actually fits.
|
||||
floor = budget // 2
|
||||
for step in range(len(images)):
|
||||
img = images[(start + step) % len(images)]
|
||||
cost = img.width
|
||||
if selected:
|
||||
cost += self._row_gap(selected[-1], img)
|
||||
if selected and used + cost > budget:
|
||||
break
|
||||
# Keep the overrun bounded at the same 1.5 budgets the
|
||||
# single-image path allows. A next row too wide to absorb
|
||||
# leaves a short window standing -- better than a window of
|
||||
# 1.9 budgets, and the same trade the always-take-the-first
|
||||
# rule below already makes.
|
||||
if used >= floor or used + cost > budget + floor:
|
||||
break
|
||||
selected.append(img)
|
||||
used += cost
|
||||
consumed += 1
|
||||
|
||||
@@ -1731,6 +1731,56 @@ class TestTrailingRuntWindow:
|
||||
"a strip this close to the budget should be shown whole every "
|
||||
"time, not split into a big pass and a sliver; got %r" % widths)
|
||||
|
||||
def test_a_short_final_row_window_is_not_left_alone(self):
|
||||
# The multi-row path has the same fault as the single-image one, and
|
||||
# wrapping does not save it: rows of 450/450/100 against a 512px budget
|
||||
# gave the 100 a pass of its own, two seconds against nine, because the
|
||||
# row it wrapped to did not fit either.
|
||||
adapter = adapter_with(content_padding=0, max_plugin_width_ratio=1.0,
|
||||
intra_plugin_gap=0, min_content_separation=0)
|
||||
rows = [canvas([(0, 450)], width=450),
|
||||
canvas([(0, 450)], width=450),
|
||||
canvas([(0, 100)], width=100)]
|
||||
|
||||
widths = []
|
||||
for _ in range(6):
|
||||
adapter.invalidate_cache('rows')
|
||||
shown = adapter.get_content(NativePlugin(list(rows)), 'rows')
|
||||
widths.append(sum(img.width for img in shown))
|
||||
|
||||
assert min(widths) >= DISPLAY_W // 2, (
|
||||
"a row window should not be a sliver, got %r" % widths)
|
||||
assert max(widths) <= DISPLAY_W * 1.5, (
|
||||
"absorbing a short row must stay bounded, got %r" % widths)
|
||||
|
||||
def test_row_rotation_still_covers_every_row(self):
|
||||
# Absorbing a short tail must not drop rows from the rotation.
|
||||
adapter = adapter_with(content_padding=0, max_plugin_width_ratio=1.0,
|
||||
intra_plugin_gap=0, min_content_separation=0)
|
||||
rows = [canvas([(0, 450)], width=450),
|
||||
canvas([(0, 450)], width=450),
|
||||
canvas([(0, 100)], width=100)]
|
||||
|
||||
seen = set()
|
||||
for _ in range(8):
|
||||
adapter.invalidate_cache('rows')
|
||||
for img in adapter.get_content(NativePlugin(list(rows)), 'rows'):
|
||||
seen.add(img.width)
|
||||
assert seen == {450, 100}, "rotation never showed every row: %r" % seen
|
||||
|
||||
def test_a_row_too_wide_to_absorb_still_bounds_the_overrun(self):
|
||||
# When the next row cannot be taken without blowing past 1.5 budgets,
|
||||
# a short window is the lesser evil — the same trade the always-show-
|
||||
# the-first-row rule already makes.
|
||||
adapter = adapter_with(content_padding=0, max_plugin_width_ratio=1.0,
|
||||
intra_plugin_gap=0, min_content_separation=0)
|
||||
rows = [canvas([(0, 900)], width=900), canvas([(0, 100)], width=100)]
|
||||
for _ in range(4):
|
||||
adapter.invalidate_cache('wide')
|
||||
shown = adapter.get_content(NativePlugin(list(rows)), 'wide')
|
||||
assert sum(i.width for i in shown) <= 900, (
|
||||
"must not merge a row that overruns the cap")
|
||||
|
||||
def test_a_genuinely_long_strip_still_gets_capped(self):
|
||||
# Absorbing runts must not become "never cap anything".
|
||||
adapter = adapter_with(content_padding=0, max_plugin_width_ratio=1.0)
|
||||
|
||||
Reference in New Issue
Block a user