<?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: Gidi Dafner</title>
    <description>The latest articles on DEV Community by Gidi Dafner (@gidi_dafner_8990eb3adead4).</description>
    <link>https://dev.to/gidi_dafner_8990eb3adead4</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3829635%2F9cedec7c-ddf5-46a3-ac89-498b590338ff.jpg</url>
      <title>DEV Community: Gidi Dafner</title>
      <link>https://dev.to/gidi_dafner_8990eb3adead4</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gidi_dafner_8990eb3adead4"/>
    <language>en</language>
    <item>
      <title>I Rewrote Pokemon Yellow Entirely in TypeScript — Here's How It Runs in Your Browser</title>
      <dc:creator>Gidi Dafner</dc:creator>
      <pubDate>Tue, 17 Mar 2026 14:30:00 +0000</pubDate>
      <link>https://dev.to/gidi_dafner_8990eb3adead4/i-rewrote-pokemon-yellow-entirely-in-typescript-heres-how-it-runs-in-your-browser-1747</link>
      <guid>https://dev.to/gidi_dafner_8990eb3adead4/i-rewrote-pokemon-yellow-entirely-in-typescript-heres-how-it-runs-in-your-browser-1747</guid>
      <description>&lt;p&gt;I spent months rewriting Pokemon Yellow from scratch in TypeScript. Not an emulator — a complete reimplementation, ported instruction-by-instruction from Z80 assembly into HTML5 Canvas.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://gididaf.github.io/retro-rom-player/" rel="noopener noreferrer"&gt;Play it here&lt;/a&gt;&lt;/strong&gt; (bring your own ROM) | &lt;strong&gt;&lt;a href="https://github.com/gididaf/retro-rom-player" rel="noopener noreferrer"&gt;Source code&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;Because I wanted to know if it was possible. The &lt;a href="https://github.com/pret/pokeyellow" rel="noopener noreferrer"&gt;pret/pokeyellow&lt;/a&gt; community has fully reverse-engineered Pokemon Yellow into readable assembly. I thought: what if I could take that assembly and turn it into something that runs natively in the browser?&lt;/p&gt;

&lt;p&gt;The entire engine is ~280KB of TypeScript. Zero game assets are shipped — everything is extracted from your own ROM file directly in the browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the ROM Extraction Works
&lt;/h2&gt;

&lt;p&gt;This was the most interesting technical challenge. When you upload your ROM, the game:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Validates it&lt;/strong&gt; via SHA1 hash&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runs 14 specialized extractors&lt;/strong&gt; that parse the raw binary — Pokemon stats, move data, map layouts, dialogue text, trainer parties, audio sequences&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decompresses Gen 1 sprites&lt;/strong&gt; using the original compression algorithm (RLE + bit-pair encoding + delta decoding)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decodes 1bpp/2bpp tile graphics&lt;/strong&gt; from Game Boy VRAM format into ImageData&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caches everything in IndexedDB&lt;/strong&gt; so return visits load instantly&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The ROM never leaves your machine. The production build contains only the engine — no copyrighted content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Porting Z80 Assembly to TypeScript
&lt;/h2&gt;

&lt;p&gt;The original game runs on a Sharp LR35902 (Game Boy CPU). Porting it means reading assembly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Audio1_CalculateFrequency:
    ld a, [wChannelOctave]
    inc a
    cp 7
    jr z, .done
    sra d
    rr e
    jr .loop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And turning it into this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;calculateFrequency&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;octave&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;freq&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;freq&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0xFF&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;e&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;freq&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0xFF&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;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;octave&lt;/span&gt; &lt;span class="o"&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;a&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;a&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0x80&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0xFF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// SRA (arithmetic shift)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;carry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&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;e&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;carry&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0xFF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// RR (rotate right through carry)&lt;/span&gt;
    &lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;newD&lt;/span&gt;&lt;span class="p"&gt;;&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="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;e&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 detail matters. &lt;code&gt;SRA&lt;/code&gt; is an arithmetic right shift (preserves the sign bit) — use &lt;code&gt;SRL&lt;/code&gt; (logical shift) by mistake and your music plays at wrong pitches. I made that mistake. Twice.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Audio Engine
&lt;/h2&gt;

&lt;p&gt;The Game Boy has 4 sound channels: two pulse waves, one programmable wave, and one noise channel. I built a &lt;code&gt;ScriptProcessorNode&lt;/code&gt;-based synthesizer that generates samples at 44.1kHz, emulating all four channels.&lt;/p&gt;

&lt;p&gt;The music engine ticks at 59.7Hz (matching the Game Boy's VBlank rate), interpreting command sequences extracted from the ROM — tempo changes, note events, duty cycle switches, vibrato, pitch slides.&lt;/p&gt;

&lt;p&gt;50+ music tracks and 37 sound effects are fully playable. The title screen music hits different when you know every byte was parsed from a 1998 ROM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gen 1 Battle System — Bugs and All
&lt;/h2&gt;

&lt;p&gt;The battle system faithfully reproduces all of Gen 1's notorious quirks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Focus Energy bug&lt;/strong&gt; — it divides your crit rate by 4 instead of multiplying. Yes, the original game has this backwards. We keep it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1/256 miss glitch&lt;/strong&gt; — moves with 100% accuracy can still miss 0.4% of the time due to an off-by-one error in the hit check&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Badge stat boosts stacking&lt;/strong&gt; — the Burn/Paralyze stat reduction re-applies badge boosts each time, causing stats to overflow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;334 battle tests verify every edge case matches the original game.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Playable
&lt;/h2&gt;

&lt;p&gt;This is a demo covering the first chunk of the game:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Title screen with animated Pikachu and full music&lt;/li&gt;
&lt;li&gt;Prof. Oak intro sequence&lt;/li&gt;
&lt;li&gt;Pallet Town -&amp;gt; Route 1 -&amp;gt; Viridian City (12 maps)&lt;/li&gt;
&lt;li&gt;Wild and trainer battles with complete Gen 1 mechanics&lt;/li&gt;
&lt;li&gt;Pikachu follower with happiness system&lt;/li&gt;
&lt;li&gt;Pokedex, party menu, items, shops, PC, save/load&lt;/li&gt;
&lt;li&gt;Touch controls for mobile/tablet&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Legal Angle
&lt;/h2&gt;

&lt;p&gt;The repo contains &lt;strong&gt;zero copyrighted assets&lt;/strong&gt;. No sprites, no music, no dialogue, no ROM. Users provide their own legally obtained ROM, and everything is extracted client-side. The same model that emulators and projects like OpenMW use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;TypeScript + HTML5 Canvas (no frameworks, no dependencies)&lt;/li&gt;
&lt;li&gt;Vite for builds&lt;/li&gt;
&lt;li&gt;Vitest for testing (375 tests)&lt;/li&gt;
&lt;li&gt;~35,000 lines of TypeScript&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://gididaf.github.io/retro-rom-player/" rel="noopener noreferrer"&gt;Live Demo&lt;/a&gt;&lt;/strong&gt; — upload your ROM and play in the browser. Works on desktop and mobile.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/gididaf/retro-rom-player" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/strong&gt; — MIT licensed. PRs welcome.&lt;/p&gt;

&lt;p&gt;Built with &lt;a href="https://claude.ai/claude-code" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt; as a coding partner.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>gamedev</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
