<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: İclal Doğan</title>
    <description>The latest articles on DEV Community by İclal Doğan (@iclaldogan).</description>
    <link>https://dev.to/iclaldogan</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3954902%2F2bd39086-cd3f-4b07-9bd7-f830c749f7ea.jpg</url>
      <title>DEV Community: İclal Doğan</title>
      <link>https://dev.to/iclaldogan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/iclaldogan"/>
    <language>en</language>
    <item>
      <title>THE NEXT SHIFT - a hospital night where the money you start with was left by the last player</title>
      <dc:creator>İclal Doğan</dc:creator>
      <pubDate>Sun, 06 Sep 2026 19:27:02 +0000</pubDate>
      <link>https://dev.to/iclaldogan/the-next-shift-a-hospital-night-where-the-money-you-start-with-was-left-by-the-last-player-1kmp</link>
      <guid>https://dev.to/iclaldogan/the-next-shift-a-hospital-night-where-the-money-you-start-with-was-left-by-the-last-player-1kmp</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for&lt;/em&gt; &lt;a href="https://dev.to/challenges/weekend-2026-09-03"&gt;&lt;em&gt;Weekend Challenge: Generosity Edition&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;THE NEXT SHIFT is a 16-bit narrative simulation about four hours of night cleaning in a public hospital — and the money you begin with is the only thing in it that isn't fiction.&lt;/p&gt;

&lt;p&gt;You don't earn your opening balance. You find it in locker 14, wrapped in a cleaning cloth, left there by whoever played this game before you, along with 140 characters they recorded on a radio that only receives. At 06:00 you take off the overalls and decide how much of what's left stays on the shelf. That decision is written to a chain, and it is what the next person opens the locker to.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F33l2hz0ez5469gqba718.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F33l2hz0ez5469gqba718.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Four people have already worked this shift. Two of them left you something.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;02:00. Corridor B is a swamp and you have a mop.&lt;/p&gt;

&lt;p&gt;The loop is small on purpose: mop the spills the desk radios you about, get paid two or three coins at a time, buy a sandwich from a machine that sometimes eats your coin. Four hours pass in seven minutes of real time. Hunger falls. Fatigue climbs.&lt;/p&gt;

&lt;p&gt;And people arrive. Not all at once — one or two at a time, all night, dealt out of a pool onto the benches and the doorways and the wet fire landing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frkkmfgh6bueljngxbsv3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frkkmfgh6bueljngxbsv3.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Neither of them has asked you for anything. That is the difficulty.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There are six of them and no more, because there are six portraits and I refuse to give one face two names. An old man four coins short of a prescription. An intern who dropped a patient's charts at three in the morning and cannot pick them up. A man outside theatre 2 who has counted the corridor tiles nine times. A head nurse who needs twenty minutes off the floor and someone who didn't see her leave. A man on the fire landing with thirty coins and a request. A cat.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft4w060enkbqdnui8env4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft4w060enkbqdnui8env4.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;He gets to finish. Nothing is asked of you while he is still talking.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fic6bh0jvstif1w626z4d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fic6bh0jvstif1w626z4d.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Eight coins is eight coins. So is three.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Every answer costs something you were counting: coins, or minutes off a four-hour clock, or half the sandwich in your pocket. Nobody thanks you at the time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But they come back.&lt;/strong&gt; Help the intern and she is worth almost nothing at the time — four points of mood, a shrug. At four in the morning she finds you in the corridor with a coffee from the doctors' machine, and that is worth fourteen. Buy the man outside theatre 2 something hot at two, and at three he may be sitting on the floor beside the chair telling you it was the bleeding, and that he has been thinking about that coffee for an hour.&lt;/p&gt;

&lt;p&gt;The night does not promise that kindness works. It only promises that it comes back to tell you.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw3rdcr3ymfcud3p0en73.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw3rdcr3ymfcud3p0en73.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Everything on this landing wants something. One of them is a cat.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There is a fourth stat under FED and TIRED, and it is called MOOD. It drains on its own, faster when you're hungry, faster still when you're wrecked — and people move it far more than coffee does. Let it fall and the game says so without a word: the vignette closes in, the colour drains out of the street, the grain lifts. Somewhere under 24 you stop in the middle of the floor and cannot immediately think why you started.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffo721a6th363tcgk3hc0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffo721a6th363tcgk3hc0.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Nothing has happened. That is somehow the worst version of it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;At 06:00 the desk says go home. You walk to locker 14, put the overalls in the bag, and look into a cracked mirror that has watched the whole shift and describes it back to you in three sentences. Then a ledger: one line per person, and what happened to them — a follow-up overwrites the first account, because it's the one that turned out to be true.&lt;/p&gt;

&lt;p&gt;Then the walkie-talkie, and the only real decision in the game.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7sfmiswoqfy4fzymlny3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7sfmiswoqfy4fzymlny3.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The game will not do this part for you.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  How it connects to the theme
&lt;/h3&gt;

&lt;p&gt;Generosity is easy to write as a button. The hard part is making it cost, and making it anonymous.&lt;/p&gt;

&lt;p&gt;So the coins are not a score. They are a real balance, sitting in a Memo Program instruction on Solana devnet, and every coin you leave is a coin you don't have. You will never meet the person who gets them. You will never know if they kept them. The only thing you can be sure of is that they will see what you left, and the 140 characters you left with it, before they mop a single tile.&lt;/p&gt;

&lt;p&gt;Every player is somebody's previous shift.&lt;/p&gt;
&lt;h3&gt;
  
  
  A word before you play
&lt;/h3&gt;

&lt;p&gt;Bring headphones — the ambience is doing more work than the graphics.&lt;/p&gt;

