Blaze Re-Redux
I’ve been somewhat busy working on the next module for Ataru over the past few weeks, so I decided to take a break. I have been writing inordinate amounts of Z80 assembly language, and a crazy thought possessed me: what if I picked up Blaze’s train wreck of a source code, and tried improving it? The challenge: it should run on OCS Amigas.
I downloaded the source code from GitHub and started tinkering. First, I set up VS Code to build the code using vasm and run it in WinUAE; a few Python scripts later, and Blaze was up and running. Next came trying to understand what the hell the code was doing. It was difficult getting into the twisted mind of teenage me, but eventually, slowly but surely, I got there.
50 Hz Scrolling
The first thing I wanted to patch out was that horrendous 25 Hz scrolling. Blaze runs at around 25 frames per second. For buttery-smooth, Sonic-like scrolling, it would need to run at a sustained 50 frames per second. The easiest solution would be installing a faster video card or CPU – but we don’t have that option. I had a couple of ideas, but after some back-of-the-envelope calculations, I decided on the only one that would give me the desired result: asynchronous scrolling. The idea is simple: simulation is decoupled from visualisation.
The Amiga hardware has a horizontal scroll register that we can tinker with, which literally shifts the viewport to the right up to fifteen pixels, at a granularity of one pixel. But how do we set this register fast enough to get 50 Hz scrolling? This is where we decouple scrolling from drawing. I set up an Interrupt Service Routine (ISR) to change the value of this register at every vertical blank (the period when CRT beam finishes drawing the screen and resets to the next frame). VBLs occur at a frequency of 50 Hz on PAL systems, so it’s fast enough for what we need. The game tells the ISR what the target position of the camera should be, and the ISR moves to the target smoothly, giving us buttery smooth horizontal scrolling.
But wait! Blaze also has vertical scrolling – how do we fix that? Same approach. However, instead of changing a horizontal scrolling register, we simply instruct the system to start fetching screen data from a particular memory location (top of the screen), which is again set in the ISR.

This solves a problem, but creates another. Blaze is drawn as a blitter object or “bob” for short. When the screen scrolls, Blaze moves with it, snapping between his predicted and current positions. It’s like he’s had too much coffee.
Easy solution: we turn Blaze into a hardware sprite. He sits under the moon in the background, so it should be fine, or so I thought. However, it soon dawned on me that the game uses a masking system to hide the player behind objects – in some cases partially so, like waterfalls or windows. A sprite can only appear either behind or in front of a playfield, not sandwiched between its elements.
The solution I chose to go with is, in my humble opinion, quite elegant: on each game frame I regenerate the player sprite from the bob data and apply the block foreground mask manually using the CPU. The advantage here is that I don’t need to blit the masked foreground tiles or even Blaze for that matter. The result is surprisingly good. Pat on the back. The disadvantage is that we lose freebees like Blaze reflected in the water below.

While I was meddling with sprites, I had another idea – what if I wrote a script to slice background sprites and generate sprite data for a more ‘impressive’ distant parallax? This turned out to be another successful detour, as it worked nicely.
But back to Blaze as a hardware sprite. When drawn as a bob, Blaze used colours from the full 32-colour palette. Now, as a sprite, he is limited to 15 colours and a transparent background. I wrote some scripts to convert assets like sprite atlases and tile sheets from RAW to PNG and back. This way I could load everything into Aseprite and edit it there. While I was at it, I started tweaking some of the tiles – although whether that was for the better is debatable.

Dispatch Tables to the Rescue
The platform handling logic in Blaze, which basically tells the game what to do when the player interacts with a surface, is a scarlet letter I will carry on my back for eternity. It worked using two sets of cascaded conditions. Each tile (or square block of graphics) is logically assigned a number from 0 to 255. When Blaze overlaps one such tile, the game first asks which decade the tile belongs to (0-9, 10-19, 20-29, etc.).
Let’s say the tile index is 24. The game would identify the third decade (20-29) jumps to the logic for tile 20, and tests for a match. If no match is found, tile 21 is tested, then 22, and so on until it reaches tile 24. In the worst case, there are around 34 checks per tile.
Too little, too late – but I replaced this mess with a tile dispatch table. Basically, we have a base address in memory that contains the address of the first tile logic handler, followed by the second, third, and so on. When we have a tile index, we simply compute
…to find the handler address and jump to it. Done.
MULUand DIVU Be Gone
So unsigned multiplication and division (mulu and divu) are deceptive instructions. The former may take up to ~80 cycles, and the latter ~150 cycles in practice. Younger me thought it fine to multiply or divide by powers of two using these instructions rather than using logical shift operations (or logical shifts plus additions). I must have replaced around 60 such instructions, which should have led to a considerable improvement, given that some of these instructions were being executed in tight loops.
This emboldened me to make the viewport wider and slightly taller than the original. Asynchronous scrolling still held up like a champ, so I’ll chalk that as another win.
Tools of the Trade
I’ve carried out many other optimisations, such as using lookup tables to eliminate multiplications and updating the HUD only when it changes. However, there was one sneaky detail that flew past my radar.
The maximum number of enemies in a level is 50. To avoid processing all enemies at once, I used a, uhm, manual spatial partitioning technique. Basically, I had reserved ten special tiles (not visual tiles, but logical ones that guide game behaviour) to select an enemy bank to process. Each bank consists of five enemies. Add one and one together, and the maximum number of enemies on screen is five – which is quite austere.
Worse, when editing levels, you have to be really careful where to place enemies and selectors – it’s easy to have enemies disappear on the player if a selector triggers by unintentionally.
So I set out to fix this by introducing more aggressive enemy culling strategies that don’t require placing special tiles. I wanted to test the changes, so I started writing a small tile viewer for Blaze levels. What begin as a small test tool, ended up as a fully fledged level editor that lets me edit levels and play them at the press of a key.

So where does that leave us?
There are clearly some bugs that I’ve introduced with my changes – nothing catastrophic, but still in need of fixing. I also need to do some palette remapping, as is evident from the screenshots, and the tile atlas graphical ‘improvements’ are still incomplete. However, I’ve had a lot of fun improving the code, and I’m sure there’s still a lot more to be done. I essentially see three ways forwards:
- I lose interest
- I polish the tools and source code and release them
- I continue working on the game, adding new levels whenever I feel like it
I’m slightly ashamed to admit that I couldn’t test on any of my Amigas because I don’t have a bloody floppy disk, nor a sane way of transfering ADFs to them. Here’s a video of the 50 Hz version running in WinUAE:
However, I couldn’t feel fulfilled until I ran the game on something connected to a CRT, so the closest thing I could get my hands on was a MiSTer PI and a Hitachi CRT TV that I’m planning to use in an arcade cabinet build:
I must admit that seeing Blaze run at 50 Hz on a CRT, with full-screen animated backgrounds, 32-colour graphics, and parallax, gives me warm fuzzy feelings.
It would be great if someone were to try it on an OCS Amiga 500 with a 1 MB Fast RAM expansion. You can download the ADF below. If you try it out on real hardware, please let me know!

Leave a Reply