Everything written this session that has not been through the device: rounds 43 to 48 (each patch adversarially verified, 43's transient-notice expectation fixed), a partial round 49, the pipeline scripts, the Android Auto design and the icon comparison mock. Also the two harness changes that were never A/B'd — adaptive waits, which may only shorten a declared wait and never lengthen it, and a ship that runs the mock walk and the deploy at the same time. The A/B and its 255-step baseline are in handoff/tools; main stays at what the phone has actually walked.
84 lines
5.8 KiB
Plaintext
84 lines
5.8 KiB
Plaintext
/* ── the queue window, across the bridge ───────────────────
|
|
Round forty-six put a bounded window of the queue into every
|
|
card update — the current item and ten either side, each with
|
|
its id, title, artist, album, duration and art key, plus where
|
|
the current one sits among them — and left MediaService
|
|
ignoring every bit of it, because media3's
|
|
SimpleBasePlayer.getState() will want a playlist and an index,
|
|
and that data should be a round old before anything depends on
|
|
it. Which means every check above this one is blind to it: a
|
|
window that arrived empty, an index off by one, or a field the
|
|
bridge dropped because a key was spelled the wrong way would
|
|
each look exactly like success.
|
|
|
|
So: what the phone SENT — __ss.card, the mirror
|
|
media.svelte.js keeps beside xfernote's — against what the
|
|
plugin LOGGED on the far side, field for field, through a next
|
|
and a previous. Field for field is the point: a camelCase key
|
|
spelled snake_case arrives as nothing at all on both hops of
|
|
this bridge, with no error anywhere, so `art` (the only
|
|
camelCase field an entry has) is compared as well as the size
|
|
and the index.
|
|
|
|
ARMED TO A KNOWN ROW, index one, rather than to wherever the
|
|
walk left off. Three things fall out of that and every one of
|
|
them is load-bearing: there is a row on either side, so next()
|
|
steps instead of stopping at the end and prev() steps instead
|
|
of seeking to zero at the top; the track starts at zero, so
|
|
prev() is still a step two and a half seconds later rather
|
|
than the seek it becomes past five, and no track ends
|
|
underneath the three readings; and while the queue index is
|
|
under ten the window's own index IS the queue index, because
|
|
the slice starts at zero and nothing is clamped off the front
|
|
— which is what makes `got.i` comparable to `base` below
|
|
without recomputing the arithmetic here. Nothing is added,
|
|
removed or reordered: this is the queue the walk has been
|
|
playing all along, stepped one row forward and one row back
|
|
inside itself.
|
|
|
|
Not skipped when it cannot be armed: a queue shorter than
|
|
three rows, or repeat-one — under which next() seeks to zero
|
|
instead of stepping — FAILS, with `armed` in the message
|
|
saying which. A check that passes by not running is the thing
|
|
this walk is most careful never to be. */
|
|
const CARD = `(function () { var s = window.__ss; if (!s || !s.card || !s.player) return null; var c = s.card, p = s.player; return { u: c.updates, n: c.window, i: c.index, art: c.art, at: c.at, qi: p.qi, title: p.track ? p.track.title : '' }; })()`;
|
|
const armed = await cdp.evaluate(`(function () { var p = window.__ss && __ss.player; return p ? { n: p.queue.length, qi: p.qi, rep: p.repeat } : null; })()`);
|
|
const base = 1;
|
|
const room = (armed?.n ?? 0) >= 3 && armed.rep !== 2;
|
|
const wins = [];
|
|
if (room) {
|
|
await cdp.evaluate(`__ss.playQueueAt(${base})`);
|
|
await sleep(2500);
|
|
wins.push(["here", await cdp.evaluate(CARD), mediaWindow()]);
|
|
await cdp.evaluate(`__ss.next()`);
|
|
await sleep(2500);
|
|
wins.push(["next", await cdp.evaluate(CARD), mediaWindow()]);
|
|
await cdp.evaluate(`__ss.prev()`);
|
|
await sleep(2500);
|
|
wins.push(["prev", await cdp.evaluate(CARD), mediaWindow()]);
|
|
}
|
|
/* each reading: the window SENT is a real one — at least two
|
|
entries, the index inside it, and the entry AT the index is the
|
|
track on the bar, which is what an off-by-one fails even when
|
|
the counts agree — and what the plugin LOGGED is that same
|
|
window, field for field. */
|
|
const sane = ([, sent, got]) => !!sent && sent.n >= 2 && sent.i >= 0 && sent.i < sent.n
|
|
&& !!sent.at && sent.at === sent.title
|
|
&& got.n === sent.n && got.i === sent.i && got.art === sent.art
|
|
&& got.at === String(sent.at).trim();
|
|
const winOk = room && wins.length === 3 && wins.every(sane)
|
|
/* and it MOVED. The queue index steps forward and back; the
|
|
LOGGED window index steps with it, which is the assertion
|
|
an off-by-one or a frozen index dies on. And each reading
|
|
is a FRESH one at both ends — the phone pushed again
|
|
(`u`) and the plugin logged again (`lines`) — because a
|
|
prev() that lands back on the row it started from reads
|
|
identically to a stale line otherwise. */
|
|
&& wins[0][1].qi === base && wins[1][1].qi === base + 1 && wins[2][1].qi === base
|
|
&& wins[0][2].i === base && wins[1][2].i === base + 1 && wins[2][2].i === base
|
|
&& wins[1][1].u > wins[0][1].u && wins[2][1].u > wins[1][1].u
|
|
&& wins[1][2].lines > wins[0][2].lines && wins[2][2].lines > wins[1][2].lines;
|
|
if (!winOk) problems.push("EXPECT queue-window " + JSON.stringify({ armed, base, wins }));
|
|
console.log((winOk ? " ok " : " FAIL ") + "queue-window".padEnd(16) + " "
|
|
+ JSON.stringify({ armed, seen: wins.map(([k, sent, got]) => [k, sent && sent.qi, got.n, got.i, got.art, got.at]) }));
|