&lt;p&gt;It's about seven minutes. Don't rush the locker at the end; that's the part the rest of it is for. And when someone comes back to you at four in the morning, it's because of something you did at two.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Play it: &lt;a href="https://the-next-shift-henna.vercel.app" rel="noopener noreferrer"&gt;https://the-next-shift-henna.vercel.app&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No install, no wallet, no sign-in. About seven minutes.&lt;/p&gt;
&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/miclaldogan/the-next-shift" rel="noopener noreferrer"&gt;https://github.com/miclaldogan/the-next-shift&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It runs with no keys at all — silent, on a local ledger, with a hand-written mirror. Every integration degrades instead of breaking, which is the only reason I could keep building at four in the morning when a faucet was dry and a rate limiter had opinions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install
cp&lt;/span&gt; .env.example .env.local
npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;Next.js and TypeScript, one HTML5 canvas at 640×360, and a Python asset pipeline. The systems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A ledger that is a linked list, not a receipt.&lt;/strong&gt; Each handover is one transaction: a &lt;code&gt;SystemProgram.transfer&lt;/code&gt; for the coins and a memo carrying &lt;code&gt;{app, shift, leftCoins, msg, prev}&lt;/code&gt;. &lt;code&gt;prev&lt;/code&gt; is the signature of the shift &lt;em&gt;this&lt;/em&gt; player inherited — so the handovers chain, the ordering is provable from the memos instead of trusted from whatever order the RPC returned, and a broken link is detectable. Point one record at the wrong parent and the API says &lt;code&gt;verified: false&lt;/code&gt;. The title screen reads the last four back.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A mirror that has been paying attention.&lt;/strong&gt; At 06:00 Gemini receives what you earned, what you spent on yourself, what you spent on other people, your mood, and a timestamped list of every choice &lt;em&gt;and how each one turned out&lt;/em&gt; — then answers in exactly three sentences, under a response schema, with the last one required to be small and physical. It is not allowed to moralise and it is not allowed to say "kindness".&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Three voices, one of them through a radio.&lt;/strong&gt; ElevenLabs speaks the previous player's message, the desk's calls and the mirror. The first two are band-passed to 300–3400 Hz, soft-clipped with &lt;code&gt;tanh&lt;/code&gt; and laid over carrier hiss, so the walkie-talkie sounds like a walkie-talkie. The mirror plays dry.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Everything else you hear has no file behind it.&lt;/strong&gt; The fluorescent hum is a 50 Hz sawtooth through a low-pass with a 100 Hz square harmonic on top. The rain is shaped brown noise in two bands. The monitor beeping four rooms away is a sine blip on a four-second timer. There are no audio assets in the repository.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Six people, dealt over time.&lt;/strong&gt; Stories are entries in one file, gated on flags and delays: &lt;code&gt;needsFlag: "oldman_helped", delayAfterFlag: 75&lt;/code&gt;. The state records &lt;em&gt;when&lt;/em&gt; each flag was raised, so "an hour and a quarter after you paid for his prescription" is expressible as data. Seventeen of the twenty-three entries are callbacks. A person can only be in one place at once.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A validator that refuses my mistakes.&lt;/strong&gt; &lt;code&gt;npm run check:content&lt;/code&gt; rejects a story whose pose has no slot in its scene, a follow-up waiting on a flag no choice ever raises, a doorway that drops you inside another doorway's trigger, a pose more than six stories share one slot for — and one name per sprite, which is the rule I broke worst before I wrote it down.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Four things that only doing it taught me
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Chroma keying is not thresholding.&lt;/strong&gt; The art arrives composited over flat magenta, and the obvious &lt;code&gt;(r&amp;gt;200)&amp;amp;(g&amp;lt;60)&amp;amp;(b&amp;gt;200)&lt;/code&gt; is wrong in a way you only see up close: the artist painted translucent things &lt;em&gt;onto&lt;/em&gt; the magenta — mop water, wet cloth, every anti-aliased edge. My first build had a vivid pink puddle under the mop, because the water was a grey film over the backing and came out at &lt;code&gt;(80,1,73)&lt;/code&gt; — magenta, nowhere near the threshold. The fix is to stop classifying pixels and solve the compositing equation instead. Magenta excess gives fractional alpha; then un-mix the key back off.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;excess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;minimum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;
&lt;span class="n"&gt;alpha&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;excess&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;226.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;# 226 = the key's own spread
&lt;/span&gt;&lt;span class="n"&gt;fg&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rgb&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;alpha&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;maximum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alpha&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.02&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That puddle pixel comes out at alpha 0.68 over near-black — a dark wet streak on the floor, which is what it was always meant to be. Residual pink across the atlas went from 0.40 % of opaque pixels to 0.11 %, and the edge-erosion pass I'd written became unnecessary. Fractional alpha &lt;em&gt;is&lt;/em&gt; anti-aliasing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Everybody was walking sideways.&lt;/strong&gt; A tester told me the old man on the bench "keeps turning left and right". He was right, and it wasn't the animation. Frames are found by connected-component labelling and each one is cropped to its own content, then anchored at the centroid of its bottom slab — the feet. When an arm moves, the bounding box changes, so the crop changes, so the anchor changes, so the &lt;em&gt;body&lt;/em&gt; jumps. The fix is to measure the error and cancel it: stamp every frame at its own anchor, find the whole-pixel shift that best overlays it on the first frame, fold that back in. The build now prints what it corrected, and the answer was much worse than one old man:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mc.walk        10px      ← the player character, all night
oldman.walk     9px
nurse.walk_far  7px
intern.kneel    6px
…12 animations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ten pixels on a 132-pixel sprite. Nobody had reported the player sliding around, because nobody looks at the thing they're steering. They looked at the old man sitting still, where the same defect had nowhere to hide.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Thirty characters made the game worse.&lt;/strong&gt; I expanded the story pool to thirty entries and five portraits ended up playing eighteen named people. The nurse PNG was five different women. You notice in about four seconds, and then nothing in the building is true any more. Cutting back to six identities and moving the variety into &lt;em&gt;time&lt;/em&gt; is the single best change I made — and I now generate names from a table keyed by sprite, so a story physically cannot invent a second face.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The live API is a different API.&lt;/strong&gt; A Gemini key issued today can call neither &lt;code&gt;gemini-2.0-flash&lt;/code&gt; nor &lt;code&gt;gemini-2.5-flash&lt;/code&gt; — both are closed to new users, and it tells you at request time, as a 404 with a sentence of prose inside it. So the model is an alias, not a pin. Then the free tier started answering &lt;code&gt;503 "This model is currently experiencing high demand"&lt;/code&gt; on roughly one request in three, at random — I spent twenty minutes certain my schema was malformed before I noticed a &lt;em&gt;plain&lt;/em&gt; request failing the same way. Retrying the same model against a capacity wall is optimism, so three attempts rotate across two models with backoff. It lands on the second one now. And Flash is a thinking model: &lt;code&gt;parts&lt;/code&gt; can carry reasoning beside the answer, and concatenating all of them produces invalid JSON from a model emitting perfectly valid JSON.&lt;/p&gt;

&lt;p&gt;And the disclaimer this game specifically needs: it is &lt;strong&gt;devnet&lt;/strong&gt;. The coins&lt;br&gt;
are test tokens worth nothing, nobody connects a wallet, and no player action&lt;br&gt;
can move real money — a backend operator keypair signs the handovers. The&lt;br&gt;
operator address shows up in the explorer links, so the ending says it plainly:&lt;br&gt;
if the game works on you, give something to someone who needs it, not to that&lt;br&gt;
address.&lt;/p&gt;

&lt;p&gt;One more, for anyone about to reach for a Solana tutorial: &lt;strong&gt;there is no API key.&lt;/strong&gt; Devnet's RPC is public and keyless. What you need is a signing key, which nobody issues to you — &lt;code&gt;Keypair.generate()&lt;/code&gt; is the whole ceremony. The one thing worth paying attention to is that the public faucet is rate limited and frequently dry, which it told me in production terms about eight times in a row, so a provider endpoint in &lt;code&gt;SOLANA_RPC_URL&lt;/code&gt; is the difference between a demo that works when four judges open it at once and one that doesn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;p&gt;I'm submitting to three: &lt;strong&gt;Best Use of Google AI&lt;/strong&gt;, &lt;strong&gt;Best Use of ElevenLabs&lt;/strong&gt;, and &lt;strong&gt;Best Use of Solana.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Best Use of Google AI
&lt;/h3&gt;

&lt;p&gt;Gemini is not just an AI feature in THE NEXT SHIFT — it is &lt;strong&gt;the mirror that has been watching your entire night.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At 06:00, Gemini receives what you earned, what you spent on yourself, what you spent on other people, your final mood, and a timestamped history of every choice you made — including how those choices eventually turned out.&lt;/p&gt;

