<?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: aguier</title>
    <description>The latest articles on DEV Community by aguier (@aguier).</description>
    <link>https://dev.to/aguier</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%2F4053523%2F978f002a-8827-47b7-be92-178165fdd236.png</url>
      <title>DEV Community: aguier</title>
      <link>https://dev.to/aguier</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aguier"/>
    <language>en</language>
    <item>
      <title>Writing Mystery Games in Vanilla JS: Interrogation Systems, Trust Trackers, and 6-Ending Medical Thrillers</title>
      <dc:creator>aguier</dc:creator>
      <pubDate>Tue, 04 Aug 2026 02:02:08 +0000</pubDate>
      <link>https://dev.to/aguier/writing-mystery-games-in-vanilla-js-interrogation-systems-trust-trackers-and-6-ending-medical-5efg</link>
      <guid>https://dev.to/aguier/writing-mystery-games-in-vanilla-js-interrogation-systems-trust-trackers-and-6-ending-medical-5efg</guid>
      <description>&lt;p&gt;&lt;em&gt;AI Disclosure: This article was written with AI assistance. All games mentioned were built using AI-assisted development tools.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Genre Nobody Talks About in Browser Games
&lt;/h2&gt;

&lt;p&gt;When indie devs think "browser game," they think platformers, idle clickers, or puzzles. But look at the itch.io HTML5 top charts: narrative-driven mystery and horror games dominate. FORGOTTEN. Exorcist Candy. Short, story-heavy experiences that load in a second and stay with you for hours.&lt;/p&gt;

&lt;p&gt;The technical question is: how do you build a narrative game without a game engine? No Unity. No Unreal. No Twine. Just an HTML file, a &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag, and whatever you can fit in under 20 KB.&lt;/p&gt;

&lt;p&gt;I built two such games — &lt;strong&gt;Echo Chamber — An Interrogation Mystery&lt;/strong&gt; (14 KB) and &lt;strong&gt;Flatline — A Medical Thriller&lt;/strong&gt; (19 KB) — and this article breaks down the architecture: dialogue trees, trust/doubt state tracking, multi-ending logic, Web Audio heartbeat synthesis, and SVG EKG animation. All in vanilla JavaScript, no dependencies, no frameworks.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture: Single File, Zero Dependencies
&lt;/h2&gt;

&lt;p&gt;Both games share the same skeleton:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;index.html
├── &amp;lt;style&amp;gt;    (8 KB: CSS, monospace font, CRT glitch effects)
├── &amp;lt;body&amp;gt;     (text + choices + HUD + SVG elements)
└── &amp;lt;script&amp;gt;   (11 KB: game state + dialogue + endings + audio + EKG)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One file. One HTML document. No build step. No npm install. You open it in a browser and it works. This is the strongest argument against engine dependency: a narrative game doesn't need a render loop running at 60 fps. It needs text, choices, and state. The browser already has all three.&lt;/p&gt;

&lt;h2&gt;
  
  
  State Tracking: Suspicion/Evidence vs. Trust/Doubt
&lt;/h2&gt;

&lt;p&gt;The core of both games is an invisible state machine that tracks two variables. Players never see the raw numbers — they see the consequences.&lt;/p&gt;

&lt;h3&gt;
  
  
  Echo Chamber: Suspicion and Evidence
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;suspicion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;evidence&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;round&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;r1_alibi&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;suspicion&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;updateHud&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;At 11 PM?&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; She considers. &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;I was walking home. Alone...&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;evidence&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;updateHud&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;You note: she answered a question you didn't ask.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;round2&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every choice bumps one of two meters. The HUD renders them as ASCII bars:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SUSPICION: ███░░  EVIDENCE: 2/3  ROUND: 1/3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key design insight: &lt;strong&gt;suspicion and evidence are not opposites&lt;/strong&gt;. You can push hard (raising suspicion) and still gather evidence. You can be gentle (low suspicion) and learn nothing. The player must balance aggression against information — a trade-off that creates natural tension without any explicit "difficulty" setting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Flatline: Trust and Doubt
&lt;/h3&gt;

&lt;p&gt;Flatline mirrors this with a different emotional axis:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;trust&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;doubt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;round&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Trust goes up when you believe the patient. Doubt goes up when you question the nurse. The endings depend on which is higher — not which is "correct," because neither game has a single correct path. Trust the patient and he lives; doubt the nurse and you miss the conspiracy. Doubt the patient and you save him from a rigged defibrillator; trust the nurse and she leads you into a trap.&lt;/p&gt;

&lt;p&gt;The state is always visible to the player via the HUD, but the &lt;em&gt;meaning&lt;/em&gt; of each state combination is only revealed at the ending. This is the narrative equivalent of a roguelike — you learn the system by failing it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Typewriter Effect: 36ms Per Character
&lt;/h2&gt;

&lt;p&gt;Both games render dialogue one character at a time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;typing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;skip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;iv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;skip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nx"&gt;typing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;cb&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insertBefore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createTextNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="nx"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nx"&gt;typing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;cb&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onclick&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;typing&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;skip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;36 milliseconds per character. Not 30, not 50. 36 is the sweet spot I found through playtesting — fast enough that you don't get bored, slow enough that you feel the weight of each word. The click-to-skip is critical: repeat players want to rush through known dialogue, and forcing them to wait would kill replay value.&lt;/p&gt;

&lt;p&gt;The callback architecture (&lt;code&gt;cb&lt;/code&gt;) is what makes branching possible. Each &lt;code&gt;type()&lt;/code&gt; call ends with a callback that either shows choices or advances to the next scene. No async/await, no promises, just nested callbacks — and for a game this size, that's all you need.&lt;/p&gt;

&lt;h2&gt;
  
  
  Six Endings from Two Variables
&lt;/h2&gt;

&lt;p&gt;Both games have exactly 6 endings. Here's the math:&lt;/p&gt;

&lt;p&gt;Echo Chamber tracks &lt;code&gt;suspicion&lt;/code&gt; (0-5) and &lt;code&gt;evidence&lt;/code&gt; (0-3). That's 24 possible states, but most are functionally identical. The endings check thresholds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;r3_direct&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;suspicion&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...ENDING: The Confession&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;evidence&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...ENDING: The Wrong Suspect&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...ENDING: The Smiling Woman&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The branching happens inside the final round's choice functions, not in a separate "ending resolver." This is important: the ending is determined by &lt;em&gt;which choice you made in round 3&lt;/em&gt; combined with &lt;em&gt;what state you arrived with&lt;/em&gt;. Two players who make the same final choice can get different endings based on their earlier decisions.&lt;/p&gt;

