2026-08-07

cybrvillain:~$ cat from_c64_memory_to_game_jam_or_bust.plan
From C64 Memory to Game Jam or Bust

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.
Salvage Run
cybrvillain:~$ cat 1_22_mb.plan
1.22 MB

It's 9:15 Friday morning, officially the start of my weekend, and I've already been coding for two hours.

Today is basically perfect. VantaQueen is out doing her Uber Eats day, I don't have a doctor's appointment, there aren't any errands waiting for me, and I have absolutely nowhere I need to be. She also apparently decided that if I'm going to spend the day buried in code, I'm going to be properly supplied. My every-other-week shipment of Jolt showed up, there are two twelve-packs of real-sugar Pepsi in the house, she made me a big-ass pot of chili mac, and there's an entire platter of duplex cream cookies sitting here. Similar to Oreos, except better. I am prepared for damn near anything.

And today that means finishing Salvage Run.

Hypnotitron sent over all of the asteroid graphics last night and absolutely killed it. I asked him for asteroid graphics and, as usual, he went above and beyond what I actually asked for. I've been implementing them this morning, and once they're in I'm down to the final testing pass.

Then it's done.

That's still weird to type.

A month ago I was refreshing my C knowledge, reading Win32 documentation, remembering how much modern engines do for you, and learning a whole pile of things I didn't know before I started this. There have been plenty of nights where I've done more reading than coding, plenty of stupid bugs, and more than a few moments where I stared at something wondering why the hell I decided writing a game with C, Win32 and GDI was a good idea.

Now the executable is sitting at 1.22 MB and the game is basically complete.

Not "I'll finish it someday." Not another prototype sitting in a directory. Finished.

I'm going to spend the rest of today getting it across that line, then probably switch over and put some time into GhostMind because apparently my idea of starting the weekend is just changing which codebase I'm staring at.

Tomorrow is Vanta's Day, so the computer stays off from 9am to 9pm. Sunday we're heading to the storage unit to start getting rid of shit so we can hopefully get that monthly bill out of the budget.

But that's Sunday.

Right now I've got Jolt, chili mac, a fresh pile of asteroids, and 1.22 MB of game sitting in front of me.

Time to finish the damn thing.


2026-08-06

cybrvillain:~$ cat ghostmind.plan
GhostMind

It's about 12:am Thursday morning.

Today is my Friday, which means week two at the new job is basically done. Hit this week's sales goal before Wednesday was over, so everything after that has just been extra. I'm not going to complain about getting paid to keep selling after the pressure's already off.

Salvage Run is sitting in a really good place. I'm still waiting on the last graphics from Hypnotitron, but the game itself is done. One megabyte. Exactly. Under the 1.44 MB limit with room to spare.

Will it win?

No clue.

I've never entered a game jam before, so I honestly don't know what to expect. I'm just happy I finished it. A year ago I couldn't have imagined writing a game like this in C.

Went to Buffalo Wild Wings tonight with VantaQueen and Pixel Mancer. First time eating there. It was good. I'd eat there again. Not Top 10, but definitely not a disappointment.

The funny thing is I barely thought about Salvage Run all day. My mind kept drifting back to GhostMind.

KhaoticRage and I have kicked this idea around off and on: a game AI that adapts to the player. If he talks to the Inn Keeper, he should get a different response than I do. No scripted responses pretending to be intelligence. The interaction changes depending on who's standing there. The world should react differently depending on who's playing it. A living, breathing world.

Because of that, GhostMind—not Terminal Ghost—has become more interesting.

The more I think about it, the more I realize GhostMind isn't really part of Terminal Ghost anymore. It's becoming something bigger. I don't want GhostMind to define one game. I want it to define Lonely Ranch Games.

When people think of id Software, they think Doom. Quake. Wolfenstein. When people think of Lonely Ranch Games, I want them thinking about GhostMind.


2026-08-04

cybrvillain:~$ cat little_things.plan
Little Things

Finished above goal at work again today.

I'm not going to pretend I hate that feeling. Selling all day and then coming home knowing I don't have to stress about whether I pulled my weight is a hell of a lot more relaxing than the alternative.

Came home to a 16-ounce steak and corn on the cob courtesy of VantaQueen, ate dinner, then sat down for what I figured would be a quick coding session.

Yeah... "quick."

Tonight wasn't about making the booster work. It already worked fine. Hypnotitron saw it almost immediately and said exactly what I was thinking. What bugged both of us was seeing the word BOOSTER on the HUD. It worked, but it felt lazy.

So I ripped it out and replaced it with an actual gauge.

That's the difference.

Nobody's ever going to point at that gauge and say, "That's why I like this game." They'll probably never notice it. But a hundred little decisions like that are why games feel finished.

The game feels a little more like something that would've been running on a Commodore 64 in 1983 and a little less like a programmer slapped a label on the screen and called it good.

I also got Hypnotitron's UFO artwork integrated tonight.

Every placeholder that disappears makes this thing look a little less like a test harness and a little more like a game.

It's midnight. SR-029 has finished compiling while I type this, and assuming the code doesn't decide to humble me one more time before bed...

...I'm done for the night.


2026-08-02

cybrvillain:~$ cat every_kilobyte_counts.plan
Every Kilobyte Counts

It's now almost 2am.

Probably a good sign it's time to call it a night.

Spent part of my weekend convincing Microsoft Defender my game isn't malware.

Hypnotitron couldn't test the alpha because Defender kept flagging it. Way too much time disappeared into troubleshooting before it finally decided to stop being... Defender.

Switched up the collectible salvage graphics. Doesn't look like a C64 game anymore. Then I added a background image that looked fantastic... and immediately realized it was eating way too much of my 1.44MB budget. So now I'm trying to figure out a procedurally generated parallax background and claw some of those bytes back. Asteroid and UFO graphics are next.

Took a break long enough to see *Spider-Man: Brand New Day*. Spider-Man and Punisher fighting together was worth the price of admission by itself. My only complaints are it felt a little too long and there wasn't nearly enough Florence Pugh. Sorry, not sorry, VantaQueen.

I'm feeling pretty confident about Salvage Run.

Maybe it doesn't win the 1.44MB Challenge. But for the first time, I'm looking at the game and thinking, "Yeah... KhaoticRage might actually play this."

I might even dust off OBS and stream it once it's finished. It's been a while, and this feels like a game worth sharing.


2026-08-01

cybrvillain:~$ cat not_carmack.plan
Not Carmack

First week at new job and almost doubled my sales goal. Target was $2,000. Finished at $3,941.

That wasn't the hard part.

The hard part was spending three hours trying to get Win32 to recognize an Xbox controller.

I got back into coding a year ago after my brother dared me to make a game. Godot spoiled me. I'd forgotten what programming on the C64 was really like: line after line of code with nothing holding your hand. The 1.44MB Challenge has me writing C with the Win32 API and GDI software rendering.

I'm not Carmack.

I wish I was.

I've done a lot of stupid shit in my life, and I try not to live with regrets. I did what I did, and I accept the consequences. But every once in a while I kick myself for not spending more time with the C64 instead of being a dumbass kid. Maybe if I'd made one or two different choices, I'd understand this stuff well enough to sit down, crack open a Jolt, and watch the code pour out onto the screen.

Instead I'm reading MSDN, changing one line, compiling, swearing, changing another line, compiling again, and eventually stumbling into the answer.

Then again... that's probably how Carmack learned it, too.