Speak rief bei ErrNoByteOutput SpeakDirect auf, das einen eigenen
vollständigen walk machte: fehlgeschlagene Cloud-Stufen wurden zweimal
angefragt (doppelte POSTs auf bezahlten APIs), Fehlschlag-Zeilen
verdoppelten sich im Log, die Worst-Case-Latenz verdoppelte sich, und
Bytes aus einer transienten Erholung im zweiten Durchlauf hätten einen
Fehler statt Wiedergabe produziert.
Speak entscheidet jetzt aus EINEM walk-Ergebnis (Bytes → Player,
nur-Direkt-Stufe → SpeakDirect direkt daran, sonst gebündelter
Stufen-Fehler aus dem geteilten Helper walkResult.noTierErr).
Synthesize/SpeakDirect behalten ihre öffentlichen Verträge; null-Player
verwirft tonlos mit Exit 0 wie zuvor.
Test: TestChainDirektEndgameEinDurchlauf pinnt Cloud-Stufe genau 1×
angefragt, jede Fehlschlag-Zeile genau 1×, Direktansage kommt zustande.
Interface Name/Available/Synthesize (Contract für die Kette aus Task 03)
plus dokumentierte optionale Erweiterung DirectSpeaker für Plattformen,
deren OS-TTS keine Audio-Bytes liefert: spd-say (speech-dispatcher) kann
nach --help-Verifikation nur selbst abspielen — dort spricht
SpeakDirect blockierend (spd-say -w -l <lang>). Synthesize liefert
RIFF/WAVE: Linux espeak/espeak-ng (--stdout), macOS say (temporäre
WAV-Datei, LEI16@22050), Windows SAPI (PowerShell, -EncodedCommand).
Tests mit Fake-Prozessen über PATH-Manipulation; echte spd-say-Ausgabe
nur hinter TALK_TEST_REAL_SPD=1.