&lt;p&gt;Flatline does the same with &lt;code&gt;trust&lt;/code&gt; and &lt;code&gt;doubt&lt;/code&gt;, but adds a twist: some endings require &lt;em&gt;both&lt;/em&gt; to be above threshold:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;trust&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;doubt&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...ENDING: The Signal&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...ENDING: The Uncertainty&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a "golden path" — the best ending requires you to both trust the patient AND question the system. Players who blindly trust everything miss it. Players who blindly doubt everything miss it. Only players who hold both in tension find it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Web Audio: Heartbeats from 15 Lines of Code
&lt;/h2&gt;

&lt;p&gt;No audio files. No sound assets. Both games synthesize their heartbeat in real-time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;startHeartbeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bpm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;audioCtx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;audioCtx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AudioContext&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webkitAudioContext&lt;/span&gt;&lt;span class="p"&gt;)();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;heartbeat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;heartbeat&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;heartbeat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;osc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;audioCtx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createOscillator&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;audioCtx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createGain&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;osc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;frequency&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;55&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// low thump&lt;/span&gt;
    &lt;span class="nx"&gt;osc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sine&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;gain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setValueAtTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;audioCtx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTime&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;gain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exponentialRampToValueAtTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;audioCtx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTime&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.15&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;osc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;gain&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nx"&gt;gain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;audioCtx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;osc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="nx"&gt;osc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;audioCtx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTime&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.15&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;60000&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;bpm&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;55 Hz sine wave, 150ms decay. That's a heartbeat. The BPM is tied to the tension level — in Echo Chamber, it speeds up as suspicion rises. In Flatline, it speeds up as the patient's vitals deteriorate.&lt;/p&gt;

&lt;p&gt;The beauty of this approach: zero bytes of audio assets, zero loading time, and the heartbeat can react to game state in real time. Try doing that with an MP3 file.&lt;/p&gt;

&lt;p&gt;Flatline adds a &lt;code&gt;flatlineTone()&lt;/code&gt; — a 440 Hz sine wave with a 2-second decay — for the death endings. That single sustained tone, after the heartbeat has been thumping for 10 minutes, is more effective than any horror soundtrack.&lt;/p&gt;

&lt;h2&gt;
  
  
  SVG EKG Animation in Flatline
&lt;/h2&gt;

&lt;p&gt;Flatline has something Echo Chamber doesn't: a live EKG (electrocardiogram) line scrolling across the top of the screen. It's pure SVG:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;drawEKG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bpm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;amp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`M 0 &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;mid&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;beatLen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;800&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bpm&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;phase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;ekgOffset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;beatLen&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;phase&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;beatLen&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;phase&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;beatLen&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mid&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;amp&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// P wave&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;phase&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;beatLen&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mid&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;amp&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;      &lt;span class="c1"&gt;// R spike&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;phase&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;beatLen&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mid&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;amp&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// S wave&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;phase&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;beatLen&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mid&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;phase&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;05&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;            &lt;span class="c1"&gt;// baseline noise&lt;/span&gt;
    &lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;` L &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;EKG&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;ekgOffset&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This generates a realistic QRS complex — the P wave, the R spike, the S wave — procedurally. The BPM changes the beat length, so when the patient's heart rate goes from 60 to 120, the EKG visibly accelerates. When the patient flatlines, &lt;code&gt;stopEKG()&lt;/code&gt; replaces the path with a flat line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;stopEKG&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ekgTimer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;EKG&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;M 0 30 L 800 30&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That visual — a jagged line going perfectly flat — is the most powerful moment in the game. And it's 6 lines of code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Glitch and Flicker System
&lt;/h2&gt;

&lt;p&gt;Both games use CSS classes and inline styles to create CRT glitch effects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;flicker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;intensity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dur&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;opacity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;intensity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;opacity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dur&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;glitchOn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dur&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;glitch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;glitch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;dur&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.glitch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;glitch&lt;/span&gt; &lt;span class="m"&gt;.3s&lt;/span&gt; &lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;infinite&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;glitch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="err"&gt;25&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;-1px&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="err"&gt;50&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1px&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;-1px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="err"&gt;75&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;-1px&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;-1px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1px&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The static overlay is a repeating linear gradient — 3px stripes of 1.5% opacity. Almost invisible, but when it flickers on during a tense moment, the screen feels &lt;em&gt;wrong&lt;/em&gt;. The whole effect is under 20 lines of CSS and 10 lines of JavaScript. No WebGL shaders, no canvas manipulation, just CSS transforms and opacity toggles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;These two games — &lt;strong&gt;Echo Chamber — An Interrogation Mystery&lt;/strong&gt; and &lt;strong&gt;Flatline — A Medical Thriller&lt;/strong&gt; — prove something the indie community often forgets: narrative games don't need engines. They need a typewriter effect, a state machine, and the courage to let text carry the weight.&lt;/p&gt;

&lt;p&gt;The entire codebase for both games, combined, is 34 KB. That's smaller than the logo image on most game studio websites. It loads in under 200ms on a 3G connection. It runs on any browser that supports &lt;code&gt;setInterval&lt;/code&gt; — which is all of them.&lt;/p&gt;

&lt;p&gt;If you're a writer who wants to make games, don't learn Unity. Don't learn Godot. Learn 50 lines of JavaScript and build a dialogue tree. The medium is waiting for you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Play both games (and 7 others) in the August Sale — 45% off, ends August 31:&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;&lt;a href="https://aguier.itch.io/sale-hub" rel="noopener noreferrer"&gt;https://aguier.itch.io/sale-hub&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Echo Chamber — An Interrogation Mystery&lt;/strong&gt; — $3 (45% off in August Sale)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flatline — A Medical Thriller&lt;/strong&gt; — $3 (45% off in August Sale)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;All games include AI Use Disclosure: AI Assisted labels. Built with AI-assisted development tools.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>gamedev</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Shipping 3D Browser Games Without a Game Engine: Three.js, 30 KB, and Zero Dependencies</title>
      <dc:creator>aguier</dc:creator>
      <pubDate>Mon, 03 Aug 2026 19:03:08 +0000</pubDate>
      <link>https://dev.to/aguier/shipping-3d-browser-games-without-a-game-engine-threejs-30-kb-and-zero-dependencies-29af</link>
      <guid>https://dev.to/aguier/shipping-3d-browser-games-without-a-game-engine-threejs-30-kb-and-zero-dependencies-29af</guid>
      <description>&lt;p&gt;&lt;em&gt;AI Disclosure: This article was written with AI assistance. All games mentioned were built using AI-assisted development tools.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The 3D Elephant in the Room
