diff --git a/PROJECT.md b/PROJECT.md
index 2ad5fe2..e8ed83b 100644
--- a/PROJECT.md
+++ b/PROJECT.md
@@ -10,7 +10,8 @@ detail of any individual change, read the git log.
the state-machine contracts that have historically produced the show-stoppers. The Dockerfile runs
it as a build stage, so a red suite cannot deploy — but finding out from Coolify is the slow way.
-Last updated 2026-08-11 (beats.js extraction, this file renamed, tests gate the build). **Start with "How to work on this", "OPEN — leftovers" and "LATER —
+Last updated 2026-08-11 (the horde's breather restored, the boss walk-on overlay; beats.js
+extraction, this file renamed, tests gate the build). **Start with "How to work on this", "OPEN — leftovers" and "LATER —
shipping it"** (all three just above the CLOSED sections) — the first tells you how Oliver's feedback
should be handled, the second is everything known to be outstanding in the game, the third is the
demo/store/campaign plan, which is decided but entirely unbuilt. All four playtest rounds are closed
@@ -101,6 +102,11 @@ zombie (now an occasional opponent, `TUNE.racketEvery`, not the every-game fixtu
left, all on `easeOutBack`; every x is `Math.round`ed, because a fractional x on a nearest-neighbour
scaled image re-snaps its pixel columns (the boiling that once read as HUD flicker). A modal with no
`in` field draws fully landed, so older/test call sites still work.
+ **`m.hold` is an optional self-dismiss clock** (seconds, ticked only once `m.in` has landed): the
+ modal announces, holds the frozen court, and lets go by itself. The chapter-boss walk-on is the
+ case it exists for — an announcement whose job is to PREPARE the player, where demanding a tap to
+ start a fight he did not ask for would be the control steal the popup policy exists to prevent.
+ A tap still skips it. Every other modal has no `hold` and waits for the player, as before.
While `m.in < 1`, `dismissModal` **returns true without dismissing** — it swallows the gesture but
won't let a panic tap that was already mid-air skip a popup nobody has read. Returning false there
would drop that tap through into a serve instead.
@@ -380,6 +386,33 @@ zombie (now an occasional opponent, `TUNE.racketEvery`, not the every-game fixtu
`RETURNED`, `DOUBLE KILL`) is a footnote to something you already watched happen. Sharing the loud
FACE with the milestone cards is right; sharing their SIZE made every footnote read as a milestone.
+## Session log: nothing arrives unannounced (2026-08-11, same day)
+
+Oliver's retest: *"the horde is not announced anymore"* and *"bosses are just there now"*. Both were
+true, and both were the same mistake — the announcement fired on the same frame as the thing it
+announced, so it was over before it registered.
+
+- **The horde gets its breather back.** `warnHorde()` (beats.js) raises THEY KEEP COMING and holds
+ an ordinary 'over' beat for `TUNE.hordeWarnT` (2.4 s); `startHorde()` runs when it expires. The
+ cheat (`forceZombies`) still starts one instantly — it is a cheat.
+ The breather runs on **its own clock** (`G.hordeWarn`, ticked in update above the 'over' block),
+ deliberately NOT on the 'over' beat it also sets: any beat that moves the phase underneath it
+ would strand the latch forever, which is the `mgT` lock this file already has a section about.
+ `startHorde()` **returns a bool** now, and if the court has been taken when the warning expires
+ the caller re-arms `pendingHorde` — the set gets its horde later rather than losing it silently.
+- **The chapter boss walks on behind an OVERLAY**, not a band card: the boss's name in `UI.bad` over
+ `ENTERED THE COURT`. It is a modal (a boss stage is guaranteed calm — `startBossFight` clears the
+ court two lines earlier, so this is on the sanctioned list) with the new `m.hold` self-dismiss
+ clock, `TUNE.bossIntroT` = 3 s. A card in the routine-set slot and the routine-set size is why the
+ biggest fight in the game read as the boss simply being there.
+- **The flamer does not trigger a horde** (his "vague feeling"). Arming any `kind: 'burst'` tin puts
+ you in the same CONTROL MODE a horde does — racket auto-fires, the finger steers you absolutely,
+ no serving — so a flamer or gun genuinely feels like a horde starting. Nothing in the weapon paths
+ touches `startHorde`; with the horde announced again the two are told apart by the announcement.
+- Three tests added (15 total): the wave may not land on the frame of its own announcement, a mode
+ that steals the court during the warning re-arms the latch, and the boss overlay names the boss
+ and hands the court back without a tap.
+
## Session log: beats.js, the rename, and the build gate (2026-08-11, same day)
Oliver's structural question ("framework? smaller files?") answered with a deliberate NO on both —
diff --git a/index.html b/index.html
index ec8dabb..f906025 100644
--- a/index.html
+++ b/index.html
@@ -398,6 +398,6 @@
-
+