&lt;p&gt;It then writes the cracked mirror's reflection of your shift in exactly three sentences. The response is schema-controlled, the final sentence must stay small and physical, and Gemini is explicitly forbidden from moralising or even using the word "kindness."&lt;/p&gt;

&lt;p&gt;The result is not a generic ending generated from a final score. Gemini sees the whole sequence of the player's night and turns it into a personalized reflection of what actually happened.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best Use of ElevenLabs
&lt;/h3&gt;

&lt;p&gt;ElevenLabs is &lt;strong&gt;why THE NEXT SHIFT is meant to be played with headphones.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It gives a voice to the previous player's message waiting inside locker 14, the hospital desk calls that interrupt your shift, and the cracked mirror at 06:00.&lt;/p&gt;

&lt;p&gt;But those voices are not simply played as clean text-to-speech. The previous player's message and the desk calls are band-passed to &lt;strong&gt;300–3400 Hz&lt;/strong&gt;, soft-clipped with &lt;code&gt;tanh&lt;/code&gt;, and layered over carrier hiss so they actually sound like transmissions coming through an old walkie-talkie.&lt;/p&gt;

&lt;p&gt;The mirror is deliberately different: its voice plays dry, without the radio processing.&lt;/p&gt;

&lt;p&gt;That means ElevenLabs is not sitting on top of the game as narration — its voices belong to different physical objects inside the world, and the way each voice sounds tells you where it is coming from.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best Use of Solana
&lt;/h3&gt;

&lt;p&gt;Solana is not used as a receipt after the game — &lt;strong&gt;the chain is part of the premise.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The opening balance you find in locker 14 comes from the previous player's handover. At the end of your own shift, you decide how many coins remain for the next person and leave them a message of up to 140 characters.&lt;/p&gt;

&lt;p&gt;Each handover becomes a Solana devnet transaction containing a &lt;code&gt;SystemProgram.transfer&lt;/code&gt; and a memo with:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;{app, shift, leftCoins, msg, prev}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;prev&lt;/code&gt; field points to the signature of the shift that player inherited, turning the handovers into a linked chain rather than a list whose ordering has to be trusted.&lt;/p&gt;

&lt;p&gt;The game can therefore verify the relationship between one shift and the next, detect a broken link, and reconstruct the previous handovers shown on the title screen.&lt;/p&gt;

&lt;p&gt;It is &lt;strong&gt;devnet&lt;/strong&gt;: the coins are test tokens worth nothing, players connect no wallet, and no player action can move real money.&lt;/p&gt;

&lt;p&gt;What matters is the idea behind the chain: every player begins with something another anonymous player chose to leave behind, and eventually has to decide what the next stranger will find in the same locker.&lt;/p&gt;




&lt;p&gt;Solo submission — built and designed by Iclal Doğan.&lt;br&gt;
GitHub: &lt;a href="https://github.com/miclaldogan/the-next-shift" rel="noopener noreferrer"&gt;@miclaldogan&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Bring headphones. Mop the corridor. And when you get to the locker, don't count it twice — you already know what you're going to do.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
    </item>
    <item>
      <title>ECHOLALIA: A Haunted Desktop Where Two AI Voices Finish Writing You</title>
      <dc:creator>İclal Doğan</dc:creator>
      <pubDate>Mon, 13 Jul 2026 02:14:20 +0000</pubDate>
      <link>https://dev.to/iclaldogan/echolalia-a-haunted-desktop-where-two-ai-voices-finish-writing-you-5hhk</link>
      <guid>https://dev.to/iclaldogan/echolalia-a-haunted-desktop-where-two-ai-voices-finish-writing-you-5hhk</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/weekend-2026-07-09"&gt;Weekend Challenge: Passion Edition&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;ECHOLALIA is a psychological-horror art simulation that runs in your browser as a haunted operating system — and the horror is that you are not the one playing it.&lt;/p&gt;

&lt;p&gt;You don't control a character. You put on headphones, and at 3:33 AM you log into a dead writer's desk — an antique, candlelit machine carved out of heavy oak — where two voices are already waiting for you.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CRITIC&lt;/strong&gt; speaks into your left ear: cold, precise, perfectionist. He hunts your clichés and takes your sentences apart on literary grounds — while quietly feeding the dread.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MUSE&lt;/strong&gt; speaks into your right ear: fervent, boundaryless, in love with the worst thing you could possibly write. She calls the Critic an idiot, and she whispers your name. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The two voices — CRITIC in your left ear, MUSE in your right — arguing over your shoulder before you have written a single word.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1kzv6ht6m71ntvqt8f01.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1kzv6ht6m71ntvqt8f01.png" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqc53bglshrwmphxbjjnf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqc53bglshrwmphxbjjnf.png" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Before you type anything, they are already fighting about you. CRITIC (left) calls you Julian; MUSE (right) corrects him with the name you actually gave.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;They are the two halves of a writer's psyche, and they are both powered by Google Gemini. They interrogate you. They read you. They hand you back a manuscript that is already started — mid-sentence, in a voice suspiciously like your own. Then you write, and they tear it apart live. A folder quietly fills with photographic "evidence." An impossible archive answers questions you should never have asked. And somewhere around the tenth sentence, you notice that pressing keys no longer produces your words.&lt;/p&gt;

&lt;p&gt;The design principle is &lt;strong&gt;"Radical Intimacy, Slow Dispossession"&lt;/strong&gt;: the closer the voices get — the more they know you, the more tenderly they say your name — the less of the manuscript is still yours. Every generation happens live, per playthrough. Nothing is pre-scripted. Two people never descend the same way.&lt;/p&gt;

&lt;p&gt;In the final stretch every keystroke renders the voices' words instead of yours — total loss of the keyboard. Then the machine begins to die: the windows go out one by one, the candlelight gutters down from a warm glow to a 5% flicker to pitch black, and the whole desk collapses into the dark. What it whispers at the very end — in a voice that finally uses your real name — is the one thing I'm keeping out of this post. You'll want to hear that one yourself.&lt;/p&gt;

&lt;p&gt;ECHOLALIA is not a game you win. It is a game that finishes writing you, collapses the desk into darkness, and hands you the manuscript to keep — a &lt;code&gt;.md&lt;/code&gt; file with two authors, only one of whom you agreed to.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;What it does in the game&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Google Gemini&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Builds a psychological profile from your conversation · writes your manuscript's personalized half-finished opening · critiques every sentence in two distinct characters, with an anti-repetition memory of everything said tonight · seizes the editor to type sinister continuations · fabricates every page of the in-game OBSCURA archive · writes the finale your own keystrokes reveal.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Google Imagen&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Develops sepia, damaged-glass-plate "evidence" photographs from your sentences · paints the desktop backdrop from your profile after the reading.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ElevenLabs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Gives both voices real voices — panned hard left / right to match their windows, with name-lines whispered · generates the sounds your sentences describe — footsteps, tape hiss, knocking — through the Sound Effects API.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How it connects to the theme
&lt;/h2&gt;

&lt;p&gt;The prompt asks for passion — obsession, devotion, the love that fuels late-night side projects. ECHOLALIA is built out of the one passion I have never managed to put down: writing, and the two voices every writer carries at 3 AM.&lt;/p&gt;