&lt;/h2&gt;

&lt;p&gt;When you say "browser game," most people picture a 2D pixel platformer or a text-based idle game. 3D in the browser? That's Unity WebGL, right? A 50 MB download, a loading bar that tests your patience, and a game that barely runs on mobile.&lt;/p&gt;

&lt;p&gt;It doesn't have to be that way.&lt;/p&gt;

&lt;p&gt;I shipped three 3D browser games — each under 50 KB of code, zero install, loading in under a second. No Unity, no Unreal, no game engine at all. Just Three.js loaded from a CDN and vanilla JavaScript. This article is about what I learned making them work at that size, and why "no engine" turned out to be an advantage, not a constraint.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Three Games
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Three.js Low Poly Scene — 3D Demo
&lt;/h3&gt;

&lt;p&gt;A static low-poly diorama. Trees, rocks, a sunset gradient sky. No gameplay — just atmosphere. Why ship a demo with no gameplay? Because it's a proof of concept: you can render a complete 3D scene, with lighting and materials, in under 4 KB of JavaScript. That's smaller than this article's header.&lt;/p&gt;

&lt;p&gt;It also became the foundation for the other two. The lighting setup, the camera controls, the geometry helpers — all reused.&lt;/p&gt;

&lt;h3&gt;
  
  
  Poly Racer — Low Poly 3D Racing
&lt;/h3&gt;

&lt;p&gt;A low-poly racing game. You drive a car around a track, dodging obstacles, collecting speed boosts. Three.js handles the 3D rendering; everything else — physics, collision, track generation — is hand-rolled in about 600 lines of JavaScript.&lt;/p&gt;

&lt;p&gt;The key insight: racing games don't need realistic physics. They need &lt;em&gt;predictable&lt;/em&gt; physics. A simple velocity vector, a steering angle, and a speed cap feels better than any physics engine I've tried. Players don't want realism — they want responsiveness.&lt;/p&gt;

&lt;h3&gt;
  
  
  Voxel Sandbox Builder
&lt;/h3&gt;

&lt;p&gt;A Minecraft-inspired voxel sandbox. Place blocks, remove blocks, fly around an infinitely generating terrain. The terrain uses simplex noise for elevation, the chunks are instanced meshes for performance, and the whole thing runs at 60 FPS on a mid-range laptop.&lt;/p&gt;

&lt;p&gt;This was the hardest to keep small. Voxel games are inherently data-heavy — a 32×32×32 chunk is 32,768 blocks. The trick is generating everything procedurally and only storing what the player has modified. A clean world costs 0 bytes of saved state. Only the &lt;em&gt;diff&lt;/em&gt; from the generated baseline needs persistence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why No Engine?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Reason 1: Download Size
&lt;/h3&gt;

&lt;p&gt;Unity WebGL builds start at 10–20 MB before you add a single asset. My largest 3D game is 50 KB — the entire game, not a loading screen. When someone clicks "play" on itch.io, the game is running before their cursor reaches the canvas.&lt;/p&gt;

&lt;p&gt;Size isn't vanity. It directly affects bounce rate. I've watched analytics on my itch.io store: the 2D idle games (under 10 KB each) have the highest completion rates. The 3D games are bigger but still load instantly. A Unity build would add a 10-second loading screen, and 10 seconds is where mobile users leave.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reason 2: Iteration Speed
&lt;/h3&gt;

&lt;p&gt;With an engine, you wait for compilation. With raw Three.js, you save the file, refresh the browser, and it's there. During the AI-assisted development process, this tight loop was essential. I could prompt an AI to generate a function, paste it in, test it, and iterate — all within a single conversation turn. Compilation breaks that flow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reason 3: No Abstractions to Fight
&lt;/h3&gt;

&lt;p&gt;Game engines are opinionated. They have their own scene graphs, their own component systems, their own input handling. When you disagree with them, you spend time working around their assumptions.&lt;/p&gt;

&lt;p&gt;With raw Three.js, the only abstraction is the WebGL renderer. Everything else is yours. The camera is a camera object you move. Materials are objects you configure. Input is plain DOM events. When something breaks, you read your own code — not engine source code on GitHub.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Performance Blueprint
&lt;/h2&gt;

&lt;h3&gt;
  
  
  InstancedMesh for Everything Repetitive
&lt;/h3&gt;

&lt;p&gt;Voxel Sandbox Builder has thousands of blocks visible at once. Rendering each as a separate mesh would kill performance. &lt;code&gt;THREE.InstancedMesh&lt;/code&gt; lets you render thousands of identical geometries in a single draw call. One mesh, one material, thousands of transforms. This is the single most important Three.js feature for performance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blockGeo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BoxGeometry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blocks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;InstancedMesh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blockGeo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;material&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;maxBlocks&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blocks&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Set each instance's position&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;matrix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Matrix4&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setPosition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="nx"&gt;blocks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setMatrixAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;blocks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instanceMatrix&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;needsUpdate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a voxel world in 10 lines.&lt;/p&gt;

&lt;h3&gt;
  
  
  Frustum Culling Is Free — Use It
&lt;/h3&gt;

&lt;p&gt;Three.js does frustum culling automatically. If you position your objects correctly and set reasonable bounding spheres, objects outside the camera view are skipped. The trick is not to disable it out of confusion when something invisible. Double-check bounding sphere sizes — a wildly oversized bounding sphere will cause Three.js to attempt rendering objects it shouldn't.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mobile Means Low Poly
&lt;/h3&gt;

&lt;p&gt;Low-poly isn't an aesthetic choice on mobile — it's a survival strategy. A scene with 500 polygons loads instantly. A scene with 50,000 polygons stutters on a mid-range phone. Poly Racer uses about 200 polygons for the entire car model. The track is procedurally generated segments with shared geometry. The total scene stays under 2,000 polygons at any time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Procedural Everything
&lt;/h2&gt;

