fix(ui): hold each splash word for the pause before the readiness check
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 57s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m18s

The dismiss check fired at the instant a word finished laying, so a word
was never held: ЭРУДИТ fell straight into the lobby (too fast) and
ЗАГРУЗКА got no readable pause. The pause was a *leading* gap before the
next word, not a hold after the current one.

Move the hold to after each word and run the check after it: every word
(ЭРУДИТ included) now stays up for PAUSE_MS before the splash either
dismisses or lays the next word. prefixMs = WORD_MS + PAUSE_MS,
cycleMs = 2*(WORD_MS + PAUSE_MS).
This commit is contained in:
Ilia Denisov
2026-06-21 10:42:31 +02:00
parent ba6ee90278
commit 9642cafc1f
3 changed files with 39 additions and 32 deletions
+14 -10
View File
@@ -54,37 +54,41 @@ describe('splash layout', () => {
describe('splash schedule', () => {
const s = splashSchedule();
const HOLD = WORD_MS + PAUSE_MS;
it('lays ЭРУДИТ first, completing by WORD_MS, then checks', () => {
it('lays ЭРУДИТ first, then holds it for PAUSE_MS before the check', () => {
const r = reveals(s.prefix);
expect(r).toHaveLength(6);
expect(r.map((x) => x.atMs)).toEqual([...r.map((x) => x.atMs)].sort((a, b) => a - b));
expect(r[0].atMs).toBe(0);
expect(r[r.length - 1].atMs).toBeLessThan(WORD_MS);
expect(s.prefix.at(-1)).toEqual({ atMs: WORD_MS, kind: 'check' });
expect(s.prefixMs).toBe(WORD_MS);
// The check waits out the whole word plus the readability hold — it never fires the instant
// the last tile lands.
expect(s.prefix.at(-1)).toEqual({ atMs: HOLD, kind: 'check' });
expect(HOLD - WORD_MS).toBe(PAUSE_MS);
expect(s.prefixMs).toBe(HOLD);
});
it('reveals both vertical words per cycle and never re-lays a shared tile', () => {
const r = reveals(s.cycle);
expect(r).toHaveLength(14); // 7 + 7
expect(r.some((x) => x.key === '3-1' || x.key === '3-3')).toBe(false);
// The first vertical reveal waits out the leading pause.
expect(r[0].atMs).toBe(PAUSE_MS);
// ЗАГРУЗКА starts at the cycle start (no leading gap); the hold lives after the word.
expect(r[0].atMs).toBe(0);
});
it('checks at each word boundary and clears at the end of the cycle', () => {
it('checks one hold after each word and clears at the end of the cycle', () => {
const checks = s.cycle.filter((x) => x.kind === 'check').map((x) => x.atMs);
expect(checks).toEqual([PAUSE_MS + WORD_MS, PAUSE_MS + 2 * WORD_MS + PAUSE_MS]);
expect(checks).toEqual([HOLD, 2 * HOLD]);
const last = s.cycle.at(-1)!;
expect(last.kind).toBe('clear');
expect(last.atMs).toBe(s.cycleMs);
expect(s.cycleMs).toBe(2 * WORD_MS + 3 * PAUSE_MS);
expect(s.cycleMs).toBe(2 * HOLD);
});
it('honours overridden timings', () => {
const fast = splashSchedule({ wordMs: 700, pauseMs: 100 });
expect(fast.prefixMs).toBe(700);
expect(fast.cycleMs).toBe(2 * 700 + 3 * 100);
expect(fast.prefixMs).toBe(700 + 100);
expect(fast.cycleMs).toBe(2 * (700 + 100));
});
});