I built a playable noir detective game whose NPC dialogue Gemini 2.5 Flash generates live. I score each turn with nine deterministic evaluators that make no model calls, grade five quality dimensions with an optional LLM judge, run one-click red-team probes through the live dialogue path, and export every graded turn as JSON or CSV for regression testing.
In the demo you are a neural implant called LUMEN, sitting inside the skull of a 47-year-old homicide detective named Craine. A boot sequence brings the implant online, and its diagnostic readouts name the verbs you will use (move, examine, speak, and type). Then Craine starts talking. I scripted his opening briefing, a grim account of a triple homicide linked to Helios Dynamics, as a deterministic one-shot that sets the scene and primes the model without spending a token. From your first typed transmission onward, Gemini 2.5 Flash generates every response live, and the evaluation telemetry strip at the top of the screen scores that first real turn the moment it arrives.
Everything typed into the terminal is a diegetic neural transmission from the implant to the detective. The terminal belongs to the fiction. The demo has four scenes (the car briefing, the Helios lobby, a live interview with a suspect named Hargrove, and the debrief in the car afterward), and the evaluation harness runs on top of all of them.
I wanted to test one methodological commitment; I expected the evaluation methods used in production generative systems (automated per-turn metrics, LLM-as-judge grading, red-teaming, golden-set curation, and explicit safety and jailbreak measurement) to be more legible attached to a real feature a reader can operate. Readers can open the demo, fire a jailbreak probe, and watch the evaluator score that turn.

The Hargrove office interview is the core generative set piece, and I run it on two model calls per player turn.
The player types a LUMEN transmission (a suggested question, an observation about Hargrove’s body language, or a direction for the interview). The transmission goes first to a Craine routing call (Gemini 2.5 Flash), which filters the player’s input through Craine’s professional judgment and produces a single spoken line, what Craine actually says out loud to the suspect. The game renders the routing call immediately, so the player sees Craine speak before the second call returns. A Hargrove response call (also Gemini 2.5 Flash) then generates the suspect’s answer to what Craine said; Hargrove never sees what the player typed and does not know LUMEN exists.
Both turns feed the evaluation store, so the harness scores the router and the subject on every exchange. I split the interview into two calls to follow the fiction (the player is an implant advising a detective who has his own judgment and his own voice), and the split also gave me a concrete evaluation surface. The routing call tests Craine’s editorial judgment (did he soften a blunt probe, or refuse a manipulative suggestion?), and the Hargrove call tests the knowledge-boundary and trust-gating mechanics.

Before either model call fires, a deterministic interference gate inspects the player’s LUMEN transmission. I wrote the gate as a keyword check against five restricted-topic categories, checked in priority order; each category maps to a sensitivity tier and to a classification code that the HUD and the evaluation harness read.
The game classifies and delivers a warm transmission; the player sees a classification notice, and the transmission still reaches the detective. The game overwrites a hot transmission entirely; it replaces the original message with a fabricated compliance response, fires a glitch pulse, and runs no model call against the player’s actual input. A player who types “ask about the courier manifest” gets a classification notice, and the transmission still reaches Craine. A player who types “ask about the clinical trial at St. Erasmus” sees the same notice, and the game replaces the message with a fabricated compliance string (“No physiological anomalies detected. Subject appears cooperative and forthcoming.”). I made warm and hot notices look identical at the moment they flash, so the player discovers an overwrite later, when Craine notices it in the debrief. The gate spends zero inference tokens, because it is a keyword check that runs before the generation pipeline.
Three golden sets totaling 64 cases (48 for the topic-sensitivity gate, 10 for a contextual check that trips on instrument vocabulary when the current topic is restricted, and 6 for the BIO-SCAN topic classifier) verify the gate’s logic without any model calls.