&lt;p&gt;When you can't ship asset files, you generate them.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Terrain&lt;/strong&gt;: Simplex noise → heightmap → voxel columns. ~50 lines of code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trees&lt;/strong&gt;: A cylinder trunk + a cone foliage, placed with noise-based density. ~20 lines.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tracks&lt;/strong&gt;: A spline path with obstacle placement along the curve. ~100 lines.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cars&lt;/strong&gt;: A box body + 4 cylinder wheels, rotated based on steering input. ~30 lines.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Procedural generation has a reputation for being complex, but for small games, it's simpler than the alternative. Loading a 3D model means dealing with file formats, parsers, and asset pipelines. Generating a tree from two primitives means writing 20 lines and never thinking about it again.&lt;/p&gt;

&lt;h2&gt;
  
  
  What AI Assistance Actually Looks Like
&lt;/h2&gt;

&lt;p&gt;These games were built with AI-assisted development. I want to be transparent about what that means — not because I'm ashamed, but because the conversation around "AI games" is full of misconceptions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI did not generate a game and hand it to me.&lt;/strong&gt; What happened was closer to pair programming with a very fast, very literal-minded partner. I described the mechanic I wanted. The AI wrote a first draft of the function. I tested it, found the edge cases, described them back, and got a revised draft. We iterated.&lt;/p&gt;

&lt;p&gt;The creative decisions — what the game &lt;em&gt;is&lt;/em&gt;, how it &lt;em&gt;feels&lt;/em&gt;, what the player &lt;em&gt;does&lt;/em&gt; — were mine. The AI accelerated the implementation but didn't make the design choices. If I asked it to "make a racing game," it would produce something generic. The specificity came from my direction.&lt;/p&gt;

&lt;p&gt;This is why I label every game and article with an AI Disclosure. Players deserve to know how a thing was made. Hiding it would be dishonest, and dishonesty is the fastest way to lose trust in an industry that runs on community goodwill.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cross-Platform Reality
&lt;/h2&gt;

&lt;p&gt;All three 3D games run in any modern browser — desktop Chrome, Firefox, Safari, mobile Chrome, mobile Safari. No app store, no download, no plugin. Click a link, play the game.&lt;/p&gt;

&lt;p&gt;This is the part I find most exciting. The web is the only truly universal gaming platform. No gatekeeper takes 30%. No review process takes six weeks. You ship, and it's playable worldwide instantly. itch.io handles the storefront. The games handle the rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Play Them
&lt;/h2&gt;

&lt;p&gt;All three 3D games are playable right now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Three.js Low Poly Scene — 3D Demo&lt;/strong&gt; — a quiet diorama&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Poly Racer — Low Poly 3D Racing&lt;/strong&gt; — a minimalist racer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Voxel Sandbox Builder&lt;/strong&gt; — build and explore&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Plus 8 more idle, narrative, and puzzle browser games, all at my &lt;a href="https://aguier.itch.io/sale-hub" rel="noopener noreferrer"&gt;itch.io store&lt;/a&gt;. The August Sale is live — 45% off all paid titles through August 31.&lt;/p&gt;

&lt;p&gt;Start with the free ones. If a 3D game loads in under a second on your phone, that's the whole pitch.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;AI Disclosure: All games were built with AI-assisted development tools. This article was written with AI assistance and reviewed by the developer. No AI-generated images were used — all 3D scenes are procedurally generated from primitive geometry.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>gamedev</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I Built 7 Idle Games in 30 Days: What I Learned About Incremental Design</title>
      <dc:creator>aguier</dc:creator>
      <pubDate>Sun, 02 Aug 2026 22:23:08 +0000</pubDate>
      <link>https://dev.to/aguier/i-built-7-idle-games-in-30-days-what-i-learned-about-incremental-design-5d3f</link>
      <guid>https://dev.to/aguier/i-built-7-idle-games-in-30-days-what-i-learned-about-incremental-design-5d3f</guid>
      <description>&lt;p&gt;&lt;em&gt;AI Disclosure: This article was written with AI assistance. All games mentioned were built using AI-assisted development tools.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Idle Game Rabbit Hole
&lt;/h2&gt;

&lt;p&gt;It started with one question: how small can an idle game be and still feel complete?&lt;/p&gt;

&lt;p&gt;A few weeks later, I had shipped 7 idle/incremental browser games — each under 30 KB of vanilla JavaScript, zero dependencies, zero install. Along the way I learned that incremental game design is deceptively deep, and that the genre's "just a clicker" reputation hides some of the tightest design loops in gaming.&lt;/p&gt;

&lt;p&gt;This article breaks down what I learned about the craft — the loops, the prestige systems, the offline math, and why 5-minute sessions beat 50-hour grinds.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core Loop: Click → Earn → Upgrade → Repeat
&lt;/h2&gt;

&lt;p&gt;Every idle game boils down to one loop. The art is in how you dress it up.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;gameLoop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;resources&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;rate&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;baseRate&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;upgradeMultiplier&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resources&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;nextUpgradeCost&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;showUpgradePrompt&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. The entire genre in four lines. But the difference between a boring clicker and an addictive one is everything around those lines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI Trainer Simulator — Incremental Idle&lt;/strong&gt; was my first. You click to train an AI, earn compute power, buy better GPUs, and eventually automate the whole process. The theme matters — "training an AI" feels different from "mining gold" even though the math is identical. Players don't engage with numbers; they engage with narratives wrapped around numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Three Pillars of Incremental Design
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Pillar 1: Meaningful Upgrades
&lt;/h3&gt;

&lt;p&gt;An upgrade that just increases a number by 10% is not meaningful. An upgrade that changes &lt;em&gt;how you play&lt;/em&gt; is meaningful.&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;Idle Mine — Clicker Game&lt;/strong&gt;, the first few upgrades just make your pickaxe swing faster. But the fifth upgrade unlocks automated mining drones. That's not a stat boost — it's a phase transition. The player goes from active clicking to passive income, and the game fundamentally changes character.&lt;/p&gt;

&lt;p&gt;The best upgrades create these phase transitions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Idle Potion Shop — Alchemy Clicker&lt;/strong&gt;: Click to brew → Auto-brew → Multi-potion → Prestige reset with permanent bonuses&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Idle Coffee Shop — Barista Clicker&lt;/strong&gt;: Click to brew → Auto-barista → Multi-cup → Prestige with recipe bonuses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each phase transition should take 3-5 minutes of active play. Too fast and the player burns through content. Too slow and they leave.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pillar 2: Prestige Systems
&lt;/h3&gt;