&lt;p&gt;Because that is what the Critic and the Muse really are. Not villains — the two halves of any creative obsession. The perfectionist in your left ear who is certain everything you make is a cliché, and the muse in your right who is in love with the darkest, rawest, worst thing you could possibly say. Anyone who has ever cared too much about a thing they were making knows this fight intimately. I just gave it two voices, panned them hard left and right, and let them argue over your shoulder while you work.&lt;/p&gt;

&lt;p&gt;And I am unreasonably in love with sound — so the whole game is staged for headphones. The voices are real voices; the sounds your sentences describe get conjured out of the air; your name arrives as a whisper. Passion, obsession, the thing you cannot stop making even as it starts making you — that is not a theme bolted onto ECHOLALIA. It is the entire machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  A word before you play
&lt;/h2&gt;

&lt;p&gt;Bring headphones. This is non-negotiable — the two voices live in separate ears, some lines are whispered, and on speakers you lose half the game. Answer the interrogation honestly, too: the voices build everything — your manuscript, every critique, your fabricated archive, your ending — out of what you actually tell them. Lie to them and all you get is a shallower haunting. A full descent runs 10–15 minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  On the voices
&lt;/h2&gt;

&lt;p&gt;Some of you will ask why the two voices contradict each other so violently — why one is glacial and the other is unhinged. That is the point. They are not two characters; they are one split psyche, and they share a memory of everything said tonight so they never recycle a cruelty. The Critic never curses — his cruelest blow always lands in the calmest tone. The Muse never stops. If it ever feels like the room is arguing &lt;em&gt;about&lt;/em&gt; you rather than &lt;em&gt;with&lt;/em&gt; you — good. It is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Play it live:&lt;/strong&gt; &lt;a href="https://echolalia-miclaldogan-6337s-projects.vercel.app" rel="noopener noreferrer"&gt;https://echolalia-miclaldogan-6337s-projects.vercel.app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Put on headphones, turn the lights down, and answer honestly. It runs entirely in the browser — there is nothing to install.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A note for judges: the voices, the live sabotage, the sound effects, OBSCURA, and the full collapse all run out of the box. The sepia "evidence" photographs are the single paid-tier feature (Google Imagen has no free image quota — see &lt;strong&gt;Cost Safety&lt;/strong&gt; in the repo), so that folder may stay empty on the shared key. The descent is whole without them. If the shared voice quota ever runs dry mid-demo, the wax seal at the bottom-right of the taskbar opens &lt;strong&gt;The Vault&lt;/strong&gt;, where you can paste your own ElevenLabs key for the sitting.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/miclaldogan" rel="noopener noreferrer"&gt;
        miclaldogan
      &lt;/a&gt; / &lt;a href="https://github.com/miclaldogan/echolalia" rel="noopener noreferrer"&gt;
        echolalia
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      🕯️ A psychological-horror art simulation where two Gemini-powered inner voices — Critic &amp;amp; Muse — read you, sabotage your writing, and slowly stop letting you type. Live AI dialogue, ElevenLabs stereo voices, Imagen evidence, and a manuscript you can't take back.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;ECHOLALIA&lt;/h1&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;&lt;em&gt;A psychological-horror art simulation where the AI is inside your head — and it stops letting you write.&lt;/em&gt;&lt;/h3&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://echolalia-miclaldogan-6337s-projects.vercel.app" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/e304698086e3bb5cf1019ce23148a9c08ea16fe11148baf15ea812ebe355e061/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f2545322539362542365f504c41592d6c6976652d3865326632353f7374796c653d666f722d7468652d6261646765" alt="Play Now"&gt;&lt;/a&gt;
&amp;nbsp;
&lt;a href="https://github.com/miclaldogan/echolalia/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/0415aa8bc9ca9cc1f078d8500cc9228b839caf8acbb56d15f48c8f69c4264557/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d6337396135623f7374796c653d666f722d7468652d6261646765" alt="License: MIT"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/345fe6d05b79b02f24f3e639a63259d9bb806130d61c7736dea8e1d8e9d3d55a/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f56616e696c6c615f4a532d6e6f5f6672616d65776f726b2d3234313731303f6c6f676f3d6a617661736372697074266c6f676f436f6c6f723d633739613562"&gt;&lt;img src="https://camo.githubusercontent.com/345fe6d05b79b02f24f3e639a63259d9bb806130d61c7736dea8e1d8e9d3d55a/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f56616e696c6c615f4a532d6e6f5f6672616d65776f726b2d3234313731303f6c6f676f3d6a617661736372697074266c6f676f436f6c6f723d633739613562" alt="Vanilla JS"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/e6e2b06fe949b745d91b5e41136498c4a39198bb8f831fe5acafcf52e4859abb/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f56657263656c2d507974686f6e5f5365727665726c6573732d3234313731303f6c6f676f3d76657263656c266c6f676f436f6c6f723d7768697465"&gt;&lt;img src="https://camo.githubusercontent.com/e6e2b06fe949b745d91b5e41136498c4a39198bb8f831fe5acafcf52e4859abb/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f56657263656c2d507974686f6e5f5365727665726c6573732d3234313731303f6c6f676f3d76657263656c266c6f676f436f6c6f723d7768697465" alt="Vercel"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/dc5c0674b83563e191df10615c484de0e3d18e57bbde092e1da5a4daebf216a4/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f476f6f676c652d47656d696e695f2b5f496d6167656e2d3234313731303f6c6f676f3d676f6f676c65266c6f676f436f6c6f723d7768697465"&gt;&lt;img src="https://camo.githubusercontent.com/dc5c0674b83563e191df10615c484de0e3d18e57bbde092e1da5a4daebf216a4/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f476f6f676c652d47656d696e695f2b5f496d6167656e2d3234313731303f6c6f676f3d676f6f676c65266c6f676f436f6c6f723d7768697465" alt="Google Gemini"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/c840cef70ad34e7cd36f74feaae3c228188b684743b1f960a8c2c5e8a0cac181/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f456c6576656e4c6162732d5454535f2b5f5346582d323431373130"&gt;&lt;img src="https://camo.githubusercontent.com/c840cef70ad34e7cd36f74feaae3c228188b684743b1f960a8c2c5e8a0cac181/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f456c6576656e4c6162732d5454535f2b5f5346582d323431373130" alt="ElevenLabs"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;You don't control a character. You put on headphones and sit down at a dead writer's desk after midnight.&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;

