The idea
Chat already keeps receipts. They screenshot the bad take, they clip the missed shot, they quote the thing you said forty minutes ago when you'd forgotten you said it. That instinct is the best unused game mechanic on Twitch.
Chat v. Streamer gives it a courtroom.
A viewer types !sue he said first try guaranteed and died six times. Two more chatters !cosign. A clerk decides whether the court has jurisdiction. Then D.A. Receipts, a prosecutor who lives for evidence, builds a case out of the streamer's own voice, pulled from a live transcript of the stream. Three exhibits go up on the overlay, verbatim. The streamer gets thirty seconds to explain themselves into their own microphone. Chat votes. And a guilty verdict rewrites the stream title to [CONVICTED] … for fifteen minutes before putting the original back.
Judge Docket is an owl in a wig. He is not impressed with you.
The one design decision everything else hangs off
The AI is never allowed to write a quote.
Every word the streamer says goes through transcription into a local SQLite database with full-text search, one row per segment with an ID. When the prosecutor builds its case, the model doesn't return text: it returns segment IDs. The server validates each ID against the candidate list it was actually shown, then renders the stored row, character for character.
If the model invents an ID, the exhibit is dropped. If every ID is invalid, the case is dismissed on the spot rather than argued with fabricated evidence.
This is what makes the bit work. A roast engine that can hallucinate quotes is a liability you'd have to supervise. One that can only find things you demonstrably said is a format. The joke is funnier because it's true, and it's safe to run unattended because it can't become untrue.
The same rule is enforced in both directions: the jury is chat and only chat. Votes are per account, last vote counts, ties acquit, zero votes is a mistrial. No model output can change a verdict, and the AI only learns the result after chat has already decided it.
What it does
For chat
| Command | Who | What |
|---|---|---|
!sue <charge> |
anyone | File a lawsuit |
!cosign [N] |
anyone but the plaintiff | Push a filing to the bench |
!guilty !g / !notguilty !ng |
anyone, during jury | Vote |
!objection |
anyone, during defense | Fill the objection meter; enough of them and the gavel comes down |
!docket !record |
anyone | Case names and the conviction record |
For the streamer
A mistrial button that works at any moment and restores your OBS scene. One exhibit strike per trial. Veto on any pending case, available through the whole pretrial. A penal code where every punishment is a JSON entry that does nothing until you mark it approved: true. Only the title sentence ships approved. Severity is capped by the guilty share: under 60% caps at 1, 80%+ reaches 3. DRY_RUN logs every outgoing Twitch action instead of performing it, and defaults on. Running cost is mostly transcription: gpt-4o-transcribe is listed at 0.006 USD per minute, about 0.36 USD per hour of the streamer actually talking, because silent chunks are skipped.
On stream
A transparent 1920×1080 OBS overlay with a real cutout for your camera: the court is built around your webcam. Receipts, camera, prompts and captions each get a zone that the others can't collide with. Long quotes stay verbatim and page themselves within the time the phase has left, so nothing is ever truncated to fit. Reduced motion is honoured.
And a comedy desk: a mallet that BONKs you on a point you calibrated yourself, stars that circle your actual head, a silly hat that attaches by its brim. Deliberately walled off in its own state domain: gags cannot touch votes, approval, sentencing or DRY_RUN.
The sentence is real
A guilty verdict saves your current title, writes the conviction, schedules the restore and persists the job to disk. Crash halfway and the restore still happens on boot. Overlapping convictions keep the true pre-court title and extend the deadline rather than stacking. Every job records whether it ran live, in dry run or in the simulator, so a rehearsal can never mark a real obligation complete.
How we built it
Node 20, strict TypeScript, no any. Express + ws + SQLite/FTS5 on the server; React and Vite for the panel and the overlay. About 12,000 lines across 82 files. 100 tests.
Four boundaries do most of the work:
A pure reducer owns the clock. server/court/machine.ts takes (state, event, settings) and returns { state, effects }. No I/O, no timers, no awaits. Time is injected, so a complete eight-phase trial runs instantly in a test. The 180-second absolute cutoff lives inside the reducer, which means no AI call can extend a trial. The worst a slow model can do is miss its slot and get a canned line instead.
Every AI call is schema-bound and deadline-bound. Four roles (clerk, trial script, rebuttal, verdict), each with a Zod schema compiled to a strict JSON schema for the Responses API, so the validator and the request are generated from one definition. Deadlines are 6s / 15s / 8s / 6s with at most one retry inside that budget. Moderation fails closed: if the moderation endpoint is unreachable, unscreened generated text never reaches the overlay, and the court falls back to written lines.
Real and simulated adapters are explicit. Twitch (Twurple OAuth + EventSub WebSocket), OBS (websocket v5), transcription and TTS each have a real implementation and a simulator behind one interface. SIM_MODE and DRY_RUN both default true, so the whole thing runs end to end with no accounts at all: npm run dev, click Run full demo trial.
Raw chat never reaches the overlay. Filings are stored for local audit and shown only in the streamer's panel, blurred if moderation failed. Overlay snapshots are sanitised: no raw filings, no audit log, no full transcript. Evidence is bound to a verified broadcast ID, so a restart during the same stream keeps its receipts and a new stream can't dig up the old ones.
How we used Codex
Codex wrote this. All of it, in one day, in ten milestones you can read in order in the commit history. The interesting part is the working habits that came out of driving it this way.
It read the types before it wrote the call. Standing instruction in AGENTS.md: read the installed integration typings and the official docs before authoring an API call. So the Twitch adapter was written against the Twurple declarations actually in node_modules and the Twitch EventSub WebSocket reference, the OBS adapter against the installed obs-websocket-js request types, the AI layer against the OpenAI SDK's own text.format declarations and the structured output guide. Almost no integration guesswork, and BUILD_LOG.md records which declarations and which documents for each milestone.
It reproduced bugs before fixing them. M7 began by reproducing every finding from a review pass and writing the measurement down first: a title regression that reverted when it should have stayed pending, an exhibit card overlapping the defense card by 660×172 pixels, a caption bar holding 173px of content in a 128px space. Then the fix, then a regression test. The before/after numbers are in the log.
It drove a browser to check its own work. npm run browser:check starts an isolated server on a temporary database, opens both routes in Chromium, runs a full trial and then scans every pixel inside the camera cutout for an alpha of zero, in four camera layouts. A CSS root-background leak got caught that way: the body was transparent and the page still rendered opaque, which no amount of reading the stylesheet would have shown.
It built its own instruments. Roughly 2,400 lines across eleven verification scripts, none of which were asked for: an injected-clock full-trial check, the browser check, a head-calibration check, a comedy check, a moving-streamer rehearsal that drives real panel controls against a video fixture and records the result with FFmpeg, a local OpenAI protocol fixture, and a readiness reporter that reports presence and counters without ever printing a secret.
It caught a vendor lying about transparency. The comedy art was generated through Higgsfield's GPT Image 2 with background=transparent. What came back was a 3504×2336 RGB atlas with a checkerboard painted into the pixels. Codex inspected the output, removed the fake checkerboard, corrected the crops, decontaminated the edges, and wrote provenance.json with per-file crop coordinates, alpha counts and SHA-256 hashes. The browser check now asserts real RGBA before the assets are allowed to ship.
And it wrote down what it had not proved. The part we're proudest of is a table in BUILD_LOG.md with a column headed "Real service actually exercised", mostly reading No, next to exactly what local fixture stood in and exactly what was still required. When a stand-in endpoint we tested against served text generation but returned 404 on moderation and speech, that went in the log too, including the detail that the app's moderator correctly rejected a benign input when the endpoint vanished, which is the fail-closed path working. Getting a model to volunteer the limits of its own evidence is harder than getting it to write the feature, and it's the reason this codebase can be trusted enough to point at a live channel.
Codex session ID: 01a18d9c-eeab-7ce1-b32e-3276ca9a8e3c (thread "Build Twitch courtroom app")
Challenges we ran into
Transparency you can't see. The overlay has to be genuinely transparent where your camera shows through. A transparent body isn't enough, because the panel and overlay share one CSS bundle and the HTML root was painting underneath. Visual inspection says it's fine. The fix was to stop trusting inspection and assert actual PNG alpha on every pixel of the camera interior.
Rehearsals pretending to be real. The nastiest one. A dry-run conviction was marking a genuine outstanding title-restoration obligation as complete, meaning a real stream could have been left wearing a conviction forever. Restoration jobs now carry execution mode and channel identity, a rehearsal can't inherit or retire a live obligation, and a legacy job of unknown provenance stays visible as pending review instead of being guessed at.
Writing a roast engine that can't be weaponised. The court hears claims about what you said on this stream that a transcript can prove or disprove. Personal life, appearance, identity, health, family, money, real-world allegations, third parties: all rejected, and rejections never echo the original filing back. Keeping that boundary tight while leaving the comedy actually funny took more prompt iterations than anything else in the build.
Accomplishments we're proud of
- A trial where the funniest line is always something you really said, because the architecture makes it impossible for it to be anything else.
- 100 tests covering ties, zero votes, votes landing on the deadline boundary, invalid IDs, moderation failure, delayed providers, broadcast scoping, and title restoration across real, dry-run and simulator restarts.
- An overlay verified pixel by pixel.
- Runs with no credentials at all. Clone,
npm install,npm run dev, click one button, watch a complete trial. - A build log an evaluator can audit, including the parts that aren't finished.
What we learned
Put the deadline inside the reducer. Once the clock could not be extended by anything, every AI call became optional by construction, and an optional AI call is one you can ship without supervising.
Let the model choose from what exists. Returning IDs instead of text removed a whole class of failure and made the output funnier, because reality is specific in ways invention isn't.
And agents will happily tell you a thing works. Asking Codex to keep a column for what it had not actually exercised changed the character of the build: it stopped producing confident summaries and started producing evidence.
What's next
- Finishing the connected sign-off: natural token refresh, the real OBS audio mix, physical mic capture, and the full recovery rehearsal.
- A hosted version, so a streamer can connect Twitch and add one OBS browser source without running anything locally.
- Appeals. Chat should be able to
!appeala conviction and force a retrial with new receipts. - A public case law page. Every verdict already saves a searchable precedent and future trials can cite one by ID; it deserves to be visible.
- More of the penal code approved out of the box, with effects streamers actually want.
Try it
- Live demo: open https://chat-v-streamer.fly.dev/panel and press Run full demo trial, then open https://chat-v-streamer.fly.dev/overlay in a second tab to watch the courtroom.
- Repository: https://github.com/0xNoramiya/chat-v-streamer
Built With
- codex
- eventsub
- express.js
- ffmpeg
- fts5
- gpt-4o-transcribe
- gpt-5
- higgsfield
- node.js
- obs-studio
- obs-websocket
- openai
- playwright
- react
- sqlite
- twitch
- twurple
- typescript
- vite
- vitest
- webaudio
- websockets
- zod
Log in or sign up for Devpost to join the conversation.