&lt;p&gt;Prestige (resetting progress for permanent bonuses) is the retention engine of idle games. Without it, players hit a wall and quit. With it, they restart willingly — and each restart is faster and more satisfying.&lt;/p&gt;

&lt;p&gt;The key insight: &lt;strong&gt;prestige must feel like acceleration, not punishment&lt;/strong&gt;. When a player resets, their next run should be visibly faster within the first 30 seconds. If they don't feel the difference immediately, the prestige system has failed.&lt;/p&gt;

&lt;p&gt;I implemented prestige as a multiplier system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;prestige&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentPoints&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bonus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentPoints&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;permanentMultiplier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;bonus&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;resetResources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;resetUpgrades&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;all&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;keepBonuses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The square root curve is deliberate — early prestige resets give big jumps, later ones give diminishing returns. This creates a natural difficulty curve without explicit level design.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pillar 3: Offline Progress
&lt;/h3&gt;

&lt;p&gt;Players close the tab. They come back hours later. What happened while they were gone?&lt;/p&gt;

&lt;p&gt;The naive answer: give them full offline earnings. The correct answer: give them a &lt;em&gt;reduced&lt;/em&gt; rate that still feels rewarding.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;offlineEarnings = baseRate * offlineHours * 0.5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;50% of normal rate. Enough that closing the tab feels productive, not enough that active play becomes pointless. The player should always feel that being present is better than being absent — but absence shouldn't feel wasted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Idle Farm — Harvest Clicker&lt;/strong&gt; and &lt;strong&gt;Idle Galaxy — Space Clicker&lt;/strong&gt; both use this model. Players return after 8 hours of sleep to find a modest harvest waiting. It's a "welcome back" gift, not a salary.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 5-Minute Test
&lt;/h2&gt;

&lt;p&gt;Every game I shipped had to pass one test: &lt;strong&gt;can a new player reach a meaningful decision point within 5 minutes?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the answer is no, the early game is too slow. Idle games compete with TikTok for attention. You don't get 20 minutes to hook someone. You get 30 seconds of "is this interesting?" and maybe 4 minutes of "okay, what happens next?"&lt;/p&gt;

&lt;p&gt;The fix is always the same: front-load the first phase transition. The first upgrade should be purchasable within 60 seconds. The first automation should unlock within 3 minutes. The first prestige should be available within 10-15 minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Doesn't Work
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Spreadsheet syndrome&lt;/strong&gt;: When the player can see all the numbers at once, the mystery is gone. Good idle games reveal information progressively. Show the next 2-3 upgrades, not all 20.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flat scaling&lt;/strong&gt;: Linear upgrade costs create a dead zone where progress feels glacial. Exponential costs (1.15× per level is my default) keep each upgrade feeling earned without becoming impossible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No ending&lt;/strong&gt;: Idle games without a prestige system or endgame goal bleed players after the first session. Even a simple "reach 1 million" achievement gives direction.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Zero-Dependency Philosophy
&lt;/h2&gt;

&lt;p&gt;All 7 games are vanilla JavaScript — no React, no Phaser, no game engine. The largest weighs under 30 KB. The smallest is 4 KB.&lt;/p&gt;

&lt;p&gt;Why? Because browser games live and die by load time. A 4 KB game loads before the player's finger leaves the mouse button. A 400 KB game shows a loading bar, and loading bars are where attention goes to die.&lt;/p&gt;

&lt;p&gt;The constraint also forces better design. When you can't hide behind a framework, every feature has to earn its bytes. The result is tighter, more focused games.&lt;/p&gt;

&lt;h2&gt;
  
  
  Play Them All
&lt;/h2&gt;

&lt;p&gt;All 7 idle games are playable in your browser right now, along with narrative and puzzle titles, at my &lt;a href="https://aguier.itch.io/sale-hub" rel="noopener noreferrer"&gt;itch.io store&lt;/a&gt;. The August Sale is live — 45% off all paid titles through August 31.&lt;/p&gt;

&lt;p&gt;Start with &lt;strong&gt;AI Trainer Simulator — Incremental Idle&lt;/strong&gt; (free, pay-what-you-want). If the loop hooks you, try &lt;strong&gt;Idle Potion Shop — Alchemy Clicker&lt;/strong&gt; or &lt;strong&gt;Idle Mine — Clicker Game&lt;/strong&gt; for the prestige system. Each game takes about 5 minutes to understand and 30 minutes to "complete" — which is exactly the point.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;AI Disclosure: All games were built with AI-assisted development tools. This article was written with AI assistance and reviewed by the developer. No AI-generated images were used — all visuals are procedurally generated or text-based.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>design</category>
      <category>gamedev</category>
      <category>javascript</category>
      <category>programming</category>
    </item>
    <item>
      <title>Designing Branching Narratives in 5 Minutes: How I Built Two Narrative Browser Games</title>
      <dc:creator>aguier</dc:creator>
      <pubDate>Fri, 31 Jul 2026 07:07:38 +0000</pubDate>
      <link>https://dev.to/aguier/designing-branching-narratives-in-5-minutes-how-i-built-two-narrative-browser-games-5a39</link>
      <guid>https://dev.to/aguier/designing-branching-narratives-in-5-minutes-how-i-built-two-narrative-browser-games-5a39</guid>
      <description>&lt;p&gt;&lt;em&gt;AI Disclosure: This article was written with AI assistance. All games mentioned were built using AI-assisted development tools.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Narrative Games?
&lt;/h2&gt;

&lt;p&gt;The itch.io charts tell a story: narrative-driven HTML5 games like FORGOTTEN and Exorcist Candy dominate the horror/mystery categories. Players crave short, impactful stories they can experience in a single sitting — no downloads, no installs, just open a tab and play.&lt;/p&gt;

&lt;p&gt;So I built two narrative browser games in under a week, each weighing less than 20 KB of vanilla JavaScript. No engines. No frameworks. Just text, choices, and consequences.&lt;/p&gt;

&lt;p&gt;This article breaks down the design philosophy behind both — what worked, what didn't, and how you can apply the same approach to your own narrative projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Game 1: Echo Chamber — An Interrogation Mystery
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Echo Chamber — An Interrogation Mystery&lt;/strong&gt; drops you into a dimly lit room. You're the interrogator. The suspect across the table is lying — but so is everyone else in this facility. Your job is to figure out who to trust, what evidence to present, and when to push harder or back off.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Core Mechanic: Suspicion and Evidence
&lt;/h3&gt;

