Applied AI · Live at qaitrek.com · Proprietary fan project
Q&AI
A live Star Trek guessing game whose hand-built Bayesian engine picks every question by expected information gain. In simulated play it identifies all 368 of its most-likely-picked characters (of 983) in 15.6 questions on average, and its deploy script refuses to ship below 98%.
Status
Live, and in maintenance: 469 commits on 26 days between 20 May and 15 Jul 2026, none since
Version
Live build v1.1.1, deployed 25 Jun 2026; the repo is at v1.2.0. Five tagged releases between 11 Jun and 9 Jul 2026, including a mobile pass (v1.0.1) and reverse mode (v1.1.0)
Licence
All rights reserved: a personal, non-commercial fan project, not open source. The characters belong to Paramount.
Runs on
The player’s browser, desktop or phone, with no server-side inference
Stack
TypeScript 5.9 strict · React 18 · Vite · a hand-built engine (entropy, expected information gain, a 7×5 likelihood table, no math library) · Node simulation harness · Bash ship gate
Measured · figures marked recorded are older · how
By hand
Checking 122,875 answers, 983 characters by 125 questions, one card at a time.
→
Now
Parallel AI subagents propose, deterministic scripts write, and a person sees only what the agents couldn’t resolve.
At a glance
the gate cohort of likeliest picks, uniquely identified in simulated play in 15.6 questions on average
368/368
clean wins across all 983 active characters
82.6%
of the gate cohort still identified when one answer is wrong
84.2%
gzipped: engine and corpus ship as one static bundle, with no server-side inference
388 KB
floor 9.94median 14mean 15.6p90 20cap 25
Fig. 1 Questions per game, gate cohort, perfect-answer simulation. The floor is log₂ 983: no yes-or-no strategy can average fewer. Most of the gap is deliberate, because no single answer may eliminate a character.How these were measured
368/368: the project’s simulation harness, tsx scripts/self_test.ts, run on 12 Oct 2026 without its miss-dump flag, so nothing was written. 368 of 368 clean wins; mean 15.6 questions, median 14, p90 20, max 25.
82.6%: the same harness with --full: 812 of 983 clean wins. The rest are 109 tie-misses and 62 hollow ties; no run ranked a wrong character above the right one. The 983 active cards are drawn from 2,967 card files; the other 1,984 only populate the character picker.
84.2%: --inject-error, a report-only probe that gives one wrong answer after question 7: 310 of 368, with the mean rising to 19.5 questions.
388 KB: the live JavaScript bundle fetched from qaitrek.com is 3,282,599 bytes, and 387,777 through gzip -n (no filename or timestamp in the header). It contains no text-to-speech or model endpoint.
Fig. 1: the floor is Math.log2(983), and the cap is the engine’s 25-question limit. For comparison, the 114 main-cast characters average 13.2 questions (--canary, 114 of 114), and all 983 average 18.2. How the gap divides comes from the project’s own floor analysis, made on an earlier corpus.
Fig. 2 Query 1 of 25. The prior is uniform and the engine has no randomness, so every game opens with this question. Five answers let a player hedge, and the likelihood table prices each one. The portrait is the game’s AI-generated host. Live capture, 12 Oct 2026, cropped to the question and answers on small screens.
The short version
Found a normalization bug that made the question selector rank by confidence instead of information. The fix was 13 lines of code. Re-measured on 12 Oct 2026, it still carries the gate: without it the cohort drops to 96.7%, and the ship script refuses to deploy.
Moved the deploy gate into the only path to production after hosted CI deployed a build whose self-test had failed. Pushing to the repository now deploys nothing.
Counted only unique-top wins, even though in June 2026 that cut the score from 95.9% to 90.7%. A tie that lands on the right answer by array order is not a win. By that stricter count the gate cohort is now 368 of 368.
Case 1 of 5
The bug that hid inside a working system
The engine reported negative expected information gain, which mutual information can never be. One missing normalization was the cause, and the fix was 13 lines of code.
Nothing looked broken. The engine still identified about 92% of the characters it was gated on; games just ran long, around 21 questions, and opened with oddly obscure ones. recorded 29 May 2026
The likelihood table is forgiving by design: a wrong answer costs a character probability but never eliminates it, so none of its seven rows sums to 1; each sums to 1.62 or 2.20. The belief update only needs relative weights. Information gain doesn’t forgive that: it over-counted every answer, more so for high-confidence questions, so the selector was ranking questions by confidence rather than by how well they split the field. A 0.91-bit question, close to the most a yes-or-no question can carry, sat at rank 41.
I normalized each row inside the information-gain calculation only, and left the tuned belief update alone. That question moved to rank 3, and 21 apparently separate ordering misses turned out to share this one cause. recorded 29 May 2026
Re-run on 12 Oct 2026 without the fix, the engine identifies 356 of 368 and fails its own gate.
Fig. 3 The fix as math rather than code. Inside the information-gain calculation, each row of the likelihood table L now sums to 1; the belief update keeps the raw, forgiving rows.
Mean questions per game on the gate cohort, on the engine and data as of 12 Oct 2026 (lower is better)
Shipped engine
15.6
Normalization removed
20.6
How we know
The before-and-after figures are recorded in the fix’s commit, 29 May 2026: on the cohort as it was then defined (255 characters, raw wins), 234 of 255 (91.8%) became 250 of 254 (98.4%), the mean fell from 21.2 to 15.1 questions, and the rank change and the 21 ordering misses are listed. The same commit’s change to the selector adds 13 lines of code, not counting comments and blank lines. On 12 Oct 2026 the normalization was removed from a scratch copy of the engine and the gate re-run on the current data: 356 of 368 (96.7%), 10 tie-misses, a mean of 20.6 questions, and a FAIL. The project itself was not modified.
Case 2 of 5
Score the contenders, not the field
A question that settles one character is worth almost nothing to the whole field, so it never got asked. Scoring over the emerging contenders fixed that: the gate cohort went from 366 to 368 of 368.
Some questions decide exactly one character: in the current data, one trait is held by 1 card and absent from the other 982. Scored against the whole corpus, such a question carries almost no information. The one that mattered ranked around 28th at the time and never got asked, so a front-runner stayed tied with its look-alikes.
Two earlier approaches had been set aside: an ask-by-name clarification phase, disabled in favour of narrow questions, and a cluster lock built on hard-coded thresholds. Before trying again I built two diagnostics, and they showed why thresholds can’t work: at any single moment, a stuck tie and a leader climbing out of one look identical.
The rebuild removed every absolute belief threshold from question selection. Once two or more characters rise above 5× the uniform prior, information gain is scored over those contenders alone. The do-not-ask veto now applies only inside the leading cluster, and when sparse answers would otherwise end the game, a leader 2.5× ahead of the runner-up is guessed instead. The buried question rose from 28th to 2nd, and games got slightly shorter, from 15.9 to 15.6 questions. recorded 9 Jun 2026
How we know
The 5× contender multiple and the 2.5× leader rule are constants in the engine’s selector and game loop. The 366 → 368 result and the rank change are recorded in the rebuild’s commit, 9 Jun 2026. On 12 Oct 2026 contender scoring was switched off in a scratch copy and the gate re-run: 367 of 368, and the one character lost is the one whose 1-yes, 982-no question the change was built to surface.
Case 3 of 5
A deploy gate that actually gates
Two hosted workflows ran side by side with nothing linking them, so a build whose self-test failed still deployed. The gate now sits in the only path to production.
From the first day, every push deployed automatically through hosted CI. A self-test workflow added two days later ran beside the deploy, with no dependency between them. Then one push’s self-test failed while its deploy succeeded: the gate had never existed.
Rather than wire the workflows together, I retired both on 29 May 2026 and moved the gate into the one path that reaches production: a local ship script under set -euo pipefail, where any failing step stops everything after it. For one operator working interactively, that is the same machine enforcement in a faster loop that is visible as it runs.
Before · retired
Every push started two hosted workflows, one to deploy and one to run the self-test.
Nothing linked them, so a failing test could sit beside a successful deploy.
Now · the ship script
Gate: at least 98% clean wins on the 368-character cohort.
Build: type-check and build, then confirm the artifacts exist.
Swap: upload hashed assets to a temporary directory and swap them in atomically.
Verify: the live site must answer HTTP 200.
Now · the only path
Pushing to the repository deploys nothing.
A separate deploy command skips only step 1, for use right after a green gate run; build, swap and verify still run.
Fig. 4 Advisory, mechanical, structural: the gate moved from a check that could be ignored to the only route to production.How we know
Both workflow files remain in the repository history up to their removal on 29 May 2026 (23:03 US Central). Each triggered on push, and neither declared a dependency on the other. The run whose test failed and whose deploy succeeded is from the project’s own records. The gate run on 12 Oct 2026: 368 of 368 against the 98% floor.
Case 4 of 5
Honest metrics over flattering ones
The harness plays every character with that character’s own answers, at no model cost. Three rules keep its number honest, and one of them cut the score on purpose.
It is deterministic, and its own header lists what it can’t detect: cases where the data’s answer differs from what a fan would say.
First, a win must make the target the unique top of the belief; a tie that lands on the target by array order doesn’t count. Second, unverified cells are answered “don’t know”, as the live game treats them, not an assumed “no”; that cost one more win. Third, if more accurate data lowers the score, the engine has the bug and the data stays: when a correct audit left two characters unidentified, that rule produced the contender rebuild instead of a rollback. recorded 5–9 Jun 2026
Every game in the full 983-character run, perfect-answer simulation, 12 Oct 2026. With perfect answers, every miss is a tie at the top.
Clean wins
812
Tie-misses
109
Hollow ties, not counted
62
Right character ranked below another
0
How we know
The four outcome classes are the harness’s own. The 95.9% → 90.7% change is 349 → 330 of 364 in the commit that introduced clean wins, 5 Jun 2026, and the floor’s history is kept in the harness source. A scratch script over the dataset on 12 Oct 2026 found 40 cards in 19 groups with identical answer vectors, which no question in the bank can separate.
Case 5 of 5
AI agents propose; deterministic code writes
Agents never write the data. They return verdicts, scripts apply them, and disagreement becomes a recorded “don’t ask”, not a coin flip.
Card data is audited by parallel AI subagents that return verdicts only. An orchestrating agent checks them with full project context and web research, re-runnable scripts apply them, and I see only what the agents couldn’t resolve. Where agents disagree, the character gets a do-not-ask tag instead of a guessed value: on the research-scientist question, three passes per card leave 45 yes, 22 do-not-ask and 916 no across the 983 active cards.
Question wording is tested first. A simulated panel of 50 to 100 AI “fans” must rate a question answerable in two seconds at least 75% of the time, because a five-agent sample swung from 4 of 5 to 2 of 5 between runs. recorded 8 Jun 2026
The voice layer got the same treatment. The text-to-speech model clipped the last word of about 40% of renders, so every line is rendered with a throwaway padding word that is trimmed off afterwards. recorded 11 Jun 2026
How we know
The 45 / 22 / 916 split is a count over the built card bundle on 12 Oct 2026 for that one question. The 75% threshold, the 4-of-5 against 2-of-5 swing and the clipping rate come from the project’s design and voice notes. The 375 per-character clips, of 379 committed voice files, were counted in the repository’s voice folder on 12 Oct 2026, and a set check confirmed that all 368 gate-cohort characters have one. They were rendered ahead of time with ElevenLabs’ eleven_v3 model, by author-time scripts and a development-only voice bench that the production build compiles out, and committed as static files. The build only copies them, and the live bundle references no speech service.
Reverse mode
Fig. 5 Reverse mode, “Read the Q” (v1.1.0): the game picks, the player asks. 93 wide questions in 8 groups, a 25-question cap, and the third wrong guess ends the game. Live capture, 12 Oct 2026.