<?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>Forem: not a pro</title>
    <description>The latest articles on Forem by not a pro (@whiplash).</description>
    <link>https://forem.com/whiplash</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%2F3808137%2Fee0c1dac-30f3-477e-bed6-9d38201ab143.png</url>
      <title>Forem: not a pro</title>
      <link>https://forem.com/whiplash</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/whiplash"/>
    <language>en</language>
    <item>
      <title>The Crazy Way a Dev Avoided Copyright on Leaked Claude Code</title>
      <dc:creator>not a pro</dc:creator>
      <pubDate>Tue, 31 Mar 2026 16:46:51 +0000</pubDate>
      <link>https://forem.com/whiplash/the-crazy-way-a-dev-avoided-copyright-on-leaked-claude-code-5cp4</link>
      <guid>https://forem.com/whiplash/the-crazy-way-a-dev-avoided-copyright-on-leaked-claude-code-5cp4</guid>
      <description>&lt;p&gt;On March 31, Anthropic’s AI coding tool &lt;strong&gt;“Claude Code”&lt;/strong&gt; had its &lt;strong&gt;entire source code&lt;/strong&gt; suddenly leaked online.&lt;/p&gt;

&lt;p&gt;The cause? A &lt;strong&gt;.map (sourcemap) file&lt;/strong&gt; included in the npm package — the very file every developer knows all too well.&lt;/p&gt;

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

&lt;p&gt;When built with Bun, the sourcemap generated by default contained the &lt;strong&gt;complete original TypeScript source code&lt;/strong&gt; embedded inside it. This allowed the full source to be extracted via the sourcemap. But the real problem starts here.&lt;/p&gt;

&lt;h3&gt;
  
  
  Leak → Immediate Backup → DMCA Blitz
&lt;/h3&gt;

&lt;p&gt;The first person to report the leak was &lt;strong&gt;Fried_rice&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The publicly shared ZIP file (&lt;strong&gt;src.zip&lt;/strong&gt;) contained Claude Code’s full architecture, system prompts, tool implementations, unreleased feature flags (such as &lt;strong&gt;KAIROS&lt;/strong&gt;, &lt;strong&gt;BUDDY&lt;/strong&gt;, &lt;strong&gt;ULTRAPLAN&lt;/strong&gt;, etc.), and even &lt;strong&gt;Undercover Mode&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;realsigridjin&lt;/strong&gt; quickly created a backup on GitHub.&lt;/p&gt;

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

&lt;p&gt;However, Anthropic reacted immediately, firing off &lt;strong&gt;DMCA (copyright takedown) notices&lt;/strong&gt; one after another. Repositories hosting the original code were taken down in rapid succession.&lt;/p&gt;

&lt;p&gt;“Copyright infringement — of course they’d do that.” Up to this point, it was a standard story.&lt;/p&gt;

&lt;h3&gt;
  
  
  Then Came the “God-Tier Evasion”
&lt;/h3&gt;

&lt;p&gt;What &lt;strong&gt;realsigridjin&lt;/strong&gt; did next was on a completely different level.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;He completely rewrote the same repository in Python and republished it.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Original: TypeScript (Anthropic’s official code itself)&lt;/li&gt;
&lt;li&gt;New: Python (automatically converted and reimplemented using Codex / oh-my-codex)&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;The new repository (instructkr/claude-code or related clawd-code repos) has &lt;strong&gt;nearly identical functionality&lt;/strong&gt;, yet can claim “this is not copyright infringement.”&lt;/p&gt;

&lt;p&gt;What’s more, the rewrite was completed in just &lt;strong&gt;a few hours&lt;/strong&gt;. It’s said that he simply handed the task to an AI agent with the instruction: “Reimplement Claude Code in Python.”&lt;/p&gt;

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

