We publish printable murder mysteries. A copy sits unplayed not because of price or quality: somebody has to host, and the host reads the answers first and so never gets to play. Most groups never find that volunteer. So let an AI host. Except the moment you put the scenario in the context window, the model knows the ending, and then it narrates the crime scene knowing, plays every suspect knowing, and decides what your search turns up, knowing. "Do not reveal the murderer" fails the way "do not think of an elephant" fails. So it isn't there. The culprit, method, motive, hints and epilogue live in server/solution.js, which is not under the static root and is never sent to the browser. The page reaches it through exactly one route, POST /api/verdict: a guess out, a mark back. On a miss it returns three booleans and a nudge, never the value you got wrong. Open devtools: the answer is not obfuscated in the bundle, it is absent from it. Four tests assert that mechanically. The intent layer is deliberately mechanical: an alias table and a bounded edit distance, no model. That constraint makes the rest mean anything, because sending the transcript to an LLM that also holds the case file puts the answer back in the loop. Four things were measured against the live endpoint rather than read off the docs, and each is a silent failure that returns fluent output. The default model code-switched English speech into Japanese katakana with no error. Pinning universal-streaming-english turned "search the ledger" into "Search is a rich". keyterms_prompt is rejected with close code 3006 unless it is a JSON array. And the recogniser keeps proper nouns while dissolving the words around them: "I accuse Tobias by oleander over authorship" came back as "Hi, I'm Tobias by Oleander Over Authorship" - every entity intact, the verb gone. Requiring the verb would have eaten the player's ending, so three filled slots is now an accusation. No dependencies. 25 tests. Runs at $0.
Category tags: