diff --git a/PROJECT.md b/PROJECT.md
index 5837d7e..fd439ea 100644
--- a/PROJECT.md
+++ b/PROJECT.md
@@ -520,6 +520,37 @@ no penalty because there was no way to earn one.
- Ten tests now (26 total), including a named regression for the auto-aim being gone and one that
deals 40 rounds and asserts colours, counts and shapes all move.
+**THIRD PASS, same day — "the drill mode immediately goes into some horrible audio loop" (phone).**
+Never reproduced on desktop, and that is the tell: **three of the four defects behind it can only
+happen on a phone, and my whole verification had been stepping frames with rAF killed on a machine
+whose AudioContext was already running.** All four are in `voiceSeq`, which is new code, and every
+one of them is a rule `voice()` had followed for months and my function quietly broke:
+
+1. **A SUSPENDED AudioContext HAS A FROZEN CLOCK.** On a phone the context starts suspended and
+ `currentTime` does not advance until it resumes. Scheduling four clips at `currentTime + 0, +0.9,
+ +1.7, +2.4` against a frozen clock puts every one of them in the PAST by the time the context
+ actually starts — so they all fire on the same instant, on top of each other, through a 1.3 s
+ reverb tail. Resume FIRST, and re-read the clock after the decode.
+2. **A SECOND CALL LAYERED INSTEAD OF REPLACING.** `voice()` has always enforced one man, one
+ microphone; `voiceSeq` SET that clock (`voiceUntil`) and never checked it. Two calls close
+ together = two overlapping lists. It stops the outstanding one now.
+3. **CANCELLING A LIST HAS TO GIVE BACK THE TIME IT BOOKED.** The first cut of fix 2 stopped the old
+ sources but left `voiceUntil` standing, so the replacement queued behind a list that would never
+ play — measured at **6.6 s of silence** before the call the player is waiting on, and every
+ redeal made it worse. `seqUntilBefore` rewinds the booking.
+4. **THE AMPLIFIER, in drill.js: the empty-court net redealt ON THE SPOT**, which makes the repair
+ rate the FRAME RATE — and every deal calls the umpire. It goes through the ordinary
+ between-rounds beat now, so the worst case is one redeal every `drillBeat` seconds: a mode that
+ limps instead of a phone screaming at you. Pinned by a test that empties the court every frame
+ for six seconds and asserts at most one call per beat.
+
+**And a measurement lesson worth more than the fix.** The first "it's clean" reading was taken by
+counting `fetch`es of `assets/audio/*` — which proved nothing, because `VOICE[]` caches every clip
+and a repeat play never fetches again. The honest probe tags each decoded buffer with the file it
+came from (patch `decodeAudioData`, pair it with the pending fetch URL) and counts
+`AudioBufferSourceNode.start` — then "did two umpire lines overlap" is a real question with a real
+answer. Counting requests measures loading; counting `start()` measures sound.
+
**Open, and every one of them is Oliver's call after he plays it:**
1. **A ball that carries through the lane can complete two steps at once** — measured: one serve
killed bib 1 and then bib 2 standing behind it, banking two correct steps in one shot. Had the
diff --git a/index.html b/index.html
index 66b283a..6934295 100644
--- a/index.html
+++ b/index.html
@@ -399,6 +399,6 @@
-
+