"""Drive a full examination end-to-end in the terminal (mock backend). python3 scripts/demo_playthrough.py Doubles as the dry-run harness referenced in the demo-video plan: it prints each turn's perceived stance, the witness's line, and the live contradiction verdict, then asserts the win fires with a cached voice-crack take. """ import os import sys sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) import numpy as np # noqa: E402 from witnessbox.backends import get_backends # noqa: E402 from witnessbox.engine import WitnessBoxEngine # noqa: E402 from witnessbox import stance as stance_mod # noqa: E402 SCRIPT = [ "So, Mr. Reid — comfortable up there?", # filler "The wire to Meridian cleared March 6th — before the board approved it on the 14th.", "Anything over $5 million needs the CFO's sign-off, and your credentials are on the authorization log.", "You were cc'd on Meridian's incorporation filing two years ago — Dana Voss, your colleague.", ] def bar(pct, n=20): f = int(round(pct / 100 * n)) return "█" * f + "·" * (n - f) def _speechlike(dur_s=2.4, sr=16000, syl_rate=5.0, pause_frac=0.15, wobble=0.0, seed=0): """A crude but *speech-like* clip: a voiced carrier (f0 + harmonics, optional pitch wobble) gated by a train of syllable bumps. Unlike a pure sine, its pause ratio, onset rate and pitch steadiness move the way real delivery does — so the stance read comes out in the right direction. high syl_rate + low pause_frac + flat pitch -> CONFIDENT low syl_rate + high pause_frac + wobble -> HESITANT """ rng = np.random.RandomState(seed) n = int(dur_s * sr) t = np.arange(n) / sr f0 = 135.0 * (1.0 + wobble * np.sin(2 * np.pi * 0.8 * t + rng.rand())) phase = 2 * np.pi * np.cumsum(f0) / sr carrier = np.sin(phase) + 0.5 * np.sin(2 * phase) + 0.33 * np.sin(3 * phase) env = np.zeros(n) period = max(1, int(sr / syl_rate)) syl_len = max(1, int(period * (1.0 - pause_frac))) for start in range(0, n, period): seg = min(syl_len, n - start) if seg <= 1: break env[start:start + seg] = 0.5 - 0.5 * np.cos(2 * np.pi * np.arange(seg) / seg) return (0.4 * carrier * env).astype(np.float32) def main(): eng = WitnessBoxEngine(get_backends()) intro = eng.start() print(f"\n BACKEND: {intro['backend']} — {intro['backend_note']}") print(f"\n ⚖️ THE COURT: {intro['narration']}") print(f" 🎙️ REID: {intro['opening_text']}\n") print(" " + "─" * 64) last = None for line in SCRIPT: last = eng.take_turn(typed_text=line) s, st = last.status, last.stance print(f"\n ⚖️ YOU [{st.tier.lower()}]: {last.examiner_text}") print(f" 🎙️ REID ({s['witness_tier']}): {last.witness_text}") if last.evidence: for ln in last.evidence.splitlines(): print(f" │ {ln}") audio = "🔊" if last.witness_audio is not None else "—" print(f" catches {s['catches']}/{s['catches_to_win']} " f"composure [{bar(s['composure'])}] standing [{bar(s['credibility'])}] {audio}") if last.events.won: print(f"\n 💥 HE BREAKS — voice-crack take: " f"{len(last.witness_audio)} samples @ {last.audio_sr} Hz, " f"epilogue {'present' if last.epilogue_audio is not None else 'missing'}") print("\n " + "─" * 64) print(" Stance scoring on speech-like clips (no real mic needed):") for name, (dur, syl_rate, pause_frac, wobble) in ( ("fluent / steady", (2.4, 5.0, 0.12, 0.0)), # dense syllables, few pauses, flat pitch ("halting / unsure", (3.2, 1.4, 0.72, 0.20)), # sparse syllables, long gaps, wavering pitch ): clip = _speechlike(dur_s=dur, syl_rate=syl_rate, pause_frac=pause_frac, wobble=wobble) r = stance_mod.analyze(clip, 16000) print(f" {name:18s} -> {r.tier:9s} conf={r.confidence:5.1f} " f"(pause={r.features.get('pause_ratio')}, rate={r.features.get('rate_hz')}, " f"pitch_std={r.features.get('pitch_std_semitones')})") assert last.events.won, "expected a win after three catches" print("\n ✅ End-to-end win path verified.\n") if __name__ == "__main__": main()