ManxomeBromide's Retro Releases and Ramblings

Retro Stuff I've Written

I mess around a lot with old systems, and sometimes I'll put something together that's worth actually uploading more broadly. Generally they'll have some kind of story behind them, either why I made them, what I learned along the way, or at least something kind of funny. I'm not exactly an indie game dev over here.

Some of this is also just Fun Stories About Weird Retro Stuff, because while I have been known to ramble on Discord about things that's a terrible place to keep stories when you could instead point them somewhere more permanent. If this gets too inconvenient I might have to break it out into separate pages, but if nothing else I'll keep this as an index eventually.

Nerm, the Worm of Bemer

A wildly over-engineered remake of a BASIC game for the TI-99/4A as a 100% assembly language game for the ZX Spectrum

screenshot

Back in 2024 or so I found some good emulators for the very, very weird old computer the TI-99/4A. (JS99'er is worth a visit if you just want to mess around with it and some old games and new demos for the thing. Also, not kidding about the very weird part: it's got a 16-bit CPU in 1979 but it also has 256 bytes of memory in its stock configuration. BASIC and stuff would just store the program in parts of its 16KB of VRAM that the screen wasn't, like, looking at at the moment.) So I did what any normal person would do: I typed in a little Snake game program out of a book so I could also play that, and then posted about it in the local Discord gaming channels like it was absolutely the newest of hotness.

As a Snake variant is is kind of interesting, even though it's fundamentally just the core game aspect! There aren't many targets per screen and you grow a lot—like, a lot a lot—whenever you eat anything, and beating levels isn't automatic because you have to actually leave the stage.

Playing it in BASIC was kind of a bummer though, because the interpreter actually struggled even to run it at the 2 or 3 FPS that the game demanded. I figured I should port it into assembly language so it could run as fast as it wanted. I also figured I should use a different machine since I wasn't keen on dealing with that whole "256 bytes of RAM" thing. I settled on the ZX Spectrum; it's a simple enough machine, I'd been meaning to get better at the Z80 CPU, and lots of people seem to like it. I didn't actually finish the port until May of 2025, and then just sort of sat on it because there wasn't anything to do with it. I've cleaned up a few extra parts for public release (instructions, credits, letting you use an AY-3 sound chip even if you don't have a Spectrum 128) and now here it is.

I comically overengineered this thing. Two sound drivers, four different control schemes including multiple completely incompatible joystick types, automatically guessing at your hardware configurations, and probably more things I've forgotten since I've written it.

Download the TZX tape image here. NeoCities doesn't want us distributing software through their site so I'm bouncing it through Google Drive. I recommend FUSE for Spectrum emulation; the game should work on 16K, 48K, or 128K Spectrums and will use the AY-3 sound chip on a 128K or compatible system.

Zanac Anti-Flash Hack

Zanac was my favorite shmup on the NES, back in the day. I made a small mod for it that disables the flashing light effects when enemy bases are destroyed or when you use the Plasma Flash weapon. The patch is available on RHDI. Alas, there's still enough independent moving objects on the screen that even with this is still probably not a great game to show off to anyone with photosensitivity issues unless they're very sure of what their triggers are. This hack will also break tool-assisted speedruns because the game timing changes.

The story behind breaking TASes is actually kind of fun, and the star of the story is actually a quirk of the NES's video architecture.

Most consoles generally have you doing all your graphics changes during the vertical blanking period so you don't get glitching or tearing, right? But the NES and SNES are significantly more hardcore about this: they've got a dedicated video memory area and you can only talk to it through an I/O port, but that circuitry is shared with the logic that actually fetches the graphics to send out to the TV so if you try to mess with VRAM in any way during the frame you're very likely to just corrupt the display, possibly in persistent ways (writes going to the wrong places, etc.)

Zanac had a problem: when you blow up an enemy base, the game wants to replace the map graphics on the screen with ruins for the tiles that were originally base stuff, and it wanted a general solution to doing it. That's a real problem if you're not sure how big the base is, and there's a pretty sharp limit to how much you can write per frame ("128 bytes if they're mostly in a line, and you're really pushing it there" is the rule of thumb.) Many bases were way bigger than that.

But!

The problem isn't being out of the blanking period. It's competing with the renderer. So Zanac flashes the screen by disabling graphics entirely and then it gets to mess with VRAM as much as it wants and has no time constraints at all. While it's doing that it sets the background to white so it can pretend that this is all just some humongous explosion.

So that's awesome. But it's also a bummer for me, the hacker, because I was hoping this would be a quick fix but now I'm going to have to solve these timing-constraint problems, right? It turns out, no: this leads us directly to something else that is awesome.

Zanac's developers were clearly very concerned about VRAM corruption and so they built an incredibly robust VRAM communication system, so it turns out that my hack really did only need turn off the screen blanking and everything is mostly fine; it realizes that it's pushing its luck and blocks the game logic until it can render what it has. This means blowing up a base may take more frames than it did, and that is why the TASes will desync.

I can't really be disappointed, honestly; this is proof that Compile was pushing the NES absolutely as hard as it could go here.

How the SMZ3 Randomizer Works

Have you heard of the Super Metroid and A Link to the Past Crossover Randomizer? Most randomizers take a single game and juggle the stuff within it so that it is still basically the same game in the same engine but with items and enemies in different places. This one takes two games and produces a single program that cross-cuts back and forth between both. This is not some emulator-level intervention like we see with projects like the Archipelago cross-game system; it's actually a single virtual SNES cartridge that has both games and keeps them both functioning simultaneously, and it will run just fine on actual hardware. This is a pretty mind-boggling thing to do to games of this era, and so one fine day I decided to dig into how this actually worked.

The short answer that I first ran into is that this works because A Link to the Past (henceforth LttP) and Super Metroid (henceforth SM) just didn't have any of their actual program ROM overlap at any point. That's real weird on the face of it, but turns out to be the least miraculous part of this. It falls out of how the SNES sets up its memory.

The SNES has 16MB of address space, and it's divided into 256 "banks" of 64KB each. The "top" 128 banks (80-FF) can run 33% faster than the "bottom" ones. The idea here was that more expensive (or just more modern) ROM chips could respond faster and thus could get more power out of the SNES, but if you didn't need it, you could make cheaper carts. If your game is 4MB or less (which is most of them, including both SM and LttP), there are two copies of the ROM in memory, one in the fast place, one in the slow place. So the first part of this is solved by using a virtual cartridge that matches the really big games like Chrono Trigger, which hold nearly 8MB of stuff, and just put SM in the fast part and LttP in the slow part. (LttP is only 1MB, so the "nearly" 8MB doesn't become a problem. SM is basically All Four Megs.)

So that "astounding coincidence" just boils down to "SM was manufactured later, when fast ROM was cheaper." That is not the terrifying sorcery.

What is the terrifying sorcery is that there were two ways to set up your cartridge if you were 4MB or less, which I will call "the Super Metroid Way" and "the Final Fantasy 6 Way". In the SM way, your 4MB of code and data gets divided up 32KB at a time and takes up half of each of the 64KB banks (4MB / 32KB = 128 banks required, and it's mirrored into the fast/slow parts). In the FF6 way, your code and data gets divided up 64KB at a time and takes up 64 banks, with half of each of those banks copied into the other 64. Some banks can only take 32KB of ROM, and they use the other 32KB for stuff like RAM and I/O. That's pretty important, so even in FF6 mode you're usually not in your "real" banks. It's a kind of weird setup, but for the Super Metroid setup it's really convenient, especially if you're 2MB or less like, oh, LttP.

Which brings us to the part that is completely wild. Chrono Trigger style carts work like FF6, not like SM or LttP. Now, Link to the Past can be made to work in this setup almost completely transparently anyway; you just use the 32KB parts of each 64KB bank that LttP actually did and either ignore the rest or use it for the actual new rando logic/data. (You have a free megabyte to use here! This is why an SMZ3 cart is exactly 1MB more than the 5MB that are SM and LttP summed up.)

But Super Metroid has to be restructured out of the SM memory layout into the FF6 one. Here's where the miracle happens. It turns out that every place where this would matter—every bit of SM that isn't in that first 2MB where you can fake SM-like behavior in a Chrono Trigger cart—is data, and not only data but compressed data. So they took all that data, shifted its location by 32KB so it's taking up the rest of the now-64KB bank that was its original bank, and were able to make all this work simply by modifying the data decompression routines to shift the source address by 32KB if it detects that it was in the top 2MB originally. So the actual cartridge data in SM is shuffled together like a deck of playing cards, and they get it to work anyway with only like 300 instructions worth of modification and extension.

This is a completely unreasonable effect for so small an extension. There is indeed some incredible serendipity that makes this possible after all... we just had to dig into it a little to find it.