&lt;p&gt;As &lt;strong&gt;Gergely Orosz&lt;/strong&gt; (&lt;a class="mentioned-user" href="https://dev.to/gergelyorosz"&gt;@gergelyorosz&lt;/a&gt;) accurately pointed out:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Copyright does not protect derived works. Rewriting TypeScript code in Python means copyright no longer applies.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;(Copyright does not protect “derived works.” If you rewrite TypeScript code in Python, copyright no longer applies.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Does This Bypass Copyright Infringement?
&lt;/h3&gt;

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

&lt;p&gt;The fundamental principle of copyright law is that it &lt;strong&gt;protects “expression,” but not “ideas, functions, or algorithms.”&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rewriting the exact same logic from scratch in a different language = “different expression”&lt;/li&gt;
&lt;li&gt;Even if an AI performed the automatic conversion, one can argue that “a human reviewed the specification and reimplemented it”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words, “translation versions” or “ports to another language” sit &lt;strong&gt;smack in the middle of a legal gray zone&lt;/strong&gt;, but in practice, &lt;strong&gt;DMCA takedowns become extremely difficult to enforce&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In fact, this Python version is still alive and its star count is skyrocketing.&lt;/p&gt;

&lt;p&gt;For Anthropic, this is the worst-case scenario:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Even if they DMCA the original, the “Python version” remains&lt;/li&gt;
&lt;li&gt;Taking it down would spark a huge debate: “Are you trying to use copyright to control even AI-generated derivative works?”&lt;/li&gt;
&lt;li&gt;It threatens the very value of the Claude/Code tools they are building&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What This Means: “The Collapse of Copyright in the AI Era”
&lt;/h3&gt;

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

&lt;p&gt;This incident made one thing crystal clear: &lt;strong&gt;closed-source code can no longer be protected&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Using AI agents, a 57MB TypeScript codebase can be converted to Python in just a few hours&lt;/li&gt;
&lt;li&gt;Simply changing the language allows one to claim “this is not copyright infringement”&lt;/li&gt;
&lt;li&gt;Developers around the world have already learned the flow: “leak → immediate rewrite → publish”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hiding source code is rapidly becoming meaningless.&lt;/p&gt;

&lt;p&gt;No matter how much Anthropic emphasizes “safety first,” we have entered an era where &lt;strong&gt;one npm publish mistake can expose the entire source&lt;/strong&gt;, and &lt;strong&gt;AI can instantly generate a “legal” clone&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  In Conclusion
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;@realsigridjin&lt;/strong&gt;’s move was truly “way too wild.”&lt;/p&gt;

&lt;p&gt;Legally on the edge, ethically highly aggressive, and technically brilliant.&lt;/p&gt;

&lt;p&gt;The perfect punchline? He reportedly did it because his girlfriend was worried that “Anthropic might sue us.”&lt;/p&gt;

&lt;p&gt;This is reality in 2026.&lt;/p&gt;

&lt;p&gt;The Claude Code leak wasn’t just an accident — it may have been &lt;strong&gt;the moment AI began smashing the old rules of copyright&lt;/strong&gt;.&lt;/p&gt;




&lt;ul&gt;
&lt;li&gt;X: &lt;a href="https://x.com/LostMyCode" rel="noopener noreferrer"&gt;@LostMyCode&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/LostMyCode" rel="noopener noreferrer"&gt;https://github.com/LostMyCode&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>claude</category>
      <category>ai</category>
      <category>opensource</category>
      <category>github</category>
    </item>
    <item>
      <title>Claude Code Turned a 2003 Windows Game into a Browser Game</title>
      <dc:creator>not a pro</dc:creator>
      <pubDate>Mon, 30 Mar 2026 14:16:30 +0000</pubDate>
      <link>https://forem.com/whiplash/claude-code-turned-a-2003-windows-game-into-a-browser-game-1ail</link>
      <guid>https://forem.com/whiplash/claude-code-turned-a-2003-windows-game-into-a-browser-game-1ail</guid>
      <description>&lt;p&gt;Have you ever played an online PC game?&lt;/p&gt;

&lt;p&gt;These days, cross-platform support is everywhere, and you can jump into games on pretty much any device without a second thought.&lt;br&gt;&lt;br&gt;
But just a few years ago, if you heard “PC game,” it almost always meant &lt;strong&gt;Windows only&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You’d wait through a painfully long download and installation, then finally launch the game — a ritual many of us remember all too well.&lt;/p&gt;

&lt;p&gt;This time, I took &lt;strong&gt;GunZ: The Duel&lt;/strong&gt;, a 2003 Windows-exclusive online TPS, and brought it fully into the browser using &lt;strong&gt;WebAssembly and WebGL&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;No download. No installation.  &lt;/p&gt;

&lt;p&gt;All you do is open the page in Google Chrome.&lt;/p&gt;

&lt;p&gt;Maybe it’s just me, but isn’t it kind of magical to play a game that once took hours of setup… by simply clicking a link?&lt;/p&gt;

&lt;p&gt;Also: I barely touched the original source code. &lt;strong&gt;About 99% of the new code was written by AI.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In this article I want to share both the technical journey of porting GunZ to the browser, and a real-world example of something that was previously considered impossible… until AI coding assistants changed the game.&lt;/p&gt;


&lt;h2&gt;
  
  
  GunZ: The Duel — Now a Full Web Game
&lt;/h2&gt;

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

&lt;p&gt;GunZ was developed by Korea’s MAIET Entertainment. Its signature gameplay — wall-running, aerial combos, switching between sword and gun mid-air — made it incredibly unique. I was obsessed with it back in the day.&lt;/p&gt;

&lt;p&gt;In 2007 the entire source code (C++ client + server) leaked, and the community has been maintaining it ever since.&lt;/p&gt;

&lt;p&gt;This game is over 20 years old… and &lt;strong&gt;no one had ever managed to make it run in a browser&lt;/strong&gt; — &lt;strong&gt;until now&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Whiplash GunZ: &lt;a href="https://gunz.sigr.io/" rel="noopener noreferrer"&gt;https://gunz.sigr.io/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Just open the link and play. No Windows required. It works on Linux, macOS, iPhone, Android — pretty much anything (though playing seriously on mobile is… challenging 😂).&lt;/p&gt;

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

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

&lt;p&gt;This isn’t a “kinda works” or “looks similar” demo.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;It’s the complete original game&lt;/strong&gt;, running exactly as it did in 2003.&lt;/p&gt;

&lt;p&gt;In this post I’ll cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How it was actually achieved&lt;/li&gt;
&lt;li&gt;Why I decided to do this now, after 20 years&lt;/li&gt;
&lt;li&gt;All the previous failed attempts&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Previous failures and the road to full browser port
&lt;/h2&gt;

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

&lt;p&gt;This isn’t my first try at browser GunZ.&lt;/p&gt;

&lt;p&gt;A few years ago I attempted to rebuild it from scratch in JavaScript + Three.js:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/LostMyCode/three-gunz" rel="noopener noreferrer"&gt;https://github.com/LostMyCode/three-gunz&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I got map rendering working, but re-implementing the entire game engine was overwhelming. The project died quietly (the repo is still there if you’re curious).&lt;/p&gt;
&lt;h3&gt;
  
  
  The WebAssembly Route
&lt;/h3&gt;

&lt;p&gt;GunZ is almost entirely written in C++.&lt;br&gt;&lt;br&gt;
We all know you can compile C++ to WebAssembly with Emscripten, so in theory it should be easy, right?&lt;/p&gt;

&lt;p&gt;Wrong.&lt;/p&gt;

&lt;p&gt;GunZ is &lt;strong&gt;extremely&lt;/strong&gt; Windows-dependent. It calls Windows-specific APIs for rendering, audio, input — everything. The rendering engine uses &lt;strong&gt;Direct3D 9&lt;/strong&gt; directly.&lt;/p&gt;

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

&lt;p&gt;Emscripten can’t magically turn Direct3D calls into something the browser understands.&lt;br&gt;&lt;br&gt;
So any serious port meant rewriting thousands of lines of rendering code to use WebGL instead.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;No way I was doing that by hand.&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;That’s why the project stayed in the “theoretically possible but I’ll die before finishing” category for years.&lt;/p&gt;
&lt;h3&gt;
  
  
  Then the AI era arrived
&lt;/h3&gt;

&lt;p&gt;I kept thinking: “The C++ source exists… if only I could just use it.”&lt;/p&gt;

&lt;p&gt;I couldn’t do the port myself. But what if AI wrote the hard parts for me?&lt;/p&gt;

&lt;p&gt;Once powerful coding agents became reliable, I finally decided to try.&lt;/p&gt;

&lt;p&gt;I used two tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Google Antigravity&lt;/strong&gt; (AI Pro plan — ¥2,900/month)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude Code&lt;/strong&gt; (Max 5x plan — $100)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I started with Google Antigravity.&lt;/p&gt;

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

&lt;p&gt;The AI handled:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Analyzing the massive C++ codebase&lt;/li&gt;
&lt;li&gt;Finding every Windows API dependency&lt;/li&gt;
&lt;li&gt;Abstracting Direct3D calls&lt;/li&gt;
&lt;li&gt;Fixing the build system for Emscripten&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Watching an AI understand and refactor a huge legacy codebase in hours instead of days was honestly mind-blowing. One evening I went out drinking with friends… came back and the work was &lt;em&gt;done&lt;/em&gt;. First time I ever felt that rush.&lt;/p&gt;

&lt;p&gt;The Google plan was amazing — until Google suddenly tightened the rate limits. A project this size would hit weekly quotas instantly.&lt;/p&gt;

&lt;p&gt;So I switched to &lt;strong&gt;Claude Code&lt;/strong&gt; (the $100 Max 5x plan).&lt;/p&gt;

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

&lt;p&gt;I was scared the $100 tier would still hit limits — but it didn’t. Quota barely moved. Bugs that blocked me for a week in Antigravity were fixed in hours.&lt;/p&gt;


&lt;h2&gt;
  
  
  How we removed the Windows dependency — real-time Direct3D → WebGL translation
&lt;/h2&gt;

&lt;p&gt;The biggest blocker was the rendering engine — tens of thousands of lines all calling Direct3D 9 directly.&lt;/p&gt;

&lt;p&gt;Rewriting every call was insane.&lt;br&gt;&lt;br&gt;
Building a full transpiler was also unrealistic.&lt;/p&gt;

&lt;p&gt;So I did this instead:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“What if the game keeps calling Direct3D exactly as before, and I just translate every command to WebGL on the fly?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That’s exactly what I built.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/LostMyCode/d3d9-webgl" rel="noopener noreferrer"&gt;https://github.com/LostMyCode/d3d9-webgl&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;I inserted a &lt;strong&gt;translation layer&lt;/strong&gt; between the game and the graphics API.&lt;br&gt;&lt;br&gt;
The original game code was left 100% untouched.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;What It Means&lt;/th&gt;
&lt;th&gt;Problem&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Manually rewrite game code&lt;/td&gt;
&lt;td&gt;Change every D3D9 call to WebGL&lt;/td&gt;
&lt;td&gt;Tens of thousands of changes, impossible to maintain&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Build a transpiler&lt;/td&gt;
&lt;td&gt;Auto-convert D3D9 → WebGL&lt;/td&gt;
&lt;td&gt;Semantic differences make it impossible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Translation layer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;D3D9-compatible API that outputs WebGL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Only have to implement the wrapper&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This approach let me remove the biggest Windows dependency without touching the original game.&lt;br&gt;&lt;br&gt;
(Again, mostly done by Antigravity and Claude — these AIs are terrifyingly good.)&lt;/p&gt;

&lt;p&gt;I wrote a separate deep-dive on the D3D9→WebGL wrapper if you’re interested:&lt;br&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/whiplash/d3d9-webgl-run-legacy-d3d9-code-in-the-browser-without-rewriting-it-1108" class="crayons-story__hidden-navigation-link"&gt;d3d9-webgl — Run Legacy D3D9 Code in the Browser Without Rewriting It&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/whiplash" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3808137%2Fee0c1dac-30f3-477e-bed6-9d38201ab143.png" alt="whiplash profile" class="crayons-avatar__image" width="400" height="400"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/whiplash" class="crayons-story__secondary fw-medium m:hidden"&gt;
              not a pro
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                not a pro
                
              
              &lt;div id="story-author-preview-content-3312504" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/whiplash" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3808137%2Fee0c1dac-30f3-477e-bed6-9d38201ab143.png" class="crayons-avatar__image" alt="" width="400" height="400"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;not a pro&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/whiplash/d3d9-webgl-run-legacy-d3d9-code-in-the-browser-without-rewriting-it-1108" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Mar 5&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/whiplash/d3d9-webgl-run-legacy-d3d9-code-in-the-browser-without-rewriting-it-1108" id="article-link-3312504"&gt;
          d3d9-webgl — Run Legacy D3D9 Code in the Browser Without Rewriting It
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webassembly"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webassembly&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webgl"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webgl&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/gamedev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;gamedev&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/whiplash/d3d9-webgl-run-legacy-d3d9-code-in-the-browser-without-rewriting-it-1108" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;4&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/whiplash/d3d9-webgl-run-legacy-d3d9-code-in-the-browser-without-rewriting-it-1108#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            3 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;





&lt;h2&gt;
  
  
  Everything Else I Had to Port
&lt;/h2&gt;

&lt;p&gt;Rendering was the hardest part, but it wasn’t the only one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Network: The Server Runs Inside the Browser
&lt;/h3&gt;

&lt;p&gt;This part surprises everyone.&lt;/p&gt;

&lt;p&gt;I also compiled the &lt;strong&gt;original C++ server&lt;/strong&gt; to WebAssembly and run it as a Web Worker in the same browser tab.&lt;/p&gt;

&lt;p&gt;Instead of real network packets, the client and server talk through &lt;code&gt;postMessage&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
Everything runs locally in one tab.&lt;/p&gt;

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

&lt;p&gt;Player data, stats, etc. are saved with SQLite + IDBFS (IndexedDB), so your progress survives tab closes — a completely serverless game server.&lt;/p&gt;

&lt;p&gt;Of course, you can still connect to a real server via WebSocket for online play with other people.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sound: FMOD → Web Audio API (1,260 lines)
&lt;/h3&gt;

&lt;p&gt;The original game used FMOD. I replaced it entirely with the Web Audio API, re-implementing 3D spatial audio (PannerNode), BGM streaming, distance culling, and everything else the game needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Input: Turning Browser Events into Win32 Messages
&lt;/h3&gt;

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

&lt;p&gt;I convert keyboard/mouse browser events into the exact Win32 messages (&lt;code&gt;WM_KEYDOWN&lt;/code&gt;, &lt;code&gt;WM_MOUSEMOVE&lt;/code&gt;, &lt;code&gt;WM_CHAR&lt;/code&gt;, etc.) the original engine expects.&lt;br&gt;&lt;br&gt;
Pointer Lock API for mouse capture and an HTML &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; overlay for login text input were also required.&lt;/p&gt;

&lt;h3&gt;
  
  
  Filesystem &amp;amp; Asset Loading: 2-Stage + Cache API
&lt;/h3&gt;

&lt;p&gt;GunZ assets are stored in custom &lt;code&gt;.mrs&lt;/code&gt; archives.&lt;br&gt;&lt;br&gt;
I load the 7 critical files first, then stream the rest (weapons, maps, etc.) in the background.&lt;br&gt;&lt;br&gt;
Cache API ensures second visits are instant.&lt;/p&gt;

&lt;h3&gt;
  
  
  Game Data Optimization (This Took Forever)
&lt;/h3&gt;

&lt;p&gt;Since everything has to come over the network, I spent weeks shrinking the data.&lt;/p&gt;

&lt;p&gt;I created version after version (default → v2 → … → v10) until load times were acceptable.&lt;/p&gt;

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

&lt;p&gt;The biggest win? Converting all sound effects from WAV to Opus.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;44.3 MB → 5.30 MB&lt;/strong&gt; — an &lt;strong&gt;88% reduction&lt;/strong&gt;.&lt;/p&gt;

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

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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;WAV (Waveform Audio)&lt;/th&gt;
&lt;th&gt;Opus&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Compression&lt;/td&gt;
&lt;td&gt;None (Linear PCM)&lt;/td&gt;
&lt;td&gt;Lossy (extremely efficient)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sound Quality&lt;/td&gt;
&lt;td&gt;Perfect (raw)&lt;/td&gt;
&lt;td&gt;Excellent (especially at low bitrates)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File Size&lt;/td&gt;
&lt;td&gt;Huge&lt;/td&gt;
&lt;td&gt;Tiny&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Latency&lt;/td&gt;
&lt;td&gt;Zero&lt;/td&gt;
&lt;td&gt;Very low (5–26 ms)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browser Compatibility&lt;/td&gt;
&lt;td&gt;Universal&lt;/td&gt;
&lt;td&gt;Modern browsers &amp;amp; mobile&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;Completely free&lt;/td&gt;
&lt;td&gt;Royalty-free&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Result? You can start playing in &lt;strong&gt;under 10 seconds&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;All assets are served from S3 + CloudFront, so they load fast from anywhere in the world.&lt;/p&gt;

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




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Sometimes you just have to wait for the right time
&lt;/h3&gt;

&lt;p&gt;Waiting for the right moment was key.  &lt;/p&gt;

&lt;p&gt;I knew almost nothing about WebGL or Direct3D. If I had tried this five years ago, I would have given up.&lt;br&gt;&lt;br&gt;
But in the AI era, it finally became possible.&lt;/p&gt;

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

&lt;p&gt;Thank you, Antigravity. Thank you, Claude Code. You two are legends.&lt;/p&gt;

&lt;h3&gt;
  
  
  To everyone who played GunZ back in the day
&lt;/h3&gt;

&lt;p&gt;I want as many old players as possible to feel this nostalgia again — no install, just click and play.&lt;/p&gt;

&lt;p&gt;If you enjoyed this, please share it!&lt;/p&gt;

&lt;p&gt;And yes, we have a Discord community too:&lt;br&gt;&lt;br&gt;
&lt;a href="https://discord.gg/ABG8JxuBQm" rel="noopener noreferrer"&gt;https://discord.gg/ABG8JxuBQm&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;20 years later, a childhood game is running inside a &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; tag.&lt;br&gt;&lt;br&gt;
The dream of bringing old games back to the web with just a click became real at exactly the right time.&lt;/p&gt;

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

&lt;p&gt;If you’re a developer who geeks out over this kind of stuff, or just someone who wants to relive the glory days — I hope you give it a try.&lt;/p&gt;

&lt;p&gt;Thanks for reading all the way to the end!&lt;br&gt;&lt;br&gt;
Now go click that link and kick some walls. 🚀&lt;/p&gt;




&lt;ul&gt;
&lt;li&gt;Whiplash GunZ Discord: &lt;a href="https://discord.gg/ABG8JxuBQm" rel="noopener noreferrer"&gt;https://discord.gg/ABG8JxuBQm&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Play now!: &lt;a href="https://gunz.sigr.io/" rel="noopener noreferrer"&gt;https://gunz.sigr.io/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>gamedev</category>
      <category>ai</category>
      <category>cpp</category>
      <category>webassembly</category>
    </item>
    <item>
      <title>How I Fully Ported a Heavily Windows-Dependent Online Game to the Browser with WebAssembly</title>
      <dc:creator>not a pro</dc:creator>
      <pubDate>Wed, 11 Mar 2026 11:30:00 +0000</pubDate>
      <link>https://forem.com/whiplash/how-i-fully-ported-a-heavily-windows-dependent-online-game-to-the-browser-with-webassembly-1fea</link>
      <guid>https://forem.com/whiplash/how-i-fully-ported-a-heavily-windows-dependent-online-game-to-the-browser-with-webassembly-1fea</guid>
      <description>&lt;p&gt;Have you ever played an online PC game?&lt;/p&gt;

&lt;p&gt;These days, cross-platform support is everywhere, and you can jump into games on pretty much any device without a second thought.&lt;br&gt;&lt;br&gt;
But just a few years ago, if you heard “PC game,” it almost always meant &lt;strong&gt;Windows only&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You’d wait through a painfully long download and installation, then finally launch the game — a ritual many of us remember all too well.&lt;/p&gt;

&lt;p&gt;This time, I took &lt;strong&gt;GunZ: The Duel&lt;/strong&gt;, a 2003 Windows-exclusive online TPS, and brought it fully into the browser using &lt;strong&gt;WebAssembly and WebGL&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;No download. No installation.&lt;br&gt;&lt;br&gt;
Just open the page in Google Chrome.&lt;/p&gt;

&lt;p&gt;A game that once took hours (or even days) of setup now starts with nothing more than a URL.&lt;/p&gt;

&lt;p&gt;Doesn’t that sound exciting?&lt;/p&gt;

&lt;p&gt;In this article, I’ll walk you through every technical challenge I tackled to make this GunZ browser port happen.&lt;/p&gt;


&lt;h2&gt;
  
  
  GunZ: The Duel — Now a Full Web Game
&lt;/h2&gt;

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

&lt;p&gt;GunZ was developed by MAIET Entertainment in Korea and published in Japan by GameOn. Its signature style — wall-kicking, sword-and-gun combos, and insane aerial combat — made it a cult classic. I played it a ton back in the day.&lt;/p&gt;

&lt;p&gt;In 2007 the entire source code (C++ client + server) leaked, and the community has been maintaining it ever since.&lt;/p&gt;

&lt;p&gt;After more than 20 years, no one had ever managed to run it in a browser… until now.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gunz.sigr.io/" rel="noopener noreferrer"&gt;https://gunz.sigr.io/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Just click and play. Works on Linux, macOS, iPhone, Android — literally anything with a modern browser (though phones are… let’s say “challenging” 😂).&lt;/p&gt;

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

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

&lt;p&gt;This isn’t a “kinda works” or “looks similar” demo.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;It’s the complete original game&lt;/strong&gt;, running exactly as it did in 2003.&lt;/p&gt;

&lt;p&gt;In this post I’ll cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How I actually pulled it off&lt;/li&gt;
&lt;li&gt;Why I chose to revive a 20-year-old game right now&lt;/li&gt;
&lt;li&gt;All the past failures that led to this moment&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Past Failures and the Road to a True Browser Port
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Previous Attempts (and Why They Failed)
&lt;/h3&gt;

&lt;p&gt;This isn’t my first try at browser GunZ.&lt;/p&gt;

&lt;p&gt;A few years ago I attempted to rebuild it from scratch in JavaScript + Three.js:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/LostMyCode/three-gunz" rel="noopener noreferrer"&gt;https://github.com/LostMyCode/three-gunz&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I got map rendering working, but re-implementing the entire game engine was overwhelming. The project died quietly (the repo is still there if you’re curious).&lt;/p&gt;
&lt;h3&gt;
  
  
  The WebAssembly Route
&lt;/h3&gt;

&lt;p&gt;GunZ is almost entirely written in C++.&lt;br&gt;&lt;br&gt;
We all know you can compile C++ to WebAssembly with Emscripten, so in theory it should be easy, right?&lt;/p&gt;

&lt;p&gt;Wrong.&lt;/p&gt;

&lt;p&gt;GunZ is &lt;strong&gt;extremely&lt;/strong&gt; Windows-dependent. It calls Windows-specific APIs for rendering, audio, input — everything. The rendering engine uses &lt;strong&gt;Direct3D 9&lt;/strong&gt; directly.&lt;/p&gt;

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

&lt;p&gt;Emscripten can’t magically turn Direct3D calls into something the browser understands.&lt;br&gt;&lt;br&gt;
So any serious port meant rewriting thousands of lines of rendering code to use WebGL instead.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;No way I was doing that by hand.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
That’s why the project stayed in the “theoretically possible but I’ll die before finishing” category for years.&lt;/p&gt;
&lt;h3&gt;
  
  
  Then AI Entered the Chat
&lt;/h3&gt;

&lt;p&gt;I kept thinking: “The full C++ source code exists… there &lt;em&gt;has&lt;/em&gt; to be a way.”&lt;/p&gt;

&lt;p&gt;Manually rewriting everything was impossible.&lt;br&gt;&lt;br&gt;
But what if I let &lt;strong&gt;AI&lt;/strong&gt; do the heavy lifting?&lt;/p&gt;

&lt;p&gt;That’s exactly what I did.&lt;/p&gt;

&lt;p&gt;I used two tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Google Antigravity&lt;/strong&gt; (AI Pro plan — ¥2,900/month)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude Code&lt;/strong&gt; (Max 5x plan — $100)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I started with Google Antigravity.&lt;/p&gt;

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

&lt;p&gt;The AI handled:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Analyzing the massive C++ codebase&lt;/li&gt;
&lt;li&gt;Finding every Windows API dependency&lt;/li&gt;
&lt;li&gt;Abstracting Direct3D calls&lt;/li&gt;
&lt;li&gt;Fixing the build system for Emscripten&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Watching an AI understand and refactor a huge legacy codebase in hours instead of days was honestly mind-blowing. One evening I went out drinking with friends… came back and the work was &lt;em&gt;done&lt;/em&gt;. First time I ever felt that rush.&lt;/p&gt;

&lt;p&gt;The Google plan was amazing — until Google suddenly tightened the rate limits. A project this size would hit weekly quotas instantly.&lt;/p&gt;

&lt;p&gt;So I switched to &lt;strong&gt;Claude Code&lt;/strong&gt; (the $100 Max 5x plan).&lt;/p&gt;

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

&lt;p&gt;Holy crap. Quotas barely moved, and bugs that had me stuck for a week were fixed in hours.&lt;br&gt;&lt;br&gt;
I’m not exaggerating when I say I can’t imagine working without it anymore.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Magic Trick: Real-Time Direct3D → WebGL Translation Layer
&lt;/h2&gt;

&lt;p&gt;The biggest blocker was the rendering engine — tens of thousands of lines all calling Direct3D 9 directly.&lt;/p&gt;

&lt;p&gt;Rewriting every call was insane.&lt;br&gt;&lt;br&gt;
Building a full transpiler was also unrealistic.&lt;/p&gt;

&lt;p&gt;So I did this instead:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“What if the game keeps calling Direct3D exactly as before, and I just translate every command to WebGL on the fly?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That’s exactly what I built.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/LostMyCode/d3d9-webgl" rel="noopener noreferrer"&gt;https://github.com/LostMyCode/d3d9-webgl&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;I inserted a &lt;strong&gt;translation layer&lt;/strong&gt; between the game and the graphics API.&lt;br&gt;&lt;br&gt;
The original game code was left 100% untouched.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;What It Means&lt;/th&gt;
&lt;th&gt;Problem&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Manually rewrite game code&lt;/td&gt;
&lt;td&gt;Change every D3D9 call to WebGL&lt;/td&gt;
&lt;td&gt;Tens of thousands of changes, impossible to maintain&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Build a transpiler&lt;/td&gt;
&lt;td&gt;Auto-convert D3D9 → WebGL&lt;/td&gt;
&lt;td&gt;Semantic differences make it impossible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Translation layer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;D3D9-compatible API that outputs WebGL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Only have to implement the wrapper&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This approach let me remove the biggest Windows dependency without touching the original game.&lt;br&gt;&lt;br&gt;
(Again, mostly done by Antigravity and Claude — these AIs are terrifyingly good.)&lt;/p&gt;

&lt;p&gt;I wrote a separate deep-dive on the D3D9→WebGL wrapper if you’re interested:&lt;br&gt;&lt;br&gt;


&lt;/p&gt;
&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/whiplash/d3d9-webgl-run-legacy-d3d9-code-in-the-browser-without-rewriting-it-1108" class="crayons-story__hidden-navigation-link"&gt;d3d9-webgl — Run Legacy D3D9 Code in the Browser Without Rewriting It&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/whiplash" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3808137%2Fee0c1dac-30f3-477e-bed6-9d38201ab143.png" alt="whiplash profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/whiplash" class="crayons-story__secondary fw-medium m:hidden"&gt;
              not a pro
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                not a pro
                
              
              &lt;div id="story-author-preview-content-3312504" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/whiplash" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3808137%2Fee0c1dac-30f3-477e-bed6-9d38201ab143.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;not a pro&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/whiplash/d3d9-webgl-run-legacy-d3d9-code-in-the-browser-without-rewriting-it-1108" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Mar 5&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/whiplash/d3d9-webgl-run-legacy-d3d9-code-in-the-browser-without-rewriting-it-1108" id="article-link-3312504"&gt;
          d3d9-webgl — Run Legacy D3D9 Code in the Browser Without Rewriting It
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webassembly"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webassembly&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webgl"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webgl&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/gamedev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;gamedev&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/whiplash/d3d9-webgl-run-legacy-d3d9-code-in-the-browser-without-rewriting-it-1108" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;4&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/whiplash/d3d9-webgl-run-legacy-d3d9-code-in-the-browser-without-rewriting-it-1108#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            3 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;







&lt;h2&gt;
  
  
  Everything Else I Had to Port
&lt;/h2&gt;

&lt;p&gt;Rendering was the hardest part, but it wasn’t the only one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Network: The Server Runs Inside the Browser
&lt;/h3&gt;

&lt;p&gt;This part surprises everyone.&lt;/p&gt;

&lt;p&gt;I also compiled the &lt;strong&gt;original C++ server&lt;/strong&gt; to WebAssembly and run it as a Web Worker in the same browser tab.&lt;/p&gt;

&lt;p&gt;Instead of real network packets, the client and server talk through &lt;code&gt;postMessage&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
Everything runs locally in one tab.&lt;/p&gt;

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

&lt;p&gt;Player data, stats, etc. are saved with SQLite + IDBFS (IndexedDB), so your progress survives tab closes — a completely serverless game server.&lt;/p&gt;

&lt;p&gt;Of course, you can still connect to a real server via WebSocket for online play with other people.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sound: FMOD → Web Audio API (1,260 lines)
&lt;/h3&gt;

&lt;p&gt;The original game used FMOD. I replaced it entirely with the Web Audio API, re-implementing 3D spatial audio (PannerNode), BGM streaming, distance culling, and everything else the game needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Input: Turning Browser Events into Win32 Messages
&lt;/h3&gt;

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

&lt;p&gt;I convert keyboard/mouse browser events into the exact Win32 messages (&lt;code&gt;WM_KEYDOWN&lt;/code&gt;, &lt;code&gt;WM_MOUSEMOVE&lt;/code&gt;, &lt;code&gt;WM_CHAR&lt;/code&gt;, etc.) the original engine expects.&lt;br&gt;&lt;br&gt;
Pointer Lock API for mouse capture and an HTML &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; overlay for login text input were also required.&lt;/p&gt;

&lt;h3&gt;
  
  
  Filesystem &amp;amp; Asset Loading: 2-Stage + Cache API
&lt;/h3&gt;

&lt;p&gt;GunZ assets are stored in custom &lt;code&gt;.mrs&lt;/code&gt; archives.&lt;br&gt;&lt;br&gt;
I load the 7 critical files first, then stream the rest (weapons, maps, etc.) in the background.&lt;br&gt;&lt;br&gt;
Cache API ensures second visits are instant.&lt;/p&gt;

&lt;h3&gt;
  
  
  Game Data Optimization (This Took Forever)
&lt;/h3&gt;

&lt;p&gt;Since everything has to come over the network, I spent weeks shrinking the data.&lt;/p&gt;

&lt;p&gt;I created version after version (default → v2 → … → v10) until load times were acceptable.&lt;/p&gt;

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

&lt;p&gt;The biggest win? Converting all sound effects from WAV to Opus.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;44.3 MB → 5.30 MB&lt;/strong&gt; — an &lt;strong&gt;88% reduction&lt;/strong&gt;.&lt;/p&gt;

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

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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;WAV (Waveform Audio)&lt;/th&gt;
&lt;th&gt;Opus&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Compression&lt;/td&gt;
&lt;td&gt;None (Linear PCM)&lt;/td&gt;
&lt;td&gt;Lossy (extremely efficient)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sound Quality&lt;/td&gt;
&lt;td&gt;Perfect (raw)&lt;/td&gt;
&lt;td&gt;Excellent (especially at low bitrates)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File Size&lt;/td&gt;
&lt;td&gt;Huge&lt;/td&gt;
&lt;td&gt;Tiny&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Latency&lt;/td&gt;
&lt;td&gt;Zero&lt;/td&gt;
&lt;td&gt;Very low (5–26 ms)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browser Compatibility&lt;/td&gt;
&lt;td&gt;Universal&lt;/td&gt;
&lt;td&gt;Modern browsers &amp;amp; mobile&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;Completely free&lt;/td&gt;
&lt;td&gt;Royalty-free&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Result? You can start playing in &lt;strong&gt;under 10 seconds&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;All assets are served from S3 + CloudFront, so they load fast from anywhere in the world.&lt;/p&gt;

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




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Waiting for the right moment was key.&lt;br&gt;&lt;br&gt;
I knew almost nothing about WebGL or Direct3D. If I had tried this five years ago, I would have given up.&lt;br&gt;&lt;br&gt;
But in the AI era, it finally became possible.&lt;/p&gt;

&lt;p&gt;Thank you, Antigravity. Thank you, Claude Code. You two are legends.&lt;/p&gt;

&lt;h3&gt;
  
  
  To everyone who played GunZ back in the day
&lt;/h3&gt;

&lt;p&gt;I want as many old players as possible to feel this nostalgia again — no install, just click and play.&lt;/p&gt;

&lt;p&gt;If you enjoyed this, please share it!&lt;/p&gt;

&lt;p&gt;And yes, we have a Discord community too:&lt;br&gt;&lt;br&gt;
&lt;a href="https://discord.gg/ABG8JxuBQm" rel="noopener noreferrer"&gt;https://discord.gg/ABG8JxuBQm&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;20 years later, a childhood game is running inside a &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; tag.&lt;br&gt;&lt;br&gt;
The dream of bringing old games back to the web with just a click became real at exactly the right time.&lt;/p&gt;

&lt;p&gt;If you’re a developer who geeks out over this kind of stuff, or just someone who wants to relive the glory days — I hope you give it a try.&lt;/p&gt;

&lt;p&gt;Thanks for reading all the way to the end!&lt;br&gt;&lt;br&gt;
Now go click that link and kick some walls. 🚀&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>webgl</category>
      <category>webassembly</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>d3d9-webgl — Run Legacy D3D9 Code in the Browser Without Rewriting It</title>
      <dc:creator>not a pro</dc:creator>
      <pubDate>Thu, 05 Mar 2026 14:50:12 +0000</pubDate>
      <link>https://forem.com/whiplash/d3d9-webgl-run-legacy-d3d9-code-in-the-browser-without-rewriting-it-1108</link>
      <guid>https://forem.com/whiplash/d3d9-webgl-run-legacy-d3d9-code-in-the-browser-without-rewriting-it-1108</guid>
      <description>&lt;p&gt;I ported a 2003 online game to the browser. The game was written in C++ with Direct3D 9. Emscripten handles the C++-to-Wasm part fine, but the moment you &lt;code&gt;#include &amp;lt;d3d9.h&amp;gt;&lt;/code&gt;, the build dies — that header only ships with the Windows DirectX SDK.&lt;/p&gt;

&lt;p&gt;The obvious options all sucked:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Problem&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Rewrite the renderer in WebGL / Three.js&lt;/td&gt;
&lt;td&gt;You're basically rewriting the game&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Convert D3D9 calls to OpenGL one by one&lt;/td&gt;
&lt;td&gt;Still a massive rewrite&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Re-implement the D3D9 API itself, backed by WebGL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;The existing code doesn't need to change&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So I went with option 3 and built &lt;strong&gt;&lt;a href="https://github.com/LostMyCode/d3d9-webgl" rel="noopener noreferrer"&gt;d3d9-webgl&lt;/a&gt;&lt;/strong&gt;: a header + source file set that implements D3D9 interfaces on top of WebGL 2.0. You drop it into an Emscripten project, and your D3D9 code compiles and runs in the browser as-is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does it actually work?
&lt;/h2&gt;

&lt;p&gt;The game I was porting is &lt;strong&gt;GunZ: The Duel&lt;/strong&gt; (2003). The rendering code required almost no changes. You can play it at &lt;a href="https://gunz.sigr.io/" rel="noopener noreferrer"&gt;gunz.sigr.io&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;

&lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://gunz.sigr.io/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgunz.sigr.io%2Fimages%2Fwhiplash-gunz-opt.jpg" height="auto" class="m-0"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://gunz.sigr.io/" rel="noopener noreferrer" class="c-link"&gt;
            Whiplash GunZ
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Play the classic GunZ Online in your browser. No download required.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
          gunz.sigr.io
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;




&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;IDirect3D9&lt;/code&gt;, &lt;code&gt;IDirect3DDevice9&lt;/code&gt;, &lt;code&gt;IDirect3DTexture9&lt;/code&gt; — all the COM interfaces are re-implemented. When your app calls &lt;code&gt;IDirect3DDevice9::DrawPrimitive()&lt;/code&gt;, the wrapper translates it to &lt;code&gt;glDrawArrays()&lt;/code&gt; internally. The application can't tell the difference.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hard parts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Rebuilding the Fixed Function Pipeline in GLSL
&lt;/h3&gt;

&lt;p&gt;D3D9's FFP — &lt;code&gt;SetLight&lt;/code&gt;, &lt;code&gt;SetMaterial&lt;/code&gt;, &lt;code&gt;SetTransform&lt;/code&gt; — doesn't exist in WebGL 2.0. I had to write GLSL shaders that replicate the entire lighting model: World/View/Projection transforms, normal transforms, per-vertex diffuse + specular for up to 3 point lights in the vertex shader, and texture blending + color composition in the fragment shader.&lt;/p&gt;

&lt;p&gt;This was the most time-consuming part of the whole project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Parsing FVF at runtime
&lt;/h3&gt;

&lt;p&gt;D3D9 vertex buffers use Flexible Vertex Format (FVF) — bit flags that describe the vertex layout. The wrapper parses these at runtime to set up &lt;code&gt;glVertexAttribPointer&lt;/code&gt; with the right stride and offset.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Position + Normal + Vertex Color + 1 UV set&lt;/span&gt;
&lt;span class="n"&gt;DWORD&lt;/span&gt; &lt;span class="n"&gt;fvf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;D3DFVF_XYZ&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;D3DFVF_NORMAL&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;D3DFVF_DIFFUSE&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;D3DFVF_TEX1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Texture format mismatches
&lt;/h3&gt;

&lt;p&gt;D3D9 uses BGRA; WebGL wants RGBA. &lt;code&gt;A8R8G8B8&lt;/code&gt; gets swizzled on upload. 16-bit formats like &lt;code&gt;R5G6B5&lt;/code&gt; and &lt;code&gt;A4R4G4B4&lt;/code&gt; are expanded to RGBA8.&lt;/p&gt;

&lt;p&gt;DXT1/DXT3/DXT5 compressed textures are passed straight through — the &lt;code&gt;WEBGL_compressed_texture_s3tc&lt;/code&gt; extension covers this on pretty much every desktop browser.&lt;/p&gt;
&lt;h3&gt;
  
  
  Y-axis flip
&lt;/h3&gt;

&lt;p&gt;D3D9: top-left origin, Y goes down. OpenGL: bottom-left origin, Y goes up. When rendering to an FBO via &lt;code&gt;SetRenderTarget&lt;/code&gt;, the image ends up flipped. The wrapper compensates during &lt;code&gt;StretchRect&lt;/code&gt; blit to screen. Direct screen rendering doesn't need the fix.&lt;/p&gt;
&lt;h3&gt;
  
  
  Clip planes via &lt;code&gt;discard&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;WebGL has no hardware clip planes. The fragment shader calculates clip-plane distance and discards fragments on the wrong side. Simple, but one of those things you don't think about until everything clips wrong.&lt;/p&gt;
&lt;h3&gt;
  
  
  State caching
&lt;/h3&gt;

&lt;p&gt;D3D9 apps call &lt;code&gt;SetRenderState&lt;/code&gt; / &lt;code&gt;SetTexture&lt;/code&gt; / &lt;code&gt;SetSamplerState&lt;/code&gt; hundreds of times per frame, and most of those calls set the same value that's already set. The wrapper caches everything — texture bindings, shader programs, sampler states, viewport, scissor — and only issues a GL call when something actually changes.&lt;/p&gt;
&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

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

&lt;p&gt;Copy five files into your project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;d3d9.h&lt;/code&gt; — D3D9 type definitions &amp;amp; interfaces&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;d3d9.cpp&lt;/code&gt; — WebGL 2.0 implementation (~3,400 lines)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;d3dx9math.h&lt;/code&gt; — D3DX math library&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;d3dx9.h&lt;/code&gt; — D3DX stubs&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;windows_compat.h&lt;/code&gt; — Windows API stubs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CMake:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cmake"&gt;&lt;code&gt;&lt;span class="nb"&gt;add_executable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;my_app main.cpp d3d9.cpp&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;target_link_options&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;my_app PRIVATE
    -sUSE_WEBGL2=1
    -sFULL_ES3=1
    -sWASM=1
    -sALLOW_MEMORY_GROWTH=1
&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 shell"&gt;&lt;code&gt;emcmake cmake &lt;span class="nb"&gt;.&lt;/span&gt;
emmake make
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That's it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;FFP only.&lt;/strong&gt; HLSL vertex/pixel shaders are not supported. The wrapper reports &lt;code&gt;VertexShaderVersion = 0&lt;/code&gt;, so applications with shader code paths need to fall back to FFP.&lt;/p&gt;

&lt;p&gt;Other limitations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Max 3 point lights (no directional or spotlights)&lt;/li&gt;
&lt;li&gt;Vertex buffer stream 0 only&lt;/li&gt;
&lt;li&gt;No &lt;code&gt;LockRect&lt;/code&gt; on render targets (no GPU readback)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;D3DXMatrixInverse&lt;/code&gt; is stubbed (returns identity)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In practice, most games and tools from the early 2000s used FFP anyway. Programmable shaders only became common in the later DX9 era, so anything before ~2005 is likely covered.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why I'm releasing this
&lt;/h2&gt;

&lt;p&gt;While building the GunZ port, I kept searching for something like this. It didn't exist. I spent a lot of time writing this wrapper, and I figured someone else out there is probably stuck on the same problem right now.&lt;/p&gt;

&lt;p&gt;If you have an old D3D9 codebase and you've been curious about running it in a browser — try it out and let me know how it goes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/LostMyCode/d3d9-webgl" rel="noopener noreferrer"&gt;d3d9-webgl&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/LostMyCode" rel="noopener noreferrer"&gt;
        LostMyCode
      &lt;/a&gt; / &lt;a href="https://github.com/LostMyCode/d3d9-webgl" rel="noopener noreferrer"&gt;
        d3d9-webgl
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Direct3D 9 Fixed-Function Pipeline → WebGL 2.0 wrapper for Emscripten/WASM
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;d3d9-webgl&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;A Direct3D 9 Fixed-Function Pipeline implementation targeting WebGL 2.0 via Emscripten/WebAssembly.&lt;/p&gt;

&lt;p&gt;Drop-in D3D9 headers and a single &lt;code&gt;.cpp&lt;/code&gt; file that translates D3D9 API calls to WebGL — enabling legacy D3D9 applications to run in the browser without rewriting their rendering code.&lt;/p&gt;

&lt;p&gt;&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;br&gt;
&lt;tbody&gt;
&lt;br&gt;
&lt;tr&gt;
&lt;br&gt;
&lt;td&gt;
&lt;br&gt;
&lt;strong&gt;01 — Rotating Cube&lt;/strong&gt;&lt;br&gt;Textures, VB/IB, DrawIndexedPrimitive&lt;/td&gt;
&lt;br&gt;
&lt;td&gt;
&lt;br&gt;
&lt;strong&gt;02 — FFP Lighting&lt;/strong&gt;&lt;br&gt;3 Point Lights, Materials, DrawPrimitiveUP&lt;/td&gt;
&lt;br&gt;
&lt;/tr&gt;
&lt;br&gt;
&lt;tr&gt;
&lt;br&gt;
&lt;td&gt;&lt;a rel="noopener noreferrer" href="https://github.com/LostMyCode/d3d9-webgl/screenshots/01-rotating-cube.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FLostMyCode%2Fd3d9-webgl%2Fscreenshots%2F01-rotating-cube.png" width="400"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;br&gt;
&lt;td&gt;&lt;a rel="noopener noreferrer" href="https://github.com/LostMyCode/d3d9-webgl/screenshots/02-lighting.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FLostMyCode%2Fd3d9-webgl%2Fscreenshots%2F02-lighting.png" width="400"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;br&gt;
&lt;/tr&gt;
&lt;br&gt;
&lt;/tbody&gt;
&lt;br&gt;
&lt;/table&gt;&lt;/div&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;✨ Features&lt;/h2&gt;
&lt;/div&gt;


&lt;ul&gt;

&lt;li&gt;

&lt;strong&gt;Full FFP Emulation&lt;/strong&gt; — Per-vertex lighting (3 point lights), materials, texture stage states, transform matrices&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;All Draw Paths&lt;/strong&gt; — &lt;code&gt;DrawIndexedPrimitive&lt;/code&gt;, &lt;code&gt;DrawIndexedPrimitiveUP&lt;/code&gt;, &lt;code&gt;DrawPrimitiveUP&lt;/code&gt;, &lt;code&gt;DrawPrimitive&lt;/code&gt;
&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;FVF Parsing&lt;/strong&gt; — Automatic vertex layout from &lt;code&gt;D3DFVF_XYZ&lt;/code&gt;, &lt;code&gt;D3DFVF_XYZRHW&lt;/code&gt;, &lt;code&gt;D3DFVF_NORMAL&lt;/code&gt;, &lt;code&gt;D3DFVF_DIFFUSE&lt;/code&gt;, &lt;code&gt;D3DFVF_TEX1&lt;/code&gt;–&lt;code&gt;TEX8&lt;/code&gt;
&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Texture Formats&lt;/strong&gt; — DXT1/3/5 (via S3TC extension), A8R8G8B8, X8R8G8B8, R5G6B5, A4R4G4B4, A1R5G5B5, R8G8B8&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Linear Fog&lt;/strong&gt; — Per-pixel D3D9 table fog (&lt;code&gt;D3DRS_FOGENABLE&lt;/code&gt;, &lt;code&gt;D3DRS_FOGCOLOR&lt;/code&gt;, &lt;code&gt;D3DRS_FOGSTART&lt;/code&gt;, &lt;code&gt;D3DRS_FOGEND&lt;/code&gt;); automatically skipped for &lt;code&gt;D3DFVF_XYZRHW&lt;/code&gt; vertices&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Render States&lt;/strong&gt; — Alpha blending, alpha test, depth test, stencil operations, culling…&lt;/li&gt;

&lt;/ul&gt;
&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/LostMyCode/d3d9-webgl" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;





&lt;p&gt;Issues and PRs welcome.&lt;/p&gt;

</description>
      <category>webassembly</category>
      <category>webdev</category>
      <category>webgl</category>
      <category>gamedev</category>
    </item>
  </channel>
</rss>
