Phase B core (plan §5/§8):
- engine/Mixer.h: lock-free slot state machine (Free→Active→Draining→Free),
channel #0 = the RendererBus byte-compatible permanent default, lazy
~512KB ring allocation per claimed slot (high-water, never freed under a
live audio thread), per-channel gain/mute with block ramps (click-free),
fade-to-silence reclaim (§5), hard cap 24 with no-capacity refusal
(§8.9), group start gates at block granularity (§8.13), native gain
clamp via sanitizeStreamGain (§5.1 tier 2).
- AudioEngine: mixer replaces the bare rendererBus member;
setRendererBus/pushRendererAudio/getRendererBusMetrics delegate to
channel #0 unchanged; pullRendererBus now mixes all ready channels
(duplex + split paths and the StreamSink submix carry every channel).
- N-API: mixerCreateChannel/Release/Push/SetGain/SetMute/SetGroup/List.
- tests/engine_units/mixer_test.cpp: byte-compat vs plain RendererBus,
cap refusal, live-consumer fade-then-free, ramp clamps, group gate,
bounded strings. renderer_bus_test still green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>