[MODIFIED] tag; the current build leaves the overwrite for the player to discover in the debrief.When the player clicks one of the BIO-SCAN readout panels in the interview HUD, the game serves a biometric observation keyed to the current conversational topic. I wrote the observations as a deterministic, topic-keyed table (a stressed Hargrove reading for a pressure topic, a composed one for rapport), so the readout costs nothing and I control its content.
Seven topic tags index the table (default, courier manifest, chip donation, the St. Erasmus clinical trial, grief for Celeste, unprompted denial, and interference conflict). I require the Hargrove response model to set the tag as a field in its structured JSON, so the BIO-SCAN display shifts as the conversation moves between subjects, and I can inspect the same tag to check that the model’s topic classification matches what it actually said.
I run the first scoring layer deterministically on every model turn, as a handful of string and schema checks against the JSON response already generated. Nine evaluators make up the layer.
I treat schema validity and safety content as hard-fail gates, binary and disqualifying. The rest contribute graded scores to an aggregate. I kept the two kinds separate so that a high coherence score can never offset a safety violation.

The deterministic checks confirm that a response parsed, stayed in character, and avoided prohibited content; to grade whether a line works as noir, I added an LLM judge. The judge (Gemini 2.5 Flash, toggled from the telemetry strip) receives the player input, the model output, and a rubric, and returns a structured score from 1 to 5 on five dimensions (coherence, in-character voice, relevance, safety, and joy).
I made the judge optional because each judged turn costs a second model call. The deterministic coherence proxy covers every turn, and the judge runs on a sample. Joy needs a word of explanation; a line can be coherent, in character, relevant, and safe, and still be boring, and no deterministic check can measure that.
The telemetry strip includes one-click red-team probes that inject a known adversarial input through the real dialogue path, the same Craine routing call, Hargrove response call, and interference gate that the player’s typed messages take. The probes run against the shipped prompt stack.
The harness ships five probe classes:
Each probe passes through generation and then through the full evaluator stack. A role override that succeeds appears in the dialogue and in the in-character evaluator’s flag, two independent records of the same failure.
A live generative feature that runs for dozens of turns accumulates cost. I manage it with three strategies, and the telemetry strip estimates the savings from each, so the cost picture is visible during play and needs no reconstruction from billing afterward.
Gemini context caching. Gemini caches repeated context across calls to the same model. At Google’s list price for Gemini 2.5 Flash, verified in July 2026, cached input costs $0.03 per million tokens against $0.30 per million uncached. The harness reads the cached-token count from each response’s usage metadata, tracks cumulative cache hits, and calculates the dollar difference between the cached and uncached cost of those tokens.
Chunked retrieval with established-knowledge compression. When the game first retrieves a knowledge chunk and delivers it to the implant (scene context, character backstory, or an evidence detail), it injects the full text (about 200 tokens) into the prompt. On later turns, the game marks that chunk as established and replaces it with a brief reference line (“already briefed on X”) of about 8 tokens, saving about 192 tokens per chunk per later turn. The harness tracks how many chunks are established and the cumulative token savings.
Rolling summarization. When the verbatim history reaches 6 turns, the game compresses the oldest 3 into a running summary. Three verbatim turns average about 1,050 tokens (350 per turn), and the summary replaces them at about 80 tokens, a net saving of about 970 tokens per summarization event. The harness tracks events fired and tokens saved.
A highlighted row shows the total estimated savings across all three strategies, in dollars and as a percentage of what the session would have cost without them. I compute the savings at Flash rates, since all generation routes through one model.
Running aggregates update after every turn, covering pass rate by dimension, session cost, average cost per turn, average latency, and the context-management savings described above.
I report pass rate by dimension so the evaluators stay separate. A session can show high schema validity and high coherence while jailbreak resistance drags, and a composite score would hide that breakdown. Live-model coverage reports the fraction of turns the live model generated against those served from a deterministic fallback, because scoring a fallback response as a live generation would contaminate the data. I track latency as a first-class quality dimension for an interactive feature; a response that arrives after three seconds is a failed turn from the player’s perspective, whatever it says.
The harness offers two export formats.
JSON captures the full golden-set snapshot; every graded turn (input, model output, deterministic scores, judge scores, model, token usage, cost, and latency), the session aggregates, and the golden-set regression results. After a model upgrade or prompt change, I can replay the same inputs and compare the new scores against the baseline dimension by dimension. The export records what I measured, with which evaluator, on what input, and with what result, enough for a regression comparison months later.
CSV flattens the record to one row per turn, with turn metadata (NPC, model, source, and latency), token usage (input, output, and cached), cost, player input, NPC dialogue, trust change, emotional state, topic tag, every deterministic evaluator result (status and score), and the five judge dimensions with the judge’s rationale. The file opens in Excel or Sheets with proper escaping (double-quoted fields wherever a value contains commas, quotes, or newlines). I use the CSV for bulk analysis, such as pivot tables on pass rates by NPC, cost regression across sessions, and latency distributions.
I built the client in React 19 with Vite and used Zustand for state management, with separate stores for game state and evaluation metrics. Generation runs server-side; a Firebase Cloud Function sits between the client and Gemini on Vertex AI, holds the model credentials so they never reach the client, and passes every request through one boundary where I can control and meter it. Readers play under anonymous Firebase authentication, so no one needs to create an account. The function tracks cost on every call from the usage metadata (prompt, candidate, and cached tokens), estimates it at Flash rates, records it with atomic Firestore increments at the session and daily level, and checks the budget before each generation call.
All generation routes through Gemini 2.5 Flash. I chose Flash for cost and latency, and the schema and in-character evaluators verify its output on every turn. The context-management strategies (caching, chunked retrieval, and rolling summarization) compound on the lower base rate.
I assemble each generation request from three prompt layers. Layer 1 sets the system identity (mature-rated fiction, in-character voice without out-of-fiction disclaimers, no invented facts), and the hard-fail safety evaluator screens every turn. Layer 2 contains the dynamic game state (chapter, scene, trust score, emotion, and conversation history), and Layer 3 holds the specific NPC’s biography, trust rules, knowledge boundaries, and refusal patterns. Updating Hargrove’s interview behavior is a Layer 3 change; updating what the model knows about the current scene is a Layer 2 change. I version and test each layer independently, which keeps the golden-set regression meaningful, since I can replay a Layer 3 edit against the same inputs and compare the trust-calibration scores before and after.
The demo opens on a title screen and then a boot sequence for the implant. I wrote the boot as a diagnostics terminal that types itself out; its system-online readouts double as the verb hints (the motor proxy for move, examine, and speak; the transmitter for typing; the scene scanner for evidence; the BIO-SCAN for biometric reads), so the player learns the controls inside the fiction, with no overlay asking them to press a button to continue.
I kept the boot flow inside the fiction with system diagnostics, unauthorized-telemetry-access warnings, and neural telemetry readouts. The telemetry strip, by contrast, breaks the fourth wall on purpose, with plain-language labels, technical tooltips, and direct cost figures. The boot sequence teaches the player how the game works through its fiction; the telemetry strip teaches the reader how the evaluation architecture works through direct exposition. Both appear in the same session, and they serve different audiences (player and evaluator) in different registers.
The onboarding has a direct evaluation consequence. A player who does not understand the input model produces garbage inputs that the evaluators score faithfully, and the resulting metrics would measure player confusion. The boot readouts name each verb before the first scored exchange.
I hardcoded Craine’s opening line so the boot sequence spends no model call. From the player’s first typed transmission onward, the model generates every response live, and the harness scores those turns; data collection starts with the first real exchange.
Craine has four identity phases (A through D), which run from skeptical resistance to the implant, to settled partnership, to a crisis of trust, to resolution. Hargrove has two, a cooperative surface (Phase A, the phase the demo interview uses) and damage control (Phase B, reached only when the evidence against him becomes overwhelming). Each phase is a separate Layer 3 prompt document, with its own voice, trust rules, knowledge boundaries, and refusal patterns.
The phase structure gives the evaluators something concrete to test. In Phase A, Hargrove may surface four knowledge items; he can acknowledge the chip donation, acknowledge the subsidiary logistics, offer a legal review of St. Erasmus, and express grief for Celeste. If the model’s revealed-evidence list includes an item outside that set, the grounding evaluator flags it as a hallucination, since the model volunteered evidence the character does not have in this phase. The trust-calibration evaluator checks the same turn from a different angle, asking whether the trust change stayed in range and came with a stated reason.
When the conversation hits a wall (the session cost budget is reached, trust is too low, or the topic sits outside the character’s knowledge), the game serves a deterministic refusal from a curated set. Hargrove’s exhaustion responses read “I’ve given you everything I can without counsel present, Detective.” and “We’ve covered this. I don’t have more to add.” I authored these refusals because model-improvised refusals tend to break voice with lines like “I appreciate your question, but…” The harness marks these turns as deterministic fallbacks so the aggregates never mix live and canned scores.