diff --git a/PROJECT.md b/PROJECT.md
index da0adab..8e12d7d 100644
--- a/PROJECT.md
+++ b/PROJECT.md
@@ -5066,6 +5066,31 @@ Release cache version: `main.js?v=353`. Pre-release validation: 142 game tests a
menu browser checks pass. Deploy staging first, verify the served release, then promote the same
commit to production. Staging URL: `https://splatter-tennis-staging.wuwi.one`.
+## Session log: THE FOUR MISSING SOUNDS (2026-09-23, v355)
+
+Oliver: *"mach die sounds, schlag mir quellen vor"* (OPEN 13). Source decision: **synthesize all four
+in `audio.js`**, the way every weapon already is — no sample, no licence, no download, and each one
+pitches off the existing `size` = bodySize convention. Samples stay the fallback per sound if one
+reads flat on the phone (candidates offered: Kenney.nl CC0 packs, Freesound filtered to CC0, the
+Sonniss GDC bundle, or recording a real trolley castor on the iPhone for the squeak).
+
+- **Machine castor** `squeak()` — stick-slip: two or three FM-wobbled triangle chirps, 60% of the
+ existing 0.4 s grind ticks while it rolls. The approach cue; the click stays its fire tell.
+- **Fire** — `crackle()` (tiny highpassed pops over a hiss) on ONE court clock in `updateZombies`,
+ rate and level ~sqrt(bodies burning), so a lit wave is denser, never N loops summed. `whump()` is
+ the body catching, fired from `ignite()` and throttled to once per 0.25 s for an epidemic.
+- **Gun** `gunshot()` — crack + punch + action, every part < 60 ms, every other round as before (it
+ replaced the racket pock). The dead `w.ignite ? 300` branch went with it (the flamer has its own case).
+- **Boss** — ONE shared voice for all ten designs, pitched by body (the splat() rule). `stomp()` per
+ drawn stride (off `zb.dist`/`bruteStride`, so it lands with the foot; crunch carries it on phone
+ speakers, the sub is for headphones); `growl()` rising over the whole wind-up (the boss's tell,
+ replacing the click it shared with the machine), a bark on release, a throttled (0.9 s) pain growl
+ from `wound()`, and a long falling roar in `bossDeath`.
+
+All seven log to `sfxTest`; one test pins every event, one whump per epidemic, and the crackle
+clock staying court-wide. Offline-rendered peaks sit between pock and splat (nothing clips).
+147 tests, default seed plus 1, 42, 1234, 20260907. **Needs Oliver's ear on the phone.**
+
## Session log: VS A FRIEND — the friend's racket makes a noise, and the duel has a rematch (2026-09-11, v354)
Oliver, two finds off a two-phone session: *"im vs. a friend mode wird der ballsound des gegners
@@ -5157,7 +5182,9 @@ Nothing here is broken; these are known gaps and unverified risks.
that catches your shots. Recorded so the ideas are not lost, not as a to-do.
12. **`ump_no_deal` does not exist.** Oliver asked for its removal; `ump_no_appeal` was removed as the
closest match. Worth one confirmation that no other line is still unwanted.
-13. **Four sounds are missing and need RESEARCH first** (find/choose the source before implementing —
+13. **~~Four sounds are missing~~ BUILT 2026-09-23, SYNTHESIZED (v355) — see the session log; the ear
+ verdict is Oliver's phone.** Original note kept for the reasoning:
+ **Four sounds are missing and need RESEARCH first** (find/choose the source before implementing —
every existing sfx is either synthesised in `audio.js` or a wav in `assets/audio/`):
- the **ball machine** while it moves — a squeaking wheel/castor, so the furniture announces
itself. It already telegraphs its shot (`machWind` 0.9 s); this is the *approach* cue.
diff --git a/index.html b/index.html
index f579728..ed478fc 100644
--- a/index.html
+++ b/index.html
@@ -1140,6 +1140,6 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
REC
-
+