The machine speaks: client-side espeak-ng WASM + the transit PA

engine/speech.js (CsdSpeech) — speech synthesis moves into the client. vendor/espeak-ng/ carries a TRIMMED emscripten espeak-ng (242 KB gz glue + 452 KB gz en-only data, repacked from the @echogarden 0.3.5 artifact by trim.mjs — the 24 MB all-languages pack stays upstream

• Determinism, the hard way: SetRandSeed is not exported and espeak's wavegen consumes rand(), so same-instance repeats drift

• Wired additively at both engines: found-player synthToBuffer feeds the same url-keyed buffer cache; live's ring + wavOut speech paths and press's decode loop all branch on src.synthText

• The worked example: when a state carries real transitwave presence (weight >= .35), the PA announces THIS seed's namebank act — "Now arriving: The Signalmen."

• Licensing: NOTICE gains a combined-work section (deliberately NOT the lamejs LGPL carve-out shape

Verified: test/speech.test.js (synth determinism, upsampler exactness, key canon, false-path, namebank-text determinism), test/speech-live- run.js (chromium rides transitwave 6 bars…

▶ Play it: Rail Replacement Rapture — parked on the genre this change names https://stellate.app/?genre=transitwave&seed=1500&path=749.12536,768.12569,730.12569&xdur=13.122

71f3516 https://github.com/aboard-io/stellate/commit/71f3516c16ab4a94ab0ada6be8a6de6bad03a50a