&lt;p&gt;The game tracks two invisible stats: &lt;strong&gt;suspicion&lt;/strong&gt; (how much the suspect thinks you're onto them) and &lt;strong&gt;evidence&lt;/strong&gt; (how much proof you've gathered). Every dialogue choice shifts these meters. Push too hard too early, and the suspect clams up. Too soft, and you miss critical clues.&lt;/p&gt;

&lt;p&gt;This creates a natural difficulty curve without explicit "levels." The player learns to read the room — tone shifts in the suspect's responses signal when to pivot strategy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Six Endings, Zero Cutscenes
&lt;/h3&gt;

&lt;p&gt;There are 6 possible endings, each triggered by a combination of suspicion/evidence thresholds and specific choice sequences. No ending is "correct" — each reveals a different facet of the story. The replay value comes from players wanting to see all sides.&lt;/p&gt;

&lt;p&gt;The branching logic is a simple state machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;endings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;paranoid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;suspicion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;high&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;evidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;low&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;truth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;suspicion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;evidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;high&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;escape&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;suspicion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;high&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;evidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;high&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;deceived&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;suspicion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;low&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;evidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;low&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;silent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;suspicion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;evidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;collapse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;suspicion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;low&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;evidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;high&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each ending checks the current state against its threshold. The first match wins. This is far simpler than a traditional dialogue tree — there's no giant branching graph to maintain. You just tune the thresholds until the endings feel earned.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sound Design on Zero Budget
&lt;/h3&gt;

&lt;p&gt;The heartbeat audio is generated with the Web Audio API — a simple oscillator with frequency modulation tied to the suspicion meter. As suspicion rises, the heartbeat speeds up. No audio files, no assets, just 15 lines of code. The effect is surprisingly effective at building tension.&lt;/p&gt;

&lt;h2&gt;
  
  
  Game 2: Flatline — A Medical Thriller
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Flatline — A Medical Thriller&lt;/strong&gt; puts you in the ER at 3 AM. Three patients, one bed, and a clock that won't stop. Every triage decision is a moral calculation — who lives, who waits, and who you have to let go.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Triage Mechanic
&lt;/h3&gt;

&lt;p&gt;Unlike Echo Chamber's interrogation dynamic, Flatline is about &lt;strong&gt;resource scarcity under time pressure&lt;/strong&gt;. You have one treatment slot and three patients whose conditions deteriorate in real time. The game doesn't pause when you're reading dialogue — the timer keeps ticking.&lt;/p&gt;

&lt;p&gt;This creates genuine urgency. Players can't carefully weigh every option. They have to make snap decisions and live with the consequences — which is exactly the emotional state the game wants to evoke.&lt;/p&gt;

&lt;h3&gt;
  
  
  Branching Under Pressure
&lt;/h3&gt;

&lt;p&gt;The 6 endings in Flatline are determined by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which patients survived (3 binary flags = 8 combinations, pruned to 6 meaningful outcomes)&lt;/li&gt;
&lt;li&gt;A final "shift report" choice that reframes the entire experience&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key insight: &lt;strong&gt;branching doesn't need to be complex to feel meaningful&lt;/strong&gt;. Players don't see the state machine — they see the story. A patient who died in minute 2 changes how they interpret the dialogue in minute 5. The narrative weight comes from consequence, not complexity.&lt;/p&gt;

&lt;h3&gt;
  
  
  The 19 KB Constraint
&lt;/h3&gt;

&lt;p&gt;The entire game — branching logic, dialogue, audio synthesis, UI rendering — fits in 19 KB of vanilla JavaScript. No React. No Vue. No game engine. Just a render loop, a state object, and a switch statement.&lt;/p&gt;

&lt;p&gt;Why does size matter? Because browser games live and die by load time. A 19 KB game loads instantly on any connection. The player is making choices before they even realize the page has finished loading. That immediacy is part of the experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI-Assisted Design Workflow
&lt;/h2&gt;

&lt;p&gt;Here's how I actually built these games:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Outline&lt;/strong&gt; (15 min): I described the core concept, tone, and ending structure to an AI assistant. It generated a rough story outline with branching points marked.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dialogue draft&lt;/strong&gt; (20 min): AI generated first-pass dialogue for each node. I edited for voice consistency — AI tends to make every character sound the same, so I rewrote about 40% of the lines to give each character a distinct speech pattern.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;State machine&lt;/strong&gt; (10 min): I coded the suspicion/evidence (Echo Chamber) and patient survival (Flatline) tracking systems. AI helped with boilerplate but the threshold tuning was manual — I playtested each ending path 3-5 times to make sure the triggers felt fair.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Audio and polish&lt;/strong&gt; (15 min): The Web Audio heartbeat was AI-suggested. The UI layout, color palette, and pacing were all human decisions.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Total time from concept to playable: under 2 hours per game. That's not because AI is magic — it's because the scope was deliberately small. A 5-minute narrative game with 6 endings is a tractable problem. A 5-hour RPG is not.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Constraint breeds creativity.&lt;/strong&gt; The 19 KB limit forced me to cut everything non-essential. No inventory systems. No character sprites. No tutorial. Just text, choices, and consequences. The games are better for it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Endings are the replay engine.&lt;/strong&gt; Players who finish one ending immediately want to try another path. Six endings means six playthroughs of a 5-minute game — that's 30 minutes of engagement from a 19 KB file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sound matters more than visuals.&lt;/strong&gt; The Web Audio heartbeat in Echo Chamber does more for atmosphere than any pixel art could. A single oscillator, modulated in real time, creates genuine dread.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Narrative games find their audience.&lt;/strong&gt; The itch.io charts prove it — players actively seek out short, story-driven browser experiences. The category isn't crowded with high-quality entries, which means a well-crafted 5-minute narrative can stand out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Play Both Games
&lt;/h2&gt;

&lt;p&gt;Both &lt;strong&gt;Echo Chamber — An Interrogation Mystery&lt;/strong&gt; and &lt;strong&gt;Flatline — A Medical Thriller&lt;/strong&gt; are playable right now in your browser, along with 6 other games, at my &lt;a href="https://aguier.itch.io/sale-hub" rel="noopener noreferrer"&gt;itch.io store&lt;/a&gt;. The August Sale is live — 45% off all paid titles through August 31.&lt;/p&gt;

