diff --git a/HANDOFF.md b/HANDOFF.md index d38c125..93fd126 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -463,8 +463,16 @@ included, or a duck silently no-ops. 48.000 s master to a 48.0174 s buffer — 17 ms of silence spliced in at every seam, once a loop, on the title screen. `menuMusic` passes `48`. Re-encode the file and re-measure: `fetch(f).then(r=>r.arrayBuffer()).then(a=>new AudioContext().decodeAudioData(a)).then(b=>b.duration)`. -`zombie-mode.wav` (5.9 MB) is the other candidate for this treatment, and it loads mid-match rather -than up front, so it matters less. +`zombie-mode` got the same treatment: 5.9 MB of 24-bit WAV to 287 KB, `loopEnd` 20.47077 (982597 +frames at 48 kHz — an un-round master, and the decoder returns 20.4787, so 7.9 ms of padding is +trimmed). Both beds are AAC now; nothing in `assets/audio/` is still a multi-megabyte WAV. + +**The opponent was running with empty hands.** `o_run1`/`o_run2` had no racket in either fist, and +the cause was the batch prompt: `opponent_run` never said the word "racket" and its reference sheet +was built from the racket-less frames it was replacing, so the generator had nothing to copy one +from. Fixed by making it a `ref_only` batch off `o_idle1` — same man, front-facing, racket +unmistakably in his right hand — with the subject spelling out that he still carries it mid-stride. +**When a prop goes missing from a sprite, check the batch's `subject` before blaming the roll.** Measuring which audio files actually loaded: `performance.getEntriesByType('resource')` is USELESS here — the ~200 sprite PNGs blow past the 250-entry resource-timing buffer and every later fetch is diff --git a/assets/_regen_batches.json b/assets/_regen_batches.json index 8057518..0b550a3 100644 --- a/assets/_regen_batches.json +++ b/assets/_regen_batches.json @@ -145,8 +145,12 @@ "o_run1", "o_run2" ], - "subject": "Two frames of the same tennis opponent in a RED shirt and dark shorts running across the court towards the camera, two stages of one stride, drawn as coarse low-resolution pixel art.", - "anchor": 0 + "ref_only": [ + "o_idle1" + ], + "anchor": 0, + "check": "same", + "subject": "The reference is ONE tennis opponent in a RED polo shirt, dark grey shorts and a white headband, seen from the FRONT, gripping a tennis racket in his RIGHT hand. Draw TWO frames of that SAME man, same size, same clothes, same face, same front-facing view, RUNNING across the court towards the camera as two stages of one stride: the first with his left leg forward and his right leg trailing, the second with the legs reversed. In BOTH frames he is STILL HOLDING THE TENNIS RACKET in his right hand, the same racket as the reference \u2014 head, strings, throat and grip all drawn, carried out to his side at about waist height as a runner carries it. His other arm swings free. The racket must be clearly visible and fully inside the canvas in both frames; neither hand is ever empty." }, "ump_boy": { "sprites": [ diff --git a/assets/audio/zombie-mode.m4a b/assets/audio/zombie-mode.m4a new file mode 100644 index 0000000..14c3265 Binary files /dev/null and b/assets/audio/zombie-mode.m4a differ diff --git a/assets/audio/zombie-mode.wav b/assets/audio/zombie-mode.wav deleted file mode 100644 index 148feca..0000000 Binary files a/assets/audio/zombie-mode.wav and /dev/null differ diff --git a/assets/o_run1.png b/assets/o_run1.png index a73bda5..02f8d55 100644 Binary files a/assets/o_run1.png and b/assets/o_run1.png differ diff --git a/assets/o_run2.png b/assets/o_run2.png index 5b02624..962518b 100644 Binary files a/assets/o_run2.png and b/assets/o_run2.png differ diff --git a/audio.js b/audio.js index b74e1d4..200c1cd 100644 --- a/audio.js +++ b/audio.js @@ -107,7 +107,10 @@ function loopBed(file, lowcut, vol, loopEnd = 0) { .catch(() => beds.delete(file)); // failed start (decode/network) can retry next call } export const ambient = () => loopBed('tennis-ambient-loop.wav', 500, 0.035); // whole-scene atmosphere, from first tap -export const zombieMusic = () => loopBed('zombie-mode.wav', 800, 0.2); // background music once splatter kicks in +// ...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. +export const zombieMusic = () => loopBed('zombie-mode.m4a', 800, 0.2, 20.47077); // Menu theme: the splash, the court picker, the death summary. Barely highpassed — this one is // MUSIC, not an atmosphere bed, so the low end is the point. Started at boot so it is already // running by the time the title is on screen; on a phone the autoplay policy holds the context @@ -129,7 +132,7 @@ function fadeBed(file, out, secs = 0.7) { b.g.gain.setValueAtTime(b.g.gain.value, t); // anchor at current level so a mid-fade toggle stays smooth b.g.gain.linearRampToValueAtTime(out ? 0 : b.vol, t + secs); } -export const zombieDuck = out => fadeBed('zombie-mode.wav', out); // key must match loopBed's, filename and all +export const zombieDuck = out => fadeBed('zombie-mode.m4a', out); // key must match loopBed's, filename and all // The menu theme is never stopped, only faded — the buffer is already decoded, so fading it back up // on the death screen costs nothing and can't miss its cue waiting on a re-fetch. export const menuDuck = out => fadeBed('background-music.m4a', out); diff --git a/index.html b/index.html index 32bf864..2afed4f 100644 --- a/index.html +++ b/index.html @@ -117,6 +117,6 @@