diff --git a/PROJECT.md b/PROJECT.md index 01b614c..10aa77f 100644 --- a/PROJECT.md +++ b/PROJECT.md @@ -451,6 +451,13 @@ followed you onto the silent menu. `menuMusic()` is now the one call that means "we are in the menus": it starts the theme, un-ducks it, and ducks both court beds. `ambient()` and `zombieMusic()` un-duck themselves for the same reason — the SECOND run would otherwise have been played in the silence the menu left them in. + +**...and each of those un-ducks ITS OWN BED ONLY.** The first cut had `ambient()` un-duck both +court beds together, which put the splatter music back on in classic tennis for the rest of the +session — Oliver caught it on the retest: *"after playing the splatter gore, the zombie background +sound also keeps playing in the normal tennis game."* LEAVING the court is one event and ducks both +beds; ARRIVING is not. The ambience belongs to every mode, the zombie bed belongs to the gore, and +only `enterSplatter` may bring that one up. **The rule, for anything added here later: a bed's start function must also un-duck it, because starting it is a no-op the second time and ducking it is not.** `audio.bedTest` exposes the duck set to the suite (same idea as `netTest`), which is what makes this testable at all — the beds diff --git a/index.html b/index.html index d77d921..4bbd5fa 100644 --- a/index.html +++ b/index.html @@ -547,6 +547,6 @@ if (location.hostname !== 'splatter-tennis.wuwi.one') { - + diff --git a/src/audio.js b/src/audio.js index 9a78f0d..59353b6 100644 --- a/src/audio.js +++ b/src/audio.js @@ -164,7 +164,15 @@ function loopBed(file, lowcut, vol, loopEnd = 0) { // whole-scene atmosphere, from the first tap. Un-ducks as well as starts, for the same reason // menuMusic does: loopBed no-ops once the bed exists, so a SECOND run would have opened over the // silence the menu left it in. -export const ambient = () => { loopBed('tennis-ambient-loop.wav', 500, 0.035); courtDuck(false); }; +// ...but ONLY ITS OWN BED. It briefly un-ducked both court beds together (courtDuck) and that put +// the splatter music back on in classic tennis for the rest of the session, because every run +// starts here (Oliver, 2026-08-15: "after playing the splatter gore, the zombie background sound +// also keeps playing in the normal tennis game"). Leaving the court is one event and ducks both; +// ARRIVING is not — the ambience belongs to every mode and the zombie bed belongs to the gore. +export const ambient = () => { + loopBed('tennis-ambient-loop.wav', 500, 0.035); + fadeBed('tennis-ambient-loop.wav', false); +}; // ...and the splatter bed, same treatment: 5.9 MB of 24-bit WAV down to 287 KB. Its master is // 982597 frames at 48 kHz, which is the un-round 20.47077 s below — an AAC loop needs the SOURCE // length as loopEnd, not the decoder's padded one. See the note on menuMusic. diff --git a/src/render.js b/src/render.js index 717863c..a483f6f 100644 --- a/src/render.js +++ b/src/render.js @@ -221,7 +221,7 @@ function farB(z) { // Asset cache-buster — bump with main.js?v=N (see the img.src note below). EXPORTED because the // menu screens hang tags on the same files, and two copies of this number is one copy too // many: the one that does not get bumped is the one that serves a cached 404. -export const SPRV = 130; +export const SPRV = 131; // The boot bar in main.js watches this. Nothing here CHANGES when things load — every sprite was // already requested at import — it just makes the wait visible and holds the menu back until the // last sheet has landed. See the onerror note below for the other half of "sometimes missing". diff --git a/test.js b/test.js index 2011e29..ecbc642 100644 --- a/test.js +++ b/test.js @@ -869,6 +869,14 @@ test('the menus own the theme, and hand the court back its silence', () => { assert.ok(d.has(AMB) && d.has(ZOM), '...and the court does not follow you into the menu'); audio.ambient(); audio.zombieMusic(); // ...and the NEXT run is not played in silence assert.ok(!d.has(AMB) && !d.has(ZOM), 'a second run sounds like the first'); + // ...but the SPLATTER BED IS THE GORE'S ALONE. Every run of every mode starts the ambience, so + // un-ducking both court beds there put the zombie music back on in classic tennis for the rest of + // the session (Oliver: "after playing the splatter gore, the zombie background sound also keeps + // playing in the normal tennis game"). Leaving the court is one event; arriving is not. + audio.menuMusic(); // back to the menu after a gore run + audio.ambient(); // ...and into a game of plain tennis + assert.ok(!d.has(AMB), 'the ambience belongs to every mode'); + assert.ok(d.has(ZOM), 'the splatter music does NOT follow you into classic tennis'); audio.menuMusic(); });