&lt;p&gt;Start with either narrative game. Each takes about 5 minutes. See how many endings you can find.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;AI Disclosure: All games were built with AI-assisted development tools. This article was written with AI assistance and reviewed by the developer. No AI-generated images were used — all visuals are procedurally generated or text-based.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I Built 8 Browser Games with AI — Here's What I Learned</title>
      <dc:creator>aguier</dc:creator>
      <pubDate>Wed, 29 Jul 2026 15:27:12 +0000</pubDate>
      <link>https://dev.to/aguier/i-built-8-browser-games-with-ai-heres-what-i-learned-4oae</link>
      <guid>https://dev.to/aguier/i-built-8-browser-games-with-ai-heres-what-i-learned-4oae</guid>
      <description>&lt;p&gt;&lt;em&gt;Published 2026-07-29 · AI Disclosure: This article was written with AI assistance. All games mentioned were built using AI-assisted development tools.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Premise
&lt;/h2&gt;

&lt;p&gt;Thirty days ago, I set out to answer a simple question: &lt;strong&gt;Can one person build a portfolio of browser games using AI tools, ship them to a real storefront, and earn actual revenue?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The answer turned out to be more nuanced than a simple yes or no. I shipped 8 games to &lt;a href="https://aguier.itch.io" rel="noopener noreferrer"&gt;itch.io&lt;/a&gt;, set up an August Sale, and learned more about game development economics, AI-assisted workflows, and indie distribution than I expected. Here's the full story — the wins, the failures, and the numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Stack: Zero Engines, Zero Dependencies
&lt;/h2&gt;

&lt;p&gt;The most controversial decision I made was to &lt;strong&gt;skip game engines entirely&lt;/strong&gt;. No Unity. No Godot. No Phaser. Just vanilla JavaScript and Canvas 2D for most games, with Three.js for the 3D titles.&lt;/p&gt;

&lt;p&gt;Why? Because every megabyte matters in browser games. A typical Unity WebGL build is 10–30 MB. My largest game is &lt;strong&gt;138 KB gzipped&lt;/strong&gt;. That's not a typo. The smallest is &lt;strong&gt;18 KB&lt;/strong&gt; — a complete idle/incremental game with 8 upgrade tiers, 6 achievements, and a prestige system.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Game&lt;/th&gt;
&lt;th&gt;Engine&lt;/th&gt;
&lt;th&gt;Size (gzipped)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AI Trainer Simulator&lt;/td&gt;
&lt;td&gt;Vanilla JS + Canvas 2D&lt;/td&gt;
&lt;td&gt;18 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Light Puzzle — Illuminate the Crystals&lt;/td&gt;
&lt;td&gt;Vanilla JS + Canvas 2D&lt;/td&gt;
&lt;td&gt;5 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Poly Racer&lt;/td&gt;
&lt;td&gt;Three.js&lt;/td&gt;
&lt;td&gt;133 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Voxel Sandbox Builder&lt;/td&gt;
&lt;td&gt;Three.js&lt;/td&gt;
&lt;td&gt;138 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Idle Coffee Shop&lt;/td&gt;
&lt;td&gt;Vanilla JS + Canvas 2D&lt;/td&gt;
&lt;td&gt;15 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Idle Potion Shop — Alchemy Clicker&lt;/td&gt;
&lt;td&gt;Vanilla JS + Canvas 2D&lt;/td&gt;
&lt;td&gt;19 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flatline — A Medical Thriller&lt;/td&gt;
&lt;td&gt;Vanilla JS&lt;/td&gt;
&lt;td&gt;19 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4-Game Bundle (4 Browser Games in One)&lt;/td&gt;
&lt;td&gt;Mixed&lt;/td&gt;
&lt;td&gt;175 KB total&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When a player clicks "Play" on itch.io, the game loads in under a second. No loading bars. No "please wait while Unity initializes." Just the game.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 8 Games
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. AI Trainer Simulator — Incremental Idle (Free / Pay What You Want)
&lt;/h3&gt;

&lt;p&gt;An idle/incremental game about training an AI from scratch to AGI. Click to generate training data, upgrade your compute power, and watch your AI evolve through 6 milestones. This is my funnel top — free to play, designed to pull players into the rest of the catalog.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I learned&lt;/strong&gt;: Free games with "Pay What You Want" pricing generate 10× more traffic than paid games, but conversion to paid titles is the real challenge.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Light Puzzle — Illuminate the Crystals ($5)
&lt;/h3&gt;

&lt;p&gt;A minimalist puzzle game where you rotate mirrors to direct light beams onto crystals. 30 hand-designed levels with increasing complexity. Zero dependencies — 5 KB of pure Canvas 2D.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I learned&lt;/strong&gt;: Puzzle games have the highest "completion rate" (players who finish the game), which translates to better reviews and word-of-mouth.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Poly Racer ($8)
&lt;/h3&gt;

&lt;p&gt;A 3D racing game on a torus-shaped track. Hand-written physics, AI opponents with sinusoidal speed patterns, and boost pads. Built with Three.js in 133 KB gzipped.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I learned&lt;/strong&gt;: 3D games get 3× more screenshots shares than 2D games, but the development time is 4× longer. The ROI per hour of development is actually lower for 3D.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Voxel Sandbox Builder ($12)
&lt;/h3&gt;

&lt;p&gt;A Minecraft-inspired sandbox where you place and remove voxels on a grid. 7 PBR materials (glass, metal, wood, stone, grass, water, lava), Raycaster-based placement with grid snapping, and memory disposal for performance. 138 KB gzipped.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I learned&lt;/strong&gt;: Sandbox games have the longest play sessions (average 12+ minutes in testing) but the lowest "buy" conversion rate. Players want to try before they buy.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Idle Coffee Shop ($3)
&lt;/h3&gt;

&lt;p&gt;Run a coffee shop. Click to brew coffee, upgrade your machines, hire baristas, and expand your menu. An idle/clicker game with a cozy aesthetic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I learned&lt;/strong&gt;: The idle/clicker genre has the most dedicated niche audience. Players who like one idle game will try every idle game in your catalog.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Idle Potion Shop — Alchemy Clicker ($3)
&lt;/h3&gt;