&lt;div&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/miclaldogan/echolalia/docs/screenshots/play_03_firstsentence.jpg"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmiclaldogan%2Fecholalia%2FHEAD%2Fdocs%2Fscreenshots%2Fplay_03_firstsentence.jpg" alt="The wooden desk: CRITIC (left), the manuscript (center), and MUSE (right), carved out of dark oak" width="92%"&gt;&lt;/a&gt;
  &lt;br&gt;
  &lt;i&gt;CRITIC in your left ear, MUSE in your right, and a manuscript that is already writing itself.&lt;/i&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;The premise&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;It is &lt;strong&gt;3:33 AM&lt;/strong&gt;. You log into a crude wooden machine — an antique, candlelit desktop carved out of heavy oak — and two voices are already waiting for you.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CRITIC&lt;/strong&gt; speaks into your &lt;strong&gt;left ear&lt;/strong&gt;: cold, precise, perfectionist. He hunts your clichés and tears your sentences apart on literary grounds — while quietly feeding the dread.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MUSE&lt;/strong&gt; speaks into your &lt;strong&gt;right ear&lt;/strong&gt;: fervent, boundaryless, in love with the worst thing you could possibly write. She calls the Critic an idiot and whispers your name.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;They are the two halves…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/miclaldogan/echolalia" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;ECHOLALIA is deliberately &lt;strong&gt;frameworkless and buildless&lt;/strong&gt; — vanilla HTML + TailwindCSS (CDN) + vanilla JS ES modules. No framework, no bundler, no build step. The browser is a fat client that owns 100% of the game state: the whole session lives in one &lt;code&gt;gameState&lt;/code&gt; object in memory, which means &lt;strong&gt;the client is the save file&lt;/strong&gt; — the export button just serializes what you lived through. The backend is a set of pure, stateless Python functions on Vercel (Fluid Compute), one file per endpoint — &lt;code&gt;profile · chat · sabotage · tts · sfx · image · browser · ping&lt;/code&gt; — and the API keys never leave the server side. The frontend only ever calls its own &lt;code&gt;/api/*&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A few systems do the heavy lifting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A shared-psyche dialogue engine.&lt;/strong&gt; Both voices run on Gemini &lt;code&gt;3.1-flash-lite&lt;/code&gt; — chosen specifically because ~1s latency is what makes the sabotage feel &lt;em&gt;live&lt;/em&gt;; a slower model would break the possession. They carry an anti-repetition memory of the whole night so no insult is ever recycled.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A possessed manuscript editor.&lt;/strong&gt; Backspace, Delete, and Ctrl+Z are dead — what is written stays written (that is not a bug). As the night deepens, the voices seize the keyboard more and more often, typing dark continuations into your document while you watch; in the final stretch, every key you press renders &lt;em&gt;their&lt;/em&gt; words instead of yours.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OBSCURA — a fake browser.&lt;/strong&gt; It fabricates encyclopedia entries, dead forum threads, and newspaper clippings for any query, weaving your profile into them. It never touches the real internet, and every page is sanitized twice (server-side tag allowlist + client-side DOM scrub).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fanc9tudm6qdszz97p59s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fanc9tudm6qdszz97p59s.png" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;OBSCURA answers any search with an invented archive. Here, a whole encyclopedia entry — Elara Vance, a recordist who tried to capture absolute silence — conjured on the spot by Gemini, and quietly stitched from what you told the voices.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A pure-CSS "wooden desk."&lt;/strong&gt; The entire aesthetic — charred-oak grain via an SVG turbulence filter, carved boards, iron nails, sealing wax, letterpress text — is CSS. Zero image assets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failure that stays in fiction.&lt;/strong&gt; Timeouts and quota errors never surface a dialog box; the screen flickers like a guttering candle, a voice goes quiet, or "the air grows thin."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F26rfbzgjuyedr2ifuel0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F26rfbzgjuyedr2ifuel0.png" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;p&gt;I'm submitting to two: &lt;strong&gt;Best Use of Google AI&lt;/strong&gt; and &lt;strong&gt;Best Use of ElevenLabs.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Best Use of Google AI
&lt;/h3&gt;

&lt;p&gt;Gemini is not a feature in ECHOLALIA — it is &lt;strong&gt;both authors.&lt;/strong&gt; In a single playthrough Gemini builds your psychological profile from the conversation, writes the personalized half-finished sentence it hands back to you, critiques every sentence you write in two distinct characters with memory of the whole night, seizes the editor to type sinister continuations, fabricates every page of the OBSCURA archive, and writes the finale your own keystrokes reveal. And &lt;strong&gt;Google Imagen&lt;/strong&gt; does all the &lt;em&gt;seeing&lt;/em&gt; — it develops your sentences into sepia, damaged-glass-plate evidence photographs and paints the desktop backdrop from your profile. Two of Google's models, doing every word and every image in the game, live, with nothing pre-scripted.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best Use of ElevenLabs
&lt;/h3&gt;

&lt;p&gt;ElevenLabs is &lt;em&gt;why you need the headphones.&lt;/em&gt; It gives the Critic and the Muse real, distinct voices, &lt;strong&gt;panned hard left and right&lt;/strong&gt; to match their windows — so the argument physically happens on two sides of your head — and the lines that carry your name are whispered. But it isn't only speech: the &lt;strong&gt;Sound Effects API&lt;/strong&gt; generates the sounds your own prose describes — footsteps, tape hiss, a knock at a door you wrote — so the world answers what you type. And &lt;strong&gt;The Vault&lt;/strong&gt; lets a judge paste their own ElevenLabs key mid-demo if the shared quota dies, so the voices never have to fall silent.&lt;/p&gt;




