Four defects from the independent 4-lens adversarial review of Stage 2.
R8 choke same-task noise pop — DEVIATION from the brief's fix, verified by
measurement. Read-before-cancel does NOT work: reading gain.value right after
setValueAtTime(0,t) in the same task returns the 1.0 default (the envelope
hasn't rendered), both offline AND live — so the anchor is 1.0 and it still
pops (measured 0.45 vs a 0.15 ohat). Correct primitive is cancelAndHoldAtTime,
which holds the timeline value at t (0 same-task, decaying level mid-ring);
read-value kept as the legacy fallback. Same idiom applied to fireKick's duck.
Verified live: same-task double-ohat 0.152 ~= single 0.149 (no pop); normal
different-task choke still cuts the tail 70%.
R9 euclid junk input — trim, split /[\s,]+/, drop empty tokens, Number(), bail
unless the first token is finite. " 4"/",4" -> E(4,16); "abc"/"" -> untouched;
"0" -> cleared; "5,2" -> E(5,16) rot 2. (Empty-token filter also fixes the ""
vs "0" distinction a bare Number() can't.)
R10 prototype pollution via kit data (MAJOR — patches/vibes are cross-user) —
own-property guard (hasOwnProperty) everywhere a caller-supplied name indexes
KIT / N.drums / DRUM_DEFS: drum, drumParam, drumParamList, drumKit, drumKitLoad,
drumSync. Verified: drumKitLoad({"__proto__":{toString:0.5}}) and
drumParam("__proto__",…) leave Object.prototype intact; legit keys still apply;
normal round-trip works.
R11 a beat is a build — hoist the canonical seed into DRUM_SEED (one source of
truth for seedBeat + zeroHasBuild); zeroHasBuild() now returns true when any
drum lane is on or its gates differ from the seed. Untouched seed still exits
quietly; ▶ or a drawn step now shows the keep/weave card so the beat survives.
Temporary verify hooks removed (grep-clean). No merge to main, no deploy.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>