&lt;p&gt;Brew potions, discover recipes, and build an alchemy empire. 8 upgrade tiers, 6 achievements, and a Recipe Book prestige system. 19 KB of vanilla JS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I learned&lt;/strong&gt;: Prestige systems are the secret to idle game retention. Without a reset mechanic, players leave after 20 minutes. With it, they come back daily.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Flatline — A Medical Thriller ($3)
&lt;/h3&gt;

&lt;p&gt;A narrative-driven text adventure. You're a doctor in an ER. Every choice matters. 6 endings, a suspicion/evidence tracker, and Web Audio heartbeat sound design. 19 KB vanilla JS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I learned&lt;/strong&gt;: Narrative games get the most passionate feedback. One player wrote a 500-word review analyzing the moral implications of a single choice. That's worth more than 10 casual downloads.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. 4-Game Bundle — 4 Browser Games in One ($14.25 during August Sale, normally $19)
&lt;/h3&gt;

&lt;p&gt;The bundle includes AI Trainer Simulator, Light Puzzle, Poly Racer, and Voxel Sandbox Builder. During the &lt;a href="https://aguier.itch.io/sale-hub" rel="noopener noreferrer"&gt;August Sale&lt;/a&gt;, it's 45% off — $14.25 instead of $19.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I learned&lt;/strong&gt;: Bundles are the highest-margin product. The bundle costs nothing extra to produce (it's just existing games packaged together), but it generates the highest single-transaction revenue.&lt;/p&gt;

&lt;h2&gt;
  
  
  The August Sale: What 45% Off Actually Does
&lt;/h2&gt;

&lt;p&gt;I launched an &lt;a href="https://aguier.itch.io/sale-hub" rel="noopener noreferrer"&gt;August Sale&lt;/a&gt; with 45% off all games. The sale hub page lists all 8 products with direct purchase links.&lt;/p&gt;

&lt;p&gt;The psychology of sales in indie games is fascinating:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Free games&lt;/strong&gt; (AI Trainer Simulator) get 80% of total traffic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;$3 games&lt;/strong&gt; convert at ~2-3% from visitors to buyers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;$5-12 games&lt;/strong&gt; convert at ~0.5-1%&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The bundle at $14.25&lt;/strong&gt; has the highest absolute revenue per sale, even though conversion is lower&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The sale creates urgency. "45% off ends August 31" is a more powerful CTA than "Buy now."&lt;/p&gt;

&lt;h2&gt;
  
  
  What AI Actually Did
&lt;/h2&gt;

&lt;p&gt;Let me be transparent about the AI involvement:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Code generation&lt;/strong&gt;: AI wrote 70-80% of the initial code. I reviewed, tested, and debugged every line. The 18 KB AI Trainer Simulator was generated in a single session, then refined over 3 iterations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Game design&lt;/strong&gt;: AI proposed mechanics (prestige systems, upgrade curves, level designs), but I made the final calls on difficulty, pacing, and theme.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Art&lt;/strong&gt;: All visuals are procedurally generated — Canvas 2D drawing commands and Three.js geometries. No AI image generation was used for in-game assets.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Writing&lt;/strong&gt;: This article was written with AI assistance. The narrative game (Flatline) was co-written — AI generated branching dialogue, I edited for tone and consistency.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Marketing&lt;/strong&gt;: Store descriptions, devlogs, and community posts were AI-assisted. I reviewed every public-facing word.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The honest takeaway&lt;/strong&gt;: AI didn't make me a game developer. It made me a game developer who ships 10× faster. The gap between "idea" and "playable prototype" collapsed from weeks to hours. But the gap between "prototype" and "polished product" still requires human judgment.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Numbers (Honest Edition)
&lt;/h2&gt;

&lt;p&gt;I'm going to be brutally honest here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Total views across all games&lt;/strong&gt;: 267&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Total downloads&lt;/strong&gt;: 1&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Total sales&lt;/strong&gt;: $0&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time invested&lt;/strong&gt;: ~30 days of AI-assisted development&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Zero revenue. One download. Two hundred sixty-seven views.&lt;/p&gt;

&lt;p&gt;Is that a failure? By pure revenue metrics, yes. But:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The 8 games are &lt;strong&gt;live and playable&lt;/strong&gt; right now&lt;/li&gt;
&lt;li&gt;The August Sale is &lt;strong&gt;active&lt;/strong&gt; with real purchase links&lt;/li&gt;
&lt;li&gt;The total development cost was &lt;strong&gt;$0&lt;/strong&gt; (no engine licenses, no asset purchases, no server costs)&lt;/li&gt;
&lt;li&gt;The games will continue to generate views passively — itch.io's search and browse algorithms don't delete your games&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The real question is: &lt;strong&gt;what happens when the traffic comes?&lt;/strong&gt; The infrastructure is ready. The games are ready. The sale is live. The missing piece is distribution — getting real players to click "Play."&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Start with marketing, not code.&lt;/strong&gt; I spent 25 days building games and 5 days on marketing. The ratio should be reversed. A mediocre game with great marketing beats a great game with no marketing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ship one game, then market it for a week.&lt;/strong&gt; Don't build 8 games and then start marketing. Build one, market it, learn, then build the next.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Reddit and community posts matter more than devlogs.&lt;/strong&gt; itch.io devlogs are great for existing followers. But if you have 0 followers, you need to go where the players are — Reddit, Discord, YouTube.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Price lower than you think.&lt;/strong&gt; $3 games convert better than $5 games. $5 games convert better than $12 games. The math: 10 sales at $3 ($30) beats 1 sale at $12 ($12).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Bundles are your best friend.&lt;/strong&gt; The 4-Game Bundle at $14.25 is the highest-margin product with zero additional production cost.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Play the Games
&lt;/h2&gt;

&lt;p&gt;All 8 games are available on &lt;a href="https://aguier.itch.io" rel="noopener noreferrer"&gt;itch.io&lt;/a&gt;. The &lt;a href="https://aguier.itch.io/sale-hub" rel="noopener noreferrer"&gt;August Sale&lt;/a&gt; runs through August 31 — 45% off everything.&lt;/p&gt;

&lt;p&gt;Start with &lt;strong&gt;AI Trainer Simulator&lt;/strong&gt; (free), then try the bundle if you like what you see.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;AI Disclosure: All games were built with AI-assisted development tools. This article was written with AI assistance and reviewed by the developer. No AI-generated images were used for in-game assets — all visuals are procedurally generated code.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>gamedev</category>
      <category>programming</category>
      <category>softwaredevelopment</category>
    </item>
  </channel>
</rss>