&lt;p&gt;Solo submission — built and designed by İclal Doğan (&lt;a href="https://github.com/miclaldogan" rel="noopener noreferrer"&gt;@miclaldogan&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Bring headphones. Answer honestly. And if the Critic asks whose hand wrote the second half — don't check.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
    </item>
    <item>
      <title>RESIDUES: A Terminal Game Inside the Dying Mind of Alan Turing</title>
      <dc:creator>İclal Doğan</dc:creator>
      <pubDate>Sat, 20 Jun 2026 23:53:24 +0000</pubDate>
      <link>https://dev.to/iclaldogan/residues-a-terminal-game-inside-the-dying-mind-of-alan-turing-2p67</link>
      <guid>https://dev.to/iclaldogan/residues-a-terminal-game-inside-the-dying-mind-of-alan-turing-2p67</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/june-game-jam-2026-06-03"&gt;June Solstice Game Jam&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;em&gt;Content note: this game deals with suicide, chemical castration, and the slow breakdown of a mind. It's a story about the death of Alan Turing, told honestly.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;RESIDUES&lt;/strong&gt; is a terminal-native narrative puzzle game, written in Rust, that runs entirely inside your terminal — no graphics window, no mouse, just 24-bit amber phosphor in the dark.&lt;/p&gt;

&lt;p&gt;You are inside the failing mind of Alan Turing on the night of his death — Wilmslow, 8 June 1954. As his cognition collapses, he replays the entire lineage of computing as six interactive puzzles, each one rebuilding the real idea its pioneer gave the world. The principle behind every act is &lt;strong&gt;"Bright Puzzle, Dark Undertow":&lt;/strong&gt; the puzzle in front of you is a clean, genuinely deep technical challenge, while underneath runs the human tragedy of the mind that invented it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Act&lt;/th&gt;
&lt;th&gt;The Mind&lt;/th&gt;
&lt;th&gt;Year&lt;/th&gt;
&lt;th&gt;What you actually do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;I&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Joseph-Marie Jacquard&lt;/td&gt;
&lt;td&gt;1804&lt;/td&gt;
&lt;td&gt;Punch an 8×8 bit matrix into modular cards — a continuous roll tears, a block-chain jams, only a looping deck survives.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;II&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Charles Babbage&lt;/td&gt;
&lt;td&gt;1837&lt;/td&gt;
&lt;td&gt;Derive a baseline by the method of differences, then survive the carry crisis by staggering delay buffers so the carry travels as a wave.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;III&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Ada Lovelace&lt;/td&gt;
&lt;td&gt;1843&lt;/td&gt;
&lt;td&gt;Write real assembly on a 3-register machine; overrun the linear cap to unlock loops, then compute a checksum with a loop that devours its own counter.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;IV&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;George Boole&lt;/td&gt;
&lt;td&gt;1854&lt;/td&gt;
&lt;td&gt;Steer a grid of logic gates to a target checksum while the &lt;em&gt;displayed&lt;/em&gt; output starts lying — but the truth always holds beneath.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;V&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Claude Shannon&lt;/td&gt;
&lt;td&gt;1937&lt;/td&gt;
&lt;td&gt;Assign prefix-free codes within a fixed channel capacity, then add a parity guard against bit-flip noise. Real information theory.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;VI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Alan Turing&lt;/td&gt;
&lt;td&gt;1936&lt;/td&gt;
&lt;td&gt;Operate a real Turing machine — a head over a tape, a transition table — stabilizing the five prior acts' corrupted residues until the machine reaches HALT.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;And the puzzles are &lt;em&gt;real&lt;/em&gt; — not reskinned button-presses. There's a working 7-instruction assembly interpreter, a genuine Turing machine with a complete transition table, real Shannon channel-capacity math, real carry-propagation. &lt;strong&gt;The puzzles ARE the history.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How it connects to the jam.&lt;/strong&gt; The June solstice is the year's turning point — the hinge where light begins to lose to darkness. RESIDUES is built entirely on that hinge. Alan Turing was born in June; June is also Pride Month, and the jam itself names him — the father of the Turing Test, persecuted for who he was. Across every act the candle on the desk burns lower, the heartbeat speeds the metronome, and memory corrupts into noise. &lt;em&gt;Light and darkness, the passage of time, a turning point&lt;/em&gt; — those are the jam's own words, and they are the game's three core systems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A word before you play.&lt;/strong&gt; Jacquard and Babbage ease you in — the opening acts are gentle, and we're warming you up on purpose. But fair warning: after that, the machine starts asking you to actually &lt;em&gt;think&lt;/em&gt;. If you get stuck somewhere, don't be mad at us — some acts are deliberately demanding with little hand-holding. And if you &lt;em&gt;don't&lt;/em&gt; get stuck? Then you already know this craft, and you have our respect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On Turing's voice.&lt;/strong&gt; Some of you will ask why we gave Turing such a broken, sick, dissolving voice. Here's why: the game dramatizes his final days — the drugs he was forced to take, and the very night he died. There was cyanide in the next room for his experiments and a half-eaten apple by his bed; whether it was an accident, an experiment, or a choice, no one will ever know. We didn't think a man would have a clear, beautiful voice on his last night — so we deliberately didn't give him one. The decay you hear is the point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Video Demo
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/or6dscTENFs"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;It's a full ~15-minute playthrough, so feel free to skip around — every act is in there. If you're short on time, the chapters in the video description jump straight to the key moments: &lt;strong&gt;Act III (writing real assembly)&lt;/strong&gt;, &lt;strong&gt;Act VI (the Turing machine)&lt;/strong&gt;, and &lt;strong&gt;the ending&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5nspoj4vvki3qxx8w1np.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5nspoj4vvki3qxx8w1np.jpeg" alt=" " width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4twhmhyla1n8x6pdqmn0.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4twhmhyla1n8x6pdqmn0.jpeg" alt=" " width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/miclaldogan" rel="noopener noreferrer"&gt;
        miclaldogan
      &lt;/a&gt; / &lt;a href="https://github.com/miclaldogan/residuesTerminal" rel="noopener noreferrer"&gt;
        residuesTerminal
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Residues: terminal edition — a true-color Rust/Ratatui TUI port (cinematic typewriter prelude, Braille portraits, Jacquard/Babbage acts)
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;RESIDUES — An Engine of Residual Minds&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A terminal-native narrative puzzle game about the birth of computation, written in Rust.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"The most terrifying part is learning how to forget."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;RESIDUES runs entirely inside your terminal — no graphics window, no mouse — rendering a truecolor (24-bit) amber-phosphor aesthetic. You are inside the failing mind of Alan Turing on the night of his death (Wilmslow, 8 June 1954). As his cognition collapses, he replays the entire lineage of computing as six interactive puzzles, each one rebuilding the actual idea its pioneer gave the world.&lt;/p&gt;
&lt;p&gt;The design principle is &lt;strong&gt;"Bright Puzzle, Dark Undertow":&lt;/strong&gt; every act is a clean, genuinely deep technical challenge, while underneath runs the human tragedy of the mind that invented it.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;The Six Acts&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Each act is a real computational toy — not a reskinned button-press. You don't read &lt;em&gt;about&lt;/em&gt; the machine; you operate one.&lt;/p&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Act&lt;/th&gt;
&lt;th&gt;Mind&lt;/th&gt;
&lt;th&gt;Year&lt;/th&gt;
&lt;th&gt;What&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;/table&gt;&lt;/div&gt;…&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/miclaldogan/residuesTerminal" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;It's genuinely easy to run yourself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/miclaldogan/residuesTerminal.git
&lt;span class="nb"&gt;cd &lt;/span&gt;residuesTerminal
cargo run &lt;span class="nt"&gt;--release&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or zero-install:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sSL&lt;/span&gt; https://raw.githubusercontent.com/miclaldogan/residuesTerminal/main/run.sh | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Play with headphones, in a truecolor terminal.&lt;/strong&gt; The audio is binaural — ghost whispers are hard-panned left and right, and the heartbeat sits under everything — so a lot of the experience is lost on speakers. (The game probes for &lt;code&gt;mpv&lt;/code&gt; / &lt;code&gt;ffplay&lt;/code&gt; / &lt;code&gt;mpg123&lt;/code&gt;; if none is installed it runs completely silent but fully playable — no crash.)&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;RESIDUES is written from scratch in &lt;strong&gt;Rust&lt;/strong&gt; (~10,400 lines, 65 passing tests) on &lt;strong&gt;ratatui + crossterm&lt;/strong&gt;. The challenge wasn't drawing screens — it was making a terminal &lt;em&gt;feel alive&lt;/em&gt;. A few systems do the heavy lifting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A real assembly micro-compiler&lt;/strong&gt; (&lt;code&gt;LOAD/STORE/ADD/SUB/IF_Z/JMP/HALT&lt;/code&gt;) with its own error codes — Lovelace's act is genuinely a register-allocation puzzle, and a &lt;code&gt;[REGISTRY ERROR]&lt;/code&gt; is the &lt;em&gt;narrative&lt;/em&gt; trigger that unlocks loops.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A crate-free, lock-free audio engine&lt;/strong&gt; that spawns detached OS players, layering an ambient score, rain, hard-panned binaural whispers, and synced voice-overs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A live heartbeat that drives the world&lt;/strong&gt; — the BPM readout isn't decoration; it physically speeds the background metronome, climbing as Turing's interrogation tightens and spiking into arrhythmia on a wrong answer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A decay engine&lt;/strong&gt; — as the chemical toxicity rises, the text on screen corrupts into glyphs in real time, so you watch the memory being erased.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A candle that is the only light source&lt;/strong&gt;, with a radial light-degradation engine anchored on the flame, guttering from 100% down to a 5% flicker by the final act.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A PNG → truecolor half-block renderer&lt;/strong&gt; for the cinematic portraits.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I used Google's &lt;strong&gt;Gemini&lt;/strong&gt; as a brainstorming partner for the historical framing and act structure. The voice and whisper audio was generated with &lt;strong&gt;ElevenLabs&lt;/strong&gt; (paid plan, commercial license).&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Category
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best Ode to Alan Turing.&lt;/strong&gt; Turing isn't a tribute bolted on at the end — he's the gravity the whole game falls toward, across all three things the category asks for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mechanics.&lt;/strong&gt; Act VI is a &lt;em&gt;literal&lt;/em&gt; Turing machine: a tape, a moving head, a transition table, and corrupted residues you stabilize until it reaches HALT. You don't read about the Universal Machine — you run one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Narrative.&lt;/strong&gt; His full arc is the climax: breaking Enigma, the silence of the Official Secrets Act, the trial for "gross indecency," the chemical castration, and his last night. The ending — which I'm keeping out of this post on purpose — recontextualizes the entire descent. It's in the video.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Design.&lt;/strong&gt; Five centuries of computing — Jacquard's cards, Babbage's gears, Lovelace's loops, Boole's logic, Shannon's entropy — are arranged as a pilgrimage that exists to deliver you to Turing's desk, in his birth month, in a jam built around the very turning point his life embodies.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Solo submission — built and designed by Iclal Doğan.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>gamechallenge</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>I Built a 1920s Butler AI That Runs Entirely on My Linux Machine. Then I Abandoned It. Then Copilot Helped Me Fix It.</title>
      <dc:creator>İclal Doğan</dc:creator>
      <pubDate>Sat, 06 Jun 2026 12:22:25 +0000</pubDate>
      <link>https://dev.to/iclaldogan/i-built-a-1920s-butler-ai-that-runs-entirely-on-my-linux-machine-then-i-abandoned-it-then-copilot-5ela</link>
      <guid>https://dev.to/iclaldogan/i-built-a-1920s-butler-ai-that-runs-entirely-on-my-linux-machine-then-i-abandoned-it-then-copilot-5ela</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/github-2026-05-21"&gt;GitHub Finish-Up-A-Thon Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Bantz&lt;/strong&gt; is a local-first, offline-capable AI assistant that runs entirely on your Linux machine. It presents itself as a 1920s English butler — always polite, subtly sarcastic, and absolutely convinced he is a real person standing in the room with you.&lt;/p&gt;

&lt;p&gt;I'm a Turkish speaker on a Linux desktop. Every cloud assistant I tried spoke to me in a foreign language, phoned home to someone else's server, and forgot everything the moment the session ended. I wanted something different: an assistant that speaks Turkish natively, runs on my own hardware, remembers its context, and actually controls my desktop. So I started building Bantz.&lt;/p&gt;

&lt;p&gt;The concept is ambitious. At its core: a Turkish ↔ English translation layer powered by Helsinki-NLP's MarianMT, a multi-step tool planner that can chain web search, Gmail, Calendar, shell commands, filesystem access, and AT-SPI desktop automation — all coordinated by an LLM running locally via Ollama. On top of that: voice I/O via faster-whisper and Piper TTS, persistent memory backed by ChromaDB + a SQLite knowledge graph (MemPalace), a 6-state butler persona that shifts tone based on CPU load and time of day, and a Textual TUI with a live health-status bar.&lt;/p&gt;

&lt;p&gt;The architecture was genuinely interesting. The execution, as of May 2026, was a mess.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;GitHub repo:&lt;/strong&gt; &lt;a href="https://github.com/miclaldogan/bantzv2" rel="noopener noreferrer"&gt;github.com/miclaldogan/bantzv2&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Broadcast Channel&lt;/strong&gt; — chatting with Bantz, web search + desktop control in action:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmiclaldogan%2Fbantzv2%2Fmain%2Fbantz-demo%2Fseg1.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmiclaldogan%2Fbantzv2%2Fmain%2Fbantz-demo%2Fseg1.gif" width="720" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full walkthrough&lt;/strong&gt; — all pages of the Operations Center:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmiclaldogan%2Fbantzv2%2Fmain%2Fbantz-demo%2Fseg5.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmiclaldogan%2Fbantzv2%2Fmain%2Fbantz-demo%2Fseg5.gif" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Screenshots
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmiclaldogan%2Fbantzv2%2Fmain%2Fbantz-demo%2FbantzChat.jpeg" width="800" height="450"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmiclaldogan%2Fbantzv2%2Fmain%2Fbantz-demo%2FbantzVitals.jpeg" width="800" height="450"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmiclaldogan%2Fbantzv2%2Fmain%2Fbantz-demo%2FbantzLogs.jpeg" width="800" height="450"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmiclaldogan%2Fbantzv2%2Fmain%2Fbantz-demo%2FbantzDirectives.jpeg" width="800" height="450"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmiclaldogan%2Fbantzv2%2Fmain%2Fbantz-demo%2FbantzAnomalyWatch.jpeg" width="800" height="450"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmiclaldogan%2Fbantzv2%2Fmain%2Fbantz-demo%2FbantzSettings.jpeg" width="800" height="450"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The Comeback Story
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Before — May 2026
&lt;/h3&gt;

&lt;p&gt;I had a 17-issue backlog and a &lt;code&gt;BROKEN_STATE.md&lt;/code&gt; file I'd written to document the damage. Here's what it said:&lt;/p&gt;

&lt;p&gt;The feature audit was brutal:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Voice input (Whisper)&lt;/td&gt;
&lt;td&gt;🔴 Broken — 3 packages missing, Picovoice key unset&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TUI status bar&lt;/td&gt;
&lt;td&gt;❌ Didn't exist&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;First-run onboarding&lt;/td&gt;
&lt;td&gt;❌ Blank cursor, zero guidance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-provider LLM support&lt;/td&gt;
&lt;td&gt;🔴 Every finalizer hardcoded &lt;code&gt;ollama&lt;/code&gt; directly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Turkish response latency&lt;/td&gt;
&lt;td&gt;🔴 12–18 seconds end-to-end&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;--doctor&lt;/code&gt; diagnostic&lt;/td&gt;
&lt;td&gt;🔴 Actively lying — reported working memory as broken&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TUI rendering&lt;/td&gt;
&lt;td&gt;🔴 Entire layout duplicated on screen after every message&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Desktop UI logs&lt;/td&gt;
&lt;td&gt;🔴 WebSocket handler silently crashed on every log event&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The most embarrassing part: I'd built a sophisticated multi-provider LLM router (&lt;code&gt;router.py&lt;/code&gt;) that could dispatch to Claude, OpenAI, Gemini, or Ollama based on config — and then every single callsite in &lt;code&gt;finalizer.py&lt;/code&gt;, &lt;code&gt;summarizer.py&lt;/code&gt;, and the streaming path had just... hardcoded &lt;code&gt;from bantz.llm.ollama import ollama&lt;/code&gt; directly. The router was completely bypassed. Anyone who configured &lt;code&gt;BANTZ_LLM_PROVIDER=claude&lt;/code&gt; would get Ollama responses with no error, no warning, nothing.&lt;/p&gt;

&lt;p&gt;The first-run experience was particularly painful. &lt;code&gt;bantz --once "merhaba"&lt;/code&gt; would hang in complete silence for up to 30 seconds as MarianMT loaded, Ollama inferred, and Piper synthesized — all sequentially, all silently. New users killed the process and never came back.&lt;/p&gt;

&lt;h3&gt;
  
  
  After — June 2026
&lt;/h3&gt;

&lt;p&gt;Seven issues closed in a single focused session, all squash-merged to &lt;code&gt;main&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PR #467 — 1-line fix, total silence explained.&lt;/strong&gt; The &lt;code&gt;_WSLogHandler&lt;/code&gt; inside &lt;code&gt;WsBroadcastServer&lt;/code&gt; referenced &lt;code&gt;self._log_q&lt;/code&gt; but the actual queue attribute was &lt;code&gt;self._q&lt;/code&gt;. One character typo. Every log record since the WebSocket server was written had thrown an &lt;code&gt;AttributeError&lt;/code&gt; that got silently swallowed, so the Tauri desktop UI had received zero log output. Fixed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PRs #468 &amp;amp; #469 — The router that wasn't routed to.&lt;/strong&gt; Replaced all three &lt;code&gt;finalizer.py&lt;/code&gt; callsites and the &lt;code&gt;summarizer.py&lt;/code&gt; Gemini/Ollama fallback chain with &lt;code&gt;from bantz.llm.router import get_llm&lt;/code&gt;. Added &lt;code&gt;get_llm = get_provider&lt;/code&gt; as a convenience alias in &lt;code&gt;router.py&lt;/code&gt;. Claude, OpenAI, and Gemini users now actually get their configured provider.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PR #470 — Service dots that told the truth.&lt;/strong&gt; The TUI's health-status bar was initialised with the hardcoded key &lt;code&gt;"Ollama"&lt;/code&gt; and &lt;code&gt;_probe_services()&lt;/code&gt; only called &lt;code&gt;check_ollama()&lt;/code&gt;. Added dynamic service key resolution from config, new &lt;code&gt;check_claude()&lt;/code&gt; and &lt;code&gt;check_openai()&lt;/code&gt; coroutines, and a dispatch table to route to the right health check based on the active provider.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PR #471 — The TUI duplication bug.&lt;/strong&gt; &lt;code&gt;_erase_prompt_line()&lt;/code&gt; used &lt;code&gt;os.write(1, ...)&lt;/code&gt; to send raw ANSI cursor-movement escapes directly to stdout while Rich Live was simultaneously rendering to the same terminal. This caused a race condition that reproduced the entire TUI block below the real one on every message. The fix: added a &lt;code&gt;Layout(name="prompt", size=1)&lt;/code&gt; panel to the layout tree and replaced the raw write with a state variable (&lt;code&gt;self._prompt_text = ""&lt;/code&gt;). The next Live refresh cycle clears the row cleanly, no escapes needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PR #472 — Cutting Turkish response latency from 18s to under 10s.&lt;/strong&gt; Two independent problems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;bridge.to_turkish()&lt;/code&gt; ran on the &lt;strong&gt;full accumulated response&lt;/strong&gt; after all LLM inference finished — sequential, never overlapping.&lt;/li&gt;
&lt;li&gt;No caching. Identical butler stock phrases like &lt;code&gt;"Done. ✓"&lt;/code&gt; re-ran the full neural translation model every single time.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Added a 256-entry FIFO LRU cache to &lt;code&gt;_Translator&lt;/code&gt; — common phrases now translate in ~0ms after the first call. Then rewrote &lt;code&gt;finalize_stream()._stream()&lt;/code&gt; to buffer LLM tokens until sentence boundaries (&lt;code&gt;(?&amp;lt;=[.!?])\s+&lt;/code&gt;) and call &lt;code&gt;bridge.to_turkish()&lt;/code&gt; per sentence immediately, yielding translated output while the LLM continues generating the next sentence. Translation now overlaps inference instead of running after it. Also removed the redundant &lt;code&gt;await _to_tr("".join(parts))&lt;/code&gt; re-translation in &lt;code&gt;ws_server.py&lt;/code&gt;'s streaming path — &lt;code&gt;finalize_stream&lt;/code&gt; already emits pre-translated tokens when the bridge is enabled.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Issue #463 — Already fixed (no PR needed).&lt;/strong&gt; Copilot confirmed &lt;code&gt;Live(screen=False)&lt;/code&gt; was already set and &lt;code&gt;REFRESH_FPS = 4&lt;/code&gt; was already present. Closed with an explanatory comment. Sometimes the right fix is recognising there's nothing to fix.&lt;/p&gt;




&lt;h2&gt;
  
  
  My Experience with GitHub Copilot
&lt;/h2&gt;

&lt;p&gt;I used Copilot in agent mode (Claude Sonnet 4.6) for the entire session. What struck me most was the discipline it brought to a codebase I'd let get messy.&lt;/p&gt;

&lt;p&gt;For every issue, Copilot followed the same workflow without being told to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Read the affected file before touching anything.&lt;/strong&gt; Not a summary — the actual file, end-to-end.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search for the exact symbol causing the problem.&lt;/strong&gt; For issue #462, it searched &lt;code&gt;_q|_log_q&lt;/code&gt; in &lt;code&gt;ws_server.py&lt;/code&gt; and immediately surfaced the mismatch. For #465, it searched for every &lt;code&gt;"Ollama"&lt;/code&gt; string literal and found three hardcoded sites at once.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make the minimal change.&lt;/strong&gt; No unrelated refactors. The &lt;code&gt;_log_q&lt;/code&gt; → &lt;code&gt;_q&lt;/code&gt; fix is literally one word on one line. The router migration replaced 3 identical patterns with identical 2-line substitutions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify syntax before committing.&lt;/strong&gt; &lt;code&gt;python -m py_compile &amp;lt;file&amp;gt;&lt;/code&gt; on every changed file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write the commit message and PR body, then create and merge the PR via &lt;code&gt;gh&lt;/code&gt;.&lt;/strong&gt; Including verifying the issue closed with &lt;code&gt;gh issue view NNN --json state&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The most valuable moment was on issue #422 (translation latency). I knew the translation was slow but I'd assumed it was just a hardware limitation — MarianMT on CPU takes what it takes. Copilot traced the full data flow from &lt;code&gt;finalize_stream()&lt;/code&gt; through &lt;code&gt;ws_server.py&lt;/code&gt; and identified that the bottleneck wasn't just the model — it was the architecture: sequential execution after completion, plus identical inputs being re-translated on every call. The sentence-boundary streaming approach it introduced had never occurred to me, and it worked on the first try.&lt;/p&gt;

&lt;p&gt;The other moment I appreciated: issue #463. Rather than making a change to justify its existence, Copilot searched for &lt;code&gt;screen=&lt;/code&gt; in &lt;code&gt;live_ui.py&lt;/code&gt;, confirmed the value was already &lt;code&gt;False&lt;/code&gt;, checked &lt;code&gt;REFRESH_FPS&lt;/code&gt;, and told me the issue was already resolved. Closing a bug report with "this is already fixed" is the correct outcome. That kind of restraint is hard to get from a tool optimised to produce output.&lt;/p&gt;

&lt;p&gt;Bantz isn't finished, voice input still needs its three packages, the &lt;code&gt;--doctor&lt;/code&gt; output still needs polish, and I want to add a proper onboarding flow. But the core pipeline now works correctly for all supported LLM providers, the TUI renders cleanly, Turkish responses arrive in under 10 seconds, and the butler's logs finally reach the desktop UI. That's a project that went from "broken in embarrassing ways" to "actually ships" — and Copilot was the pairing partner that made it happen in a single afternoon.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>githubchallenge</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
