Keep the scheduler check in e2e 34 from flaking on a clock tick (#366)

The check "the scheduler plans a looped bar" read the position and
the audio clock one after the other and compared them to 1 tick. In
Chrome the audio clock moves on by a render block even inside one
task, so now and then the two reads lay 2.9 ms (3.5 ticks) apart.
It was red in CI task 580 and in 2 of 5 runs during #351.

- The clock is now read before and after the position. The position
  has to lie between the two, with 1 tick to spare, also across the
  seam of the loop.
- The scheduler itself is unchanged, it was never wrong.

Tests:
- e2e 34 (melektaus-atx): ALL PASSED, 93 checks, 3 of 3 runs.
- Counter-probe with the position 10 ticks off: this check red.
- Only the e2e file changed. node --check, Prettier and ESLint are
  clean on it, the gate did not run again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-10-09 09:51:15 +02:00
1 parent f840ec3c44
commit 87ec62aace
1 file changed
+14 -4
+14 -4
View File
@@ -1714,6 +1714,8 @@ export async function sound(t) {
// quarters at 150 BPM in a loop: every note is planned before its second comes, each 0.4 s
// after the one before, also across the seam into the next round. Where the music stands
// is the audio clock less the output latency, counted from the first note.
// #366: the audio clock moves on by a render block even between two reads in one expression, so
// it is read before and after the position, and the position lies between the two.
const bar = [60, 62, 64, 65].map((pitch, i) => ({
tick: i * 480,
length: 240,
@@ -1730,13 +1732,21 @@ export async function sound(t) {
40,
);
const where = await json(
'({ position: __gm.music.position(), clock: __gm.sound.clock(), latency: __gm.music.latency() })',
`({
before: __gm.sound.clock(),
position: __gm.music.position(),
after: __gm.sound.clock(),
latency: __gm.music.latency(),
})`,
);
await evaluate('__gm.music.stop()');
const musicOff = await waitFor(stats, (s) => s.playing === 0);
const apart = planned.slice(1, 9).map((p, i) => +(p.at - planned[i].at).toFixed(6));
const ahead = planned.map((p) => +(p.at - p.planned).toFixed(3));
const heardTicks = (((where.clock - where.latency - planned[0].at) * 150 * 480) / 60) % 1920;
const ticksHeardAt = (clock) => ((clock - where.latency - planned[0].at) * 150 * 480) / 60;
const heardTicks = [ticksHeardAt(where.before), ticksHeardAt(where.after)];
// How far the position lies past the first read, around the loop of 1920 ticks.
const pastBefore = (((where.position.ticks - heardTicks[0]) % 1920) + 1920) % 1920;
check(
'the scheduler plans a looped bar on the audio clock: every note ahead of its second, 0.4 s apart across the seam',
planned.length >= 9 &&
@@ -1747,11 +1757,11 @@ export async function sound(t) {
planned.every((p) => p.voice > 0 && Math.abs(p.duration - 0.2) < 1e-9 && p.tempo === 150) &&
apart.every((gap) => Math.abs(gap - 0.4) < 1e-6) &&
ahead.every((s) => s > 0 && s <= 0.16) &&
Math.abs(where.position.ticks - heardTicks) < 1 &&
(pastBefore <= heardTicks[1] - heardTicks[0] + 1 || pastBefore > 1919) &&
where.position.bar === 1 &&
musicOff.playing === 0 &&
(await json('__gm.music.playing()')) === false,
JSON.stringify({ apart, ahead, where, heardTicks, playing: musicOff.playing }),
JSON.stringify({ apart, ahead, where, heardTicks, pastBefore, playing: musicOff.playing }),
);
// A note planned ahead through the pitch worklet starts on its sample, also inside a render block: