Default lookahead 8s -> 4s: don't hang unsung lines through gaps

Field report ("Marks Of The Evil One", authored syllable-timed lyrics
with short lines around ~6s instrumental gaps): with an 8s lookahead the
banner no longer hid during those gaps — it sat there showing the next
dim lines while nothing was being sung, which reads as "the lyrics are
out of sync". The highlight clock was never wrong (invariant-tested);
the preview policy was.

4s keeps the full upcoming-context win during dense singing (next lines
start within a couple of seconds) while instrumental gaps go blank like
they used to. Still live-tunable via highway.setLyricsDisplay(). New
regression test pins the mid-gap hide + within-lookahead return.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: topkoa <topkoa@gmail.com>
This commit is contained in:
topkoa
2026-07-19 14:01:13 -04:00
co-authored by Claude Fable 5
parent 5a35d1c0b4
commit dd6e971390
3 changed files with 25 additions and 5 deletions
+19 -3
View File
@@ -47,16 +47,16 @@ function makeStorage(initial) {
test('cfg reader: defaults, clamping, corrupt JSON, live re-read', () => {
const storage = makeStorage();
const read = loadCfgReader(storage);
assert.deepEqual(read(), { upcomingLines: 2, lookaheadSec: 8 });
assert.deepEqual(read(), { upcomingLines: 2, lookaheadSec: 4 });
storage.setItem('lyricsDisplay', JSON.stringify({ upcomingLines: 99, lookaheadSec: 0 }));
assert.deepEqual(read(), { upcomingLines: 4, lookaheadSec: 1 }); // clamped
storage.setItem('lyricsDisplay', '{not json');
assert.deepEqual(read(), { upcomingLines: 2, lookaheadSec: 8 }); // corrupt -> defaults
assert.deepEqual(read(), { upcomingLines: 2, lookaheadSec: 4 }); // corrupt -> defaults
storage.setItem('lyricsDisplay', JSON.stringify({ upcomingLines: 1 }));
assert.deepEqual(read(), { upcomingLines: 1, lookaheadSec: 8 }); // partial merges over defaults
assert.deepEqual(read(), { upcomingLines: 1, lookaheadSec: 4 }); // partial merges over defaults
});
// ── drawLyrics harness ───────────────────────────────────────────────────
@@ -153,6 +153,22 @@ test('pre-song preview appears within lookahead, not before', () => {
assert.deepEqual(near.calls.map(c => c.text), ['one']);
});
test('banner hides during an instrumental gap, returns within lookahead', () => {
// The "Marks Of The Evil One" report: short authored lines around a ~6s
// instrumental gap. Mid-gap the banner must hide (nothing is being
// sung), then return once the next line is inside the lookahead.
const lyrics = [syl(10, 'sung', true), syl(17, 'next', true)];
const draw = loadDrawLyrics({ upcomingLines: 2, lookaheadSec: 4 });
const midGap = makeCtx();
draw({ lyrics, ctx: midGap, currentTime: 12 }, 2000, H); // next is 5s out
assert.equal(midGap.calls.length, 0, 'mid-gap: no unsung lyrics on screen');
const nearNext = makeCtx();
draw({ lyrics, ctx: nearNext, currentTime: 13.5 }, 2000, H); // 3.5s out
assert.deepEqual(nearNext.calls.map(c => c.text), ['sung', 'next']);
});
test('banner hides after the last line ends with nothing upcoming', () => {
const lyrics = [syl(10, 'one', true)];
const draw = loadDrawLyrics({ upcomingLines: 2, lookaheadSec: 8 });