The umpire is one man with one microphone, and voiceSeq forgot it
"the drill mode immediately goes into some horrible audio loop" — on a phone, never on desktop, and that is the tell. Three of the four defects can only happen on a phone, and every one of them is a rule voice() had followed for months that my new function quietly broke. - A SUSPENDED AudioContext HAS A FROZEN CLOCK. Scheduling four clips at currentTime +0/+0.9/+1.7/+2.4 while the context is suspended puts all of them in the past by the time it starts, so they fire on one instant through a 1.3s reverb tail. Resume first, re-read the clock after the decode. - A second call layered instead of replacing. voiceSeq SET voiceUntil and never checked it. It stops the outstanding list now. - ...and cancelling a list has to give back the time it booked, or the replacement queues behind a list that will never play. Measured at 6.6s of silence before the call the player is waiting on. - The amplifier was in drill.js: the empty-court net redealt on the spot, making the repair rate the frame rate — and every deal calls the umpire. It goes through the ordinary between-rounds beat now. Verified by tagging each decoded buffer with its source file and counting start() calls: one drill_call, three colours, spaced, zero overlapping voice lines. The earlier "clean" reading counted fetches, which proves nothing — VOICE[] caches, so a repeat play never fetches again. 27 tests; the new one empties the court every frame for six seconds and asserts at most one umpire call per beat. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ef8a413031
commit
435a470ec7
5 files changed
+93
-7
No files matched your search
@@ -402,6 +402,29 @@ test('being caught costs the point, not the run', () => {
|
||||
assert.equal(zombies.length, 0, 'and the court is cleared for the next one');
|
||||
});
|
||||
|
||||
test('an emptied court cannot machine-gun the umpire', () => {
|
||||
// Oliver, on a phone: "the drill mode immediately goes into some horrible audio loop". The trigger
|
||||
// was never reproduced on desktop (a suspended AudioContext has a frozen clock, so a scheduled
|
||||
// sequence all fires at once on resume — see voiceSeq), but the AMPLIFIER was here: the
|
||||
// empty-court net used to redeal on the spot, which makes the repair rate the frame rate, and
|
||||
// every deal calls the umpire. Whatever empties the court, it must never announce more than once
|
||||
// per beat.
|
||||
drillPlaying();
|
||||
let deals = 0;
|
||||
const seq0 = G.drill.seq;
|
||||
for (let i = 0; i < 60 * 6; i++) { // six seconds with the court emptied every single frame
|
||||
zombies.length = 0;
|
||||
update(DT);
|
||||
if (G.drill.seq !== seq0 && G.drill.callT > 0 && zombies.length) { deals++; }
|
||||
if (zombies.length) zombies.length = 0; // ...and emptied again, so it can never settle
|
||||
}
|
||||
const repairs = G.missLog.filter(m => m.result === 'INVARIANT').length;
|
||||
assert.ok(repairs > 0, 'the net notices and leaves evidence');
|
||||
assert.ok(repairs <= 6 / TUNE.drillBeat + 2,
|
||||
'at most one repair per drillBeat, not one per frame — got ' + repairs);
|
||||
assert.ok(deals <= 6 / TUNE.drillBeat + 2, 'and at most one deal (one umpire call) per beat — got ' + deals);
|
||||
});
|
||||
|
||||
test('endDrill hands the court back', () => {
|
||||
drillPlaying();
|
||||
endDrill();
|
||||
|
||||
Reference in new issue
Block a user