<?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: Dominic Harmon</title>
    <description>The latest articles on DEV Community by Dominic Harmon (@dominic_harmon_18363706c7).</description>
    <link>https://dev.to/dominic_harmon_18363706c7</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4004719%2Fa1686e81-0968-4ac2-aff7-f5be182b8e8b.png</url>
      <title>DEV Community: Dominic Harmon</title>
      <link>https://dev.to/dominic_harmon_18363706c7</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dominic_harmon_18363706c7"/>
    <language>en</language>
    <item>
      <title>My first raylib game — Plane War Ultimate (C++17, 2300 LOC, no engine)</title>
      <dc:creator>Dominic Harmon</dc:creator>
      <pubDate>Sat, 27 Jun 2026 07:58:45 +0000</pubDate>
      <link>https://dev.to/dominic_harmon_18363706c7/my-first-raylib-game-plane-war-ultimate-c17-2300-loc-no-engine-1761</link>
      <guid>https://dev.to/dominic_harmon_18363706c7/my-first-raylib-game-plane-war-ultimate-c17-2300-loc-no-engine-1761</guid>
      <description>&lt;h1&gt;
  
  
  I Built a 2D Shoot-'em-Up in C++ Without a Game Engine — Here's What I Learned
&lt;/h1&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%2Fraw.githubusercontent.com%2Fdominicharmon-commits%2FPlaneWarUltimate%2Fmain%2Fscreenshots%2Fgameplay.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fdominicharmon-commits%2FPlaneWarUltimate%2Fmain%2Fscreenshots%2Fgameplay.gif" alt="Gameplay Demo"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;After a few weeks of late nights and too much coffee, I shipped my first real game: &lt;strong&gt;Plane War Ultimate&lt;/strong&gt; — a classic arcade shoot-'em-up built from scratch in C++17.&lt;/p&gt;

&lt;p&gt;No Unity. No Unreal. No Godot. Just C++, raylib, and a lot of trial and error.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/dominicharmon-commits/PlaneWarUltimate" rel="noopener noreferrer"&gt;github.com/dominicharmon-commits/PlaneWarUltimate&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Download:&lt;/strong&gt; &lt;a href="https://github.com/dominicharmon-commits/PlaneWarUltimate/releases" rel="noopener noreferrer"&gt;v1.0 Release&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why Build Without an Engine?
&lt;/h2&gt;

&lt;p&gt;Because I wanted to understand what's actually happening under the hood.&lt;/p&gt;

&lt;p&gt;When you use Unity, a lot is done for you — physics, collision, rendering pipeline, object lifecycle. Building from scratch forces you to answer questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How do I manage 300 particles without GC stutter?&lt;/li&gt;
&lt;li&gt;What's the cleanest way to structure a game loop?&lt;/li&gt;
&lt;li&gt;How do I make a boss fight feel satisfying?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every feature in this game is something I had to design, implement, and debug myself. That's where the real learning happens.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the Game Actually Does
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Details&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Combat&lt;/td&gt;
&lt;td&gt;Hold-to-shoot, triple-shot power-up, screen-clearing bomb&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Enemies&lt;/td&gt;
&lt;td&gt;3 types (small/medium/large) with difficulty scaling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Boss&lt;/td&gt;
&lt;td&gt;2-phase AI — aimed shots in phase 1, 5-way fan pattern in phase 2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Death FX&lt;/td&gt;
&lt;td&gt;Screen flash, slow-motion, multi-stage explosion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Visuals&lt;/td&gt;
&lt;td&gt;Particle explosions, combo popups, screen shake, procedural sprites&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Audio&lt;/td&gt;
&lt;td&gt;Procedural SFX generation (sine/square/saw waves)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Persistence&lt;/td&gt;
&lt;td&gt;Local high score save/load&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Settings&lt;/td&gt;
&lt;td&gt;Master volume + SFX volume, fullscreen toggle&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Architecture: Keep It Simple
&lt;/h2&gt;

&lt;p&gt;The codebase is ~2,300 lines split across 7 modules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;main.cpp           Entry point
game.h / game.cpp  Game loop, state machine, rendering (1,200 lines)
player             Movement, shooting, invincibility frames
enemy              Three enemy types, spawning logic
boss               2-phase AI, attack patterns, death effects
particle           Explosion particle system
powerup            Heal / triple-shot / bomb pickups
pool.h             Generic object pool template (37 lines!)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Object Pool
&lt;/h3&gt;

&lt;p&gt;Every dynamic object in the game — bullets, enemies, particles, powerups — uses the same 37-line template:&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="k"&gt;template&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typename&lt;/span&gt; &lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;Pool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;Acquire&lt;/span&gt;&lt;span class="p"&gt;()&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="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&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="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&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="nb"&gt;nullptr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Clear&lt;/span&gt;&lt;span class="p"&gt;()&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="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;new&lt;/code&gt;. No &lt;code&gt;delete&lt;/code&gt;. No heap allocation during gameplay. At 60 FPS with 300 particles, 100 bullets, and 30 enemies on screen, this matters.&lt;/p&gt;

&lt;h3&gt;
  
  
  State Machine
&lt;/h3&gt;

&lt;p&gt;The game uses a flat enum-based state machine:&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="k"&gt;enum&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GameState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;MENU&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SETTINGS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PLAYING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PAUSED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GAMEOVER&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;Game&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;GameState&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;MENU&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="n"&gt;UpdateMenu&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;     &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;GameState&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;SETTINGS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UpdateSettings&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;GameState&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;PLAYING&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;UpdatePlaying&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;GameState&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;PAUSED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="n"&gt;UpdatePaused&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;GameState&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;GAMEOVER&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UpdateGameOver&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No framework, no middleware — just a switch statement. It's boring code, and boring code is easy to debug.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Boss Fight
&lt;/h2&gt;

&lt;p&gt;The boss was the most fun to design. 240x140 pixels, all drawn in code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Phase 1 (HP &amp;gt; 50%):
  - Two wing pods with gun barrels
  - Central hull with panel lines
  - Blue-glowing core
  - Twin engine exhaust with animated flames
  - Single aimed shot at the player

Phase 2 (HP &amp;lt;= 50%):
  - Core turns red with pulsing glow
  - Red border flicker on all modules
  - Sinusoidal vertical movement
  - 5-way fan-pattern attacks
  - Visible hull cracks at low HP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On death: screen flashes white, 3 simultaneous explosions spawn 90 particles, and the shake intensity doubles. It's dumb, it's over-the-top, and it's exactly what a boss fight should feel like.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Hardest Bug
&lt;/h2&gt;

&lt;p&gt;I shipped a bug where the game froze after 2 seconds. Turns out I'd accidentally renamed a function in a way that created infinite recursion:&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;// The method was originally named PlaySfx, calling raylib's PlaySound&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;Game&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;PlaySound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Sound&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;snd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;SetSoundVolume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;snd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sfxVolume&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;PlaySound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;snd&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// Calls itself! Stack overflow -&amp;gt; freeze&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;replace_all&lt;/code&gt; refactor gone wrong. Lesson: never blindly rename functions that shadow library APIs. Name your wrapper something unambiguous: &lt;code&gt;PlayGameSfx&lt;/code&gt;, not &lt;code&gt;PlaySound&lt;/code&gt;.&lt;/p&gt;




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

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Split game.cpp earlier.&lt;/strong&gt; 1,200 lines in one file is manageable but pushing it. I'd split into &lt;code&gt;game_menus.cpp&lt;/code&gt;, &lt;code&gt;game_combat.cpp&lt;/code&gt;, &lt;code&gt;game_render.cpp&lt;/code&gt; from the start.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Add unit tests for Pool.&lt;/strong&gt; It's a 37-line template, but it's the backbone of the entire game. A simple test suite would have caught edge cases early.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use a fixed-point physics timestep.&lt;/strong&gt; Right now, everything runs at display rate. If vsync fails or the monitor is 144Hz, gameplay speed changes. A proper accumulator-based loop would fix this.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pre-render complex shapes to textures.&lt;/strong&gt; The boss is drawn with ~30 draw calls every frame. Baking that into a render texture would cut that to 1.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




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

&lt;p&gt;The project builds on Windows with Visual Studio 2022 and raylib 6.0. Everything you need is in the repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/dominicharmon-commits/PlaneWarUltimate.git
&lt;span class="c"&gt;# Open MyFirstGame.sln in VS2022&lt;/span&gt;
&lt;span class="c"&gt;# Switch to Release + x64, press F5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or just download the zip from &lt;a href="https://github.com/dominicharmon-commits/PlaneWarUltimate/releases" rel="noopener noreferrer"&gt;Releases&lt;/a&gt; and run.&lt;/p&gt;




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

&lt;p&gt;Building a game without an engine is not the efficient way to make a game. But if your goal is to learn — to actually understand memory management, game loops, collision detection, and audio pipelines — there's no substitute.&lt;/p&gt;

&lt;p&gt;The full source is on GitHub. Star it if you find it useful, and I'd love feedback from anyone who's walked this path before.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; &lt;code&gt;#cpp&lt;/code&gt; &lt;code&gt;#gamedev&lt;/code&gt; &lt;code&gt;#raylib&lt;/code&gt; &lt;code&gt;#tutorial&lt;/code&gt; &lt;code&gt;#beginners&lt;/code&gt;&lt;/p&gt;

</description>
      <category>cpp</category>
      <category>gamedev</category>
      <category>raylib</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>I Built a Claude Code Skill That Finds Broken Callers Before You Deploy</title>
      <dc:creator>Dominic Harmon</dc:creator>
      <pubDate>Sat, 27 Jun 2026 02:33:31 +0000</pubDate>
      <link>https://dev.to/dominic_harmon_18363706c7/i-built-a-claude-code-skill-that-finds-broken-callers-before-you-deploy-52a2</link>
      <guid>https://dev.to/dominic_harmon_18363706c7/i-built-a-claude-code-skill-that-finds-broken-callers-before-you-deploy-52a2</guid>
      <description>&lt;h1&gt;
  
  
  I Built a Claude Code Skill That Finds Broken Callers Before You Deploy
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;You fix a bug. You deploy. Three hours later, production is on fire. Here's how I built a tool to stop that.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The moment I realized I needed this
&lt;/h2&gt;

&lt;p&gt;I was working on a Chinese chess app. Changed &lt;code&gt;is_checkmate()&lt;/code&gt; — added a simple empty-board guard clause. Two lines. Trivial.&lt;/p&gt;

&lt;p&gt;Ran my tests. Green. Deployed.&lt;/p&gt;

&lt;p&gt;Two hours later: puzzle solving was broken. Level submission returned wrong results. &lt;strong&gt;I changed one function. It silently broke two callers I didn't even know existed.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The test coverage report said 87%. It didn't matter. Coverage tells you which lines ran — not which callers your change silently corrupted.&lt;/p&gt;




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

&lt;p&gt;&lt;strong&gt;Test Shield&lt;/strong&gt; — a Claude Code skill with one job:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read your git diff&lt;/li&gt;
&lt;li&gt;Trace every caller of every changed function&lt;/li&gt;
&lt;li&gt;Show you which impacts are expected (you meant to change that) and which are surprising (you didn't know that used it)&lt;/li&gt;
&lt;li&gt;Generate pytest regression tests for the surprising ones&lt;/li&gt;
&lt;li&gt;Run them — all green? Ship. Any red? Stop and fix.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/test-shield
     │
     ▼
Changed: is_checkmate() — chess_utils.py:320

⚠️  Unexpected impact:
  ● solve_puzzle() — levels.py:687     ← wouldn't have caught this
  ● submit_level_solution() — levels.py:516   ← or this

→ Generate 6 regression tests
→ 4/6 pass, 2 need review
→ Safe to merge. Here's the evidence.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Why this is different from coverage tools
&lt;/h2&gt;

&lt;p&gt;Coverage tells you: "Line 42 was executed during tests."&lt;/p&gt;

&lt;p&gt;Test Shield tells you: "Line 42 changed. These 3 callers depend on it. They have zero tests. You didn't know about 2 of them."&lt;/p&gt;

&lt;p&gt;One is about &lt;strong&gt;what ran&lt;/strong&gt;. The other is about &lt;strong&gt;what will break&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The hard lesson: what it won't do
&lt;/h2&gt;

&lt;p&gt;I learned this the hard way during building: Python's dynamic nature means some callers are invisible to static analysis. &lt;code&gt;getattr()&lt;/code&gt;, &lt;code&gt;importlib&lt;/code&gt;, decorator injection, monkey-patching — AST tracing can't see through those.&lt;/p&gt;

&lt;p&gt;So Test Shield doesn't pretend. Every run ends with a "honesty report":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tracing method:         AST static analysis
Dynamic call risks:     0 detected
Known blind spots:      getattr, importlib, decorators
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it can't trace something, it tells you. No false confidence.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try it yourself
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Standalone — no Claude Code required&lt;/span&gt;
git clone https://github.com/dominicharmon-commits/test-shield.git
&lt;span class="nb"&gt;cd &lt;/span&gt;your-python-project
python ../test-shield/scripts/analyze.py &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# With Claude Code&lt;/span&gt;
/test-shield
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Zero dependencies. Pure Python stdlib. Python 3.10+.&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;[ ] TypeScript/Jest support (v1.2)&lt;/li&gt;
&lt;li&gt;[ ] pytest-cov integration for coverage verification (v1.1)&lt;/li&gt;
&lt;li&gt;[ ] CI/CD integration (v2.0)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;PRs welcome. MIT licensed.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with Claude Code. Tested on a real production codebase. Stars appreciated.&lt;/em&gt; ⭐&lt;/p&gt;

</description>
      <category>python</category>
      <category>claude</category>
      <category>testing</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
