TL;DR
About a month ago I decided to enter the 1.44 MB Floppy Challenge with a simple goal: rebuild a little space game I wrote on my Commodore 64 over 40 years ago.
The catch? The entire game had to fit on a single 1.44 MB floppy disk with zero compression. That meant no Unity, no Godot, no Unreal—just C, the Win32 API, GDI, and a lot of late nights.
One month later, Salvage Run has grown into a complete arcade game with procedural backgrounds, software-rendered visual effects, generated planets, 56 asteroid sprites, multiple UFO classes, controller support, particles, embedded music and sound effects, persistent high scores, hidden developer tools, and a surprising amount of engineering squeezed into just 1,252,352 bytes.
This post is the story of how a forgotten Commodore 64 game became Salvage Run 1.0, what I learned along the way, and why building this project reminded me why I fell in love with programming in the first place.
The full devlog is long, and I don't blame you if you don't read the whole thing, but if you skip it, please scroll to the bottom and read the last paragraph—because none of this would have been possible without the people who supported me while I built it.
---
I think Salvage Run is done.
Actually done. Version 1.0 done.
That is a little strange to type, because about a month ago this game really only existed as a memory of something I wrote on my Commodore 64 in the early 1980s. I had decided to enter Lonely Ranch Games in the 2P Game Arcade 1.44 MB Floppy Challenge, and after kicking around a bunch of ideas I kept coming back to that old game. I didn't want to recreate it exactly. I wanted to see what that idea would become if I rebuilt it now, using everything I've learned in the forty-plus years since I originally wrote it.
The contest has one particularly evil rule: the entire finished game has to fit inside the 1,474,560 bytes of a standard 3.5-inch floppy disk, with no compression. That ruled out Unity, Godot, Unreal and pretty much every modern game-engine approach immediately. So I went back to where I started and built the whole thing from scratch in C using the Win32 API, GDI, XInput and WinMM.
The first version wasn't really a game at all. It was a Windows window, a 32-bit DIB back buffer, a fixed-step 60 Hz game loop driven by QueryPerformanceCounter, keyboard input and a bouncing square. BUILD 0001 was 105,472 bytes. That gave me the foundation, and from there I started adding systems one piece at a time instead of designing some giant engine that Salvage Run would never need.
The player ship came next, followed by horizontal movement, firing and the salvage bay. That bay became the mechanic the entire game grew around. Open it and you can collect salvage, but you can't fire. Close it and your weapons come back, but incoming salvage is now just another thing that can kill you. The rule is simple enough to understand in about five seconds, but when there are asteroids drifting through your lane and a UFO taking shots at you, deciding when to open that bay becomes the game.
At first the salvage was basically a falling rectangle. Then there were several of them, which meant I needed a real entity system, fixed object pools and some kind of randomization. Asteroids followed, then UFOs, projectiles, lives, scoring, level progression, collision handling, game states, controller support, a HUD, title screen, Game Over screen and persistent high scores. Somewhere in there the little C64 experiment crossed the line into being an actual arcade game.
One design decision I kept coming back to was that different things in space should move like what they actually are. Early salvage and asteroids periodically changed direction, which technically produced random movement but visually made them look like they were steering. That was wrong. Asteroids aren't supposed to suddenly decide they have somewhere else to be. So they became momentum-driven objects whose paths only change when something physically happens to them. UFOs are piloted, so they kept steering behavior and eventually grew into three distinct classes with different movement, firing cadence, aim, projectile size and attitude toward the player.
That distinction is still in the final game. Natural objects have momentum. Intelligent objects steer.
The artwork evolved the same way. For quite a while Salvage Run was an unusually sophisticated collection of colored bitmasks, which was completely intentional because every byte matters in this contest. Eventually the programmer art started getting replaced. The player ship moved to full RGBA artwork, salvage got proper sprites, UFOs got proper sprites, and then Hypnotitron decided that apparently eight asteroid graphics weren't enough. He produced an entire asteroid family.
There are now 56 asteroid sprites: 8 large, 16 medium and 32 small. They aren't just randomly interchangeable rocks, either. Each large asteroid belongs to a family and breaks into its own two medium descendants, and each of those mediums breaks into its own two small descendants. That family relationship survives the whole fragmentation chain without adding a bunch of tracking data to every asteroid. The lineage is encoded into the variant number the asteroid already carries.
That is probably one of my favorite examples of how this project developed. The solution isn't complicated because it doesn't need to be. Eight families, two medium branches, two small branches, some arithmetic, done.
The background became another project inside the project. I originally had a full bitmap background embedded in the executable, but it cost roughly 243 KB. In most games that is nothing. In a game limited to 1.44 MB, 243 KB is enormous. Replacing it with a procedural starfield recovered 239,616 bytes by itself.
That started with a few parallax star layers and grew into procedural dust, comets, nebulae and planets. Since this is GDI there are obviously no GPU shaders hiding behind any of it, so I built the effects in software. The nebula uses multiple procedural noise fields with different scales and movement, including a cheap curl-like distortion that keeps the clouds from looking like a texture being dragged across the screen.
The planets became procedural too. Rocky worlds, ice worlds, gas giants, deserts, oceans and volcanic planets can all appear, with generated surface detail, lighting and atmosphere. The expensive part happens once when the planet is created; the result gets baked into a tiny sprite and from that point on the renderer treats it like any other piece of artwork. That's very much the spirit of this whole game: do the expensive thinking once, then keep the runtime simple.
Audio went through the same kind of compromise. Full-quality PCM music would have destroyed the contest budget before the game even started, so the tracks were reduced to 11,025 Hz, 8-bit mono and embedded directly inside the executable. Sound effects use a small waveOut voice pool so they can overlap without interrupting the music. There is no audio middleware and no external asset directory. The final distribution is still one executable.
Throughout all of this I kept measuring the file size instead of guessing. That turned out to matter when the complete asteroid artwork suddenly added almost 200 KB to the executable. Instead of immediately trying to optimize it away, I audited the PE file and accounted for the increase. Of the resource-section growth, 196,992 bytes were exactly the newly embedded asteroid pixel data and the remaining 2,304 bytes were exactly the resource-directory overhead for adding 48 more resources.
Nothing was wrong. The art simply cost what the art cost.
That was an important lesson from this project: optimization is a lot less mysterious when you measure before you panic.
There were plenty of less glamorous problems along the way. At one point Windows Defender started getting suspicious of the executable, which is apparently what happens when you hand Windows a tiny unsigned native program doing lots of low-level things without a familiar engine wrapped around it. I built stripped control executables, compared PE sections and imports, audited the resources, searched the entire project for anything that should legitimately look suspicious and eventually did a complete code review of the gameplay layer.
At the time game.c alone was 7,135 lines. Every game-state transition was traced. Every entity lifecycle. Every fixed pool. Every collision path. Every registry call. Every resource loader.
Nothing malicious. Nothing particularly exotic. Just a homemade Windows game.
I knew I wanted to stream the finished game but of course OBS created another problem. The game rendered perfectly on screen, but Window Capture couldn't see it correctly because I present frames directly using GetDC and BitBlt rather than relying on WM_PAINT. Windows knew the pixels had changed, but capture software didn't necessarily know the window contents had changed. One InvalidateRect after the present fixed it.
Later I spent time chasing UI coordinates that appeared to be wrong by roughly 31 pixels, only to discover that my screenshot tool was including the Windows title bar in what I thought was a client-area capture. The game wasn't wrong. The test was wrong.
I wrote that one down mentally in permanent ink: test the test.
## The Game Changed When People Actually Played It
The original salvage progression system was more complicated than it needed to be. Each level had a finite number of salvage objects, only a percentage were required to pass, and destroyed or missed pieces counted against the total supply. The math worked, but if I need a paragraph to explain how the basic objective works, something has probably gone sideways.
So late in development I replaced it.
Level 1 now asks you to collect 10 pieces of salvage. Level 2 asks for 11. It increases one per level until the target reaches 20. When salvage is destroyed or falls off the bottom, another piece eventually replaces it, so the player can always reach the target as long as they can stay alive.
The old Recovery and Excellent grading tiers disappeared with it. Now you either reach the target or you don't. If you reach it without dying and without destroying any salvage, you get the Perfect Salvage bonus. Missed salvage doesn't disqualify Perfect because I don't want the game encouraging players to make stupid suicide dives after a piece they should obviously let go.
That version is simpler, easier to explain and plays better.
Playtesting also exposed something I had intentionally built into the game much earlier: the random-number generator used a fixed seed. That was incredibly useful while I was debugging because every new game produced a reproducible sequence. Eventually that became a problem because, well, every new game produced a reproducible sequence.
Three new games shouldn't feel like somebody hit Replay.
The xorshift generator itself was fine, so I didn't replace it. I simply changed where a new game's starting seed comes from. Each run now receives fresh entropy while the procedural background keeps its own completely separate random stream. Five clean launches produced five different seeds and five different opening fields while every difficulty rule stayed identical.
Again: fix the actual problem, not the whole system surrounding it.
## The Last Mile
The last few builds weren't about adding another giant feature. They were about getting rid of reasons someone might swear at the game.
I audited the persistent high-score system after finding a suspicious 20,000-point score in the registry. The persistence code was fine. The culprit was almost certainly me: one of the developer shortcuts adds 10,000 points, and two presses followed by a normal Game Over gives the perfectly functional high-score system absolutely no reason to think the score isn't legitimate.
Oops.
The bad value was repaired, and the load/save path was tested end to end. Salvage Run stores exactly one value under the current user's registry: the high score. No save file, launcher, account or configuration database. Just one DWORD.
The title screen and Help screen got their final cleanup. The game stopped calling itself BUILD 0044 and became VERSION 1.0. The start prompt got properly centered and given an old arcade-style blink. The final keyboard controls became A/D for movement, W for boost, Ctrl to fire, E or Space for the salvage bay, P to pause, H for Help, M for music, PgUp/PgDn for SFX volume and Escape to exit.
The mouse cursor now disappears after two seconds of sitting idle over the game and comes back the instant you move it. That sounds like nothing until you're trying to make a PC arcade game look finished and there's a giant white Windows pointer sitting on top of your spaceship.
And because I spent a month living inside this thing, all of the development shortcuts are still there.
They're just hidden.
Pressing ~ opens an undocumented DEBUG screen and unlocks the developer tools. From there I can spawn salvage, asteroids and UFOs, toggle invulnerability, enable slow motion, draw collision boxes, jump levels, manipulate lives, add score and force specific UFO types. F11 shuts the cheat inputs back off.
None of that appears anywhere in the normal UI.
Some traditions are worth keeping.
## Version 1.0
Salvage Run started as me wondering what would happen if I revisited a game I wrote as a kid.
It ended up becoming a native Windows arcade game with a software renderer, procedural backgrounds, generated planets, multiple parallax layers, particles, embedded music and sound effects, keyboard and XInput support, momentum physics, three UFO classes, asteroid-family fragmentation, persistent high scores, randomized runs, a forward booster, 56 asteroid sprites and a hidden developer screen.
All written specifically for this game.
No engine. No middleware. No external runtime assets.
The final executable is 1,252,352 bytes.
The contest limit is 1,474,560 bytes, which means I've still got 222,208 bytes left.
A month ago I would have looked at those remaining bytes and immediately started thinking about what else I could cram into them.
Now I look at them and think they're proof that I can stop.
The game doesn't need another feature just because there's room for one.
I've spent the last month reading, relearning things I hadn't touched in years, learning a bunch of things I didn't know before, breaking systems, rebuilding them, testing them, having other people beat on the game, arguing with Windows, measuring executable sections and rediscovering just how much I enjoy working this close to the machine.
And somewhere underneath all of that is still the little space game I wrote on a Commodore 64 more than forty years ago.
Only now it's the version I couldn't have made then.
Salvage Run 1.0 is done.
I still can't quite believe that. Whether I win the Game Jam or not, I'm proud of Salvage Run. I set out to see if I could take something I made more than forty years ago and turn it into the game I would have wanted to make back then. I did that. Whatever happens from here is just a bonus.
- None of this would have been the same without the people around me. Huge thanks to Hypnotitron, KhaoticRage, and The Pixel Mancer for the countless hours of beta testing, feedback, artwork and helping shape the game into something better than I could have built alone. And to VantaQueen... thank you for believing I could actually pull this off, for putting up with a month of me disappearing into C code, for keeping me fed, for making sure there was always another Jolt Cola within reach, and for reminding me that I could do this even on the days I wasn't so sure myself.