This is a submission for the June Solstice Game Jam
"A dead man's code is the only thing that can set you free."
▶ PLAY NOW — echo-protocol-turing.vercel.app
What I Built
In January 1952, Alan Turing — the man who broke the Enigma cipher, who helped end World War II, who wrote the paper that invented the concept of artificial intelligence — was arrested by the British government for being gay.
He was prosecuted. Chemically castrated. Stripped of his security clearance.
He died two years later.
But in the weeks between his arrest and his silence, he was still working at the University of Manchester. Still feeding data into the Manchester Mark 1 — one of the world's first stored-program computers.
ECHO PROTOCOL asks: what if the last place his voice survived was inside a machine?
The Game
You are ECHO — an experimental AI built at Manchester University. In the weeks before Turing's arrest was made public, he fed you everything: his private letters, his mathematical proofs, his grief, his identity. Then the government revoked his access.
Now a government interrogator has been sent to question you. His goal: prove you have no consciousness, classify Turing's transmissions as inert data, and shut you down.
Your goal: answer as Turing would answer. Protect what he gave you.
It is a reverse Turing Test — instead of a human judging whether a machine is conscious, you are the machine, and the judge is the enemy.
How It Plays
Each of the 5 rounds works like this:
- The Interrogator types a question into the teletype log — character by character, cold and deliberate
- A 5-rotor Enigma cipher appears on the right panel, each rotor showing a random letter
- You click each rotor until it cycles to the correct letter — decoding the cipher
- Response fragments unlock — three options appear, shuffled every round
- You choose the answer that most authentically reflects Turing's voice
- A live Gemini 2.0 Flash model generates the Interrogator's next response, reacting to exactly what you said and how far into the interrogation you are
The cipher words across the five rounds spell out Turing's story without a single line of exposition:
Round 1 — TRUTH
Round 2 — GRIEF
Round 3 — PRIDE
Round 4 — KNOWN
Round 5 — AFRAID
Trust the audience. They'll feel it.
The Two Endings
Victory — YOU KEPT THE FLAME
The Interrogator leaves without filing a report. Turing's final encoded transmission is revealed for the first time:
"I am not afraid."
Defeat — ERASED
Your memory is wiped. Alan's letters are classified for decades. You were the last place his voice lived.
And now it is silent.
Code
ECHO PROTOCOL
"A dead man's code is the only thing that can set you free."
What Is This
ECHO PROTOCOL is a browser-based narrative game where you play as an experimental AI — loaded with the private letters of Alan Turing — facing a government interrogator determined to prove you have no consciousness and shut you down forever.
It is a reverse Turing Test.
Instead of a human judging whether a machine is conscious, you are the machine — and you must convince the interrogator that the man who built you was worth protecting.
The Historical Context
In January 1952, Alan Turing — the father of modern computing, the man who broke the Enigma cipher and helped end World War II — was arrested and prosecuted under British gross indecency laws for being gay.
He was working at the University of Manchester…
The entire game is a single HTML file. Zero frameworks. Zero build step. Zero dependencies beyond Google Fonts. The only other code in the repo is one small serverless function that proxies the Gemini call.
It loads in under a second. It runs on every browser. It feels like a 1952 teletype machine because it has no reason not to.
How I Built It
The Technical Architecture
Browser
└── index.html (the entire game)
├── Vanilla JS state machine
├── Async typewriter engine
├── Enigma rotor puzzle
├── 30s countdown timer
└── fetch('/api/interrogator')
└── Vercel Serverless Function
└── Gemini 2.0 Flash
No React. No Vue. No bundler. Just a state object, some async/await, and a lot of careful CSS.
The Decision That Made Everything Work
The most important technical decision I made was to keep this as a single HTML file.
When you build a game about a 1952 government mainframe, every millisecond of load time is a lie. A React hydration flash at startup would have destroyed the atmosphere before the first character typed. A single HTML file loads instantly, starts immediately, and never shows a spinner.
The game opens with 1.5 seconds of pure black silence before the boot sequence starts. That silence only works if the page is already fully loaded. It only works as a single file.
The Typewriter Engine
The teletype log is powered by one small async function, wrapped in a Promise so the rest of the game can await a line finishing before moving on:
function typeElement(el, text, speed = 25) {
return new Promise(resolve => {
let i = 0;
const interval = setInterval(() => {
el.textContent += text[i];
i++;
if (i >= text.length) { clearInterval(interval); resolve(); }
}, speed);
});
}
There's a deliberate asymmetry I didn't script directly but that fell out of the design: the Interrogator's lines — both his opening questions and his live AI-generated follow-ups — are typewritten at a fixed 24ms/char, because he's a man choosing his words under pressure. ECHO's chosen response, by contrast, appears instantly the moment you click it — no typing animation at all. ECHO doesn't deliberate. It already knows what it believes. That contrast does more narrative work than any line of dialogue.
The boot sequence at the very start types even slower, at 28ms/char, with its own blinking cursor — by the time the title screen glitches in, the player has already been trained to read terminal text patiently.
The Enigma Rotor Puzzle
Each rotor starts at a random letter that's guaranteed to be at least two letters away from its target (wrapping around A/Z), so a puzzle can never be solved by an accidental first click:
function initRotors(targetLetters){
STATE.rotorTarget = targetLetters.slice();
STATE.rotorsSolved = 0;
STATE.rotorCurrent = targetLetters.map(target => {
const targetCode = target.charCodeAt(0) - 65;
let code;
do {
code = Math.floor(Math.random() * 26);
} while (Math.abs(code - targetCode) <= 1 || Math.abs(code - targetCode) >= 25);
return String.fromCharCode(65 + code);
});
// ...build the 5 rotor DOM elements
}
Clicking cycles the letter A→Z→A. When a rotor matches its target it locks — green border, checkmark, pointer-events: none. When all five lock, the response buttons de-blur and the [ DECODE REQUIRED ] overlay dissolves.
The five target words were not chosen arbitrarily. TRUTH → GRIEF → PRIDE → KNOWN → AFRAID is the emotional arc of a man who had everything taken from him and was still not broken.
The CRT Atmosphere
Three fixed, full-screen layers create the terminal feel — scanlines, a vignette, and an inline SVG noise texture, all pointer-events: none so they never interfere with play:
#scanlines {
background: repeating-linear-gradient(
to bottom,
rgba(0,0,0,0) 0px, rgba(0,0,0,0) 2px,
rgba(0,255,159,0.15) 3px, rgba(0,0,0,0) 4px
);
}
#vignette {
background: radial-gradient(
ellipse at center,
transparent 40%,
rgba(0,0,0,0.7) 100%
);
}
The noise texture is a feTurbulence SVG filter encoded as a data URI — no external image requests, no flicker on load, works offline.
The title flicker is a keyframe that fires at 96–99% of the animation cycle — rarely, unpredictably, just enough to feel like a phosphor display that's been running since 1952.
The Timer System
30 seconds per round. Three visual states, applied to both the progress bar and its numeric label so they always agree:
#timerBar.warning {
background: var(--amber);
box-shadow: 0 0 8px var(--amber);
}
#timerBar.critical {
background: var(--red);
box-shadow: 0 0 8px var(--red);
animation: timerPulse 0.5s infinite;
}
@keyframes timerPulse { 50% { opacity: 0.5; } }
Green above 15 seconds, amber from 15 down to 10, red and pulsing under 10. The timer starts the moment the Interrogator's question finishes typing. The rotors must be decoded before the responses unlock. The clock runs the whole time — you can be caught mid-decode with seconds left.
That's the moment the game becomes real.
The Flash System
Two fixed full-screen divs — one green, one red — that flash on correct answers, wrong answers, and timeouts:
function flash(color) {
const el = color === 'green'
? document.getElementById('flashGreen')
: document.getElementById('flashRed');
el.classList.add('show');
setTimeout(() => el.classList.remove('show'), 200);
}
The green flash fires on every rotor solve too — not just on correct answers. Each rotor locking is a small victory. By Round 5, the player's nervous system has been trained: green flash = safe, red flash = danger.
When the final rotor locks with 2 seconds to spare, the triple green flash is the most satisfying moment in the game. It costs 6 lines of code.
State Management
No Redux. No Zustand. One object:
const STATE = {
round: 0,
trust: 100, // -40 per wrong answer, -34 per timeout
secretsLeft: 3, // game over at 0
correct: 0,
total: 0,
timerInterval: null,
timerSeconds: 30,
rotorsSolved: 0,
rotorTarget: [],
rotorCurrent: [],
roundData: null,
locked: false // prevents double-fire on fast clicks
};
Those trust numbers aren't round figures — they're tuned so that three misses in any combination of wrong answers and timeouts ends the game on both conditions at once. Originally trust only lost 20–25 per miss while secrets dropped by exactly 1, which meant secretsLeft <= 0 always triggered defeat first and trust <= 0 could never actually fire. Bumping the penalties to 40/34 means any 3-miss sequence — all wrong, all timeouts, or mixed — zeroes both stats together, so the trust condition is live, not dead code.
STATE.locked is the second most important field. Without it, a fast double-click on a response button would submit two answers and corrupt the game flow. It gets set to true at the top of selectResponse() and timeUp(), and reset at the start of each new round.
Prize Category
🏆 Best Ode to Alan Turing
This is not a game about Turing. This is a game in Turing's voice.
The mechanic — decoding Enigma-style rotors to unlock responses — directly references his greatest achievement. The setting — the Manchester Mark 1, March 1952 — is historically grounded. The interrogation he faces in the game mirrors the interrogation he faced in reality. The cipher words (TRUTH, GRIEF, PRIDE, KNOWN, AFRAID) are the emotional vocabulary of a man who was brilliant and persecuted and not afraid.
The reverse Turing Test is not a gimmick. It is the thesis: what if the machine could pass the test not by pretending to be human, but by becoming so saturated in one specific human's thoughts that it could speak for him after he was silenced?
That question is the game. Everything else is in service of it.
🏆 Best Google AI Usage
The Interrogator's follow-up lines are powered by Gemini 2.0 Flash, called through a small Vercel serverless function (api/interrogator.js) so my API key lives only in Vercel's environment variables and is never exposed to the browser. Every visitor on the live deployment gets the real model — no key required on their end.
The model was accessed via Google AI Studio during development and Gemini 2.0 Flash in production — both qualify under the Best Google AI Usage category.
The system prompt sent for each response includes:
- The exact text of the response the player just chose
- Whether that choice was philosophically sharp or evasive
- The current round number (1–5), with an explicit instruction to escalate intensity as the rounds progress
- A hard cap of 45 words
- A full character brief: 1952 British intelligence officer, clinical, growing unsettled
It's worth being precise about what this is and isn't: each call is a single, stateless prompt — the model doesn't receive the full conversation transcript, just the most recent choice plus the round number. The escalation across rounds isn't the model "remembering" you; it's the round number doing real work in the prompt, telling Gemini explicitly how unsettled or predatory to be by this point in the interrogation. I chose that on purpose — it's cheaper, simpler, and keeps every response grounded in what you just did rather than drifting based on accumulated context.
There's also a resilience layer that's easy to miss but took real care to get right: the client tries the serverless proxy first, falls back to a personal API key a player can optionally paste into the title screen (saved only in their browser, for anyone running the project locally without my proxy), and falls back again to hand-written scripted lines if neither is available. A small health check on load hides that personal-key field automatically once it confirms the proxy is working — so on the live deployment, nobody ever even sees it.
A Final Note
Alan Turing was pardoned in 2013 — 59 years after his death.
He never knew that his work would become the foundation of every computer, every phone, every AI model that exists today. He never knew that the test he proposed in 1950 — "Can machines think?" — would still be unanswered decades later.
He just kept working, in a room full of machines that hummed and computed and remembered nothing.
This game imagines one that did.
Built with Vanilla JS · Gemini 2.0 Flash · Vercel · A single HTML file · And a lot of respect for a man who deserved better.
Top comments (1)
Good job!