Porting 8-bit Sonic 2 to the TI-84+ CE

grubbycoder
11 min readApr 9, 2024

--

If you’re here from the Cemetech or Sonic Retro threads, then you probably already know the gist of this post. I’ve ported Sonic 2 to the TI-84+ CE.

This post is more about how I ported the game, rather than telling people that I did it. If you just want to try it out on your graphing calculator, click here.

Now that I’ve got that out of the way, let’s begin.

Part 1: Where It All Began

It all started in fall of 2022, when I was watching This Does Not Compute’s video on the history of graphing calculator gaming. Around the 5-minute mark, he offhandedly mentions the kind of processors TI’s graphing calculator line uses. Most of them use the Z80, the 89 and 92 use the M68K, and the Nspire line uses an ARM-based processor.

That really piqued my interest, since I already knew the processors that Sega’s retro game consoles used: The Z80 for the Master System, and the M68K for the Genesis. The calcs have a grayscale screen, but I wanted to know if anyone ever tried porting a Sonic game from the consoles to one of the calcs.

To my surprise, I found out that not only has no one tried to port a Sonic game to the calcs, but that, aside from SonicUP and Sonic TI-Blast, no one has ever made a full-fledged Sonic platformer for TI’s calcs. Aside from the Nspire, which is powerful enough to emulate retro game consoles, so I wasn’t really interested in it.

That made me wonder if I was capable of porting an existing Sonic disassembly to a TI graphing calculator. But first, I was going to have to single out one game specifically.

Part 2: Choosing the Game

First off, I had to choose the specific calcs I would port the Sonic game to. It wasn’t that hard at all. The entire 83+ line, sans the newest ones, the 84+ CSE and CE, has a 96x64 grayscale display. The M68K-based ones have a 160x140 one. The Sega console with the smallest resolution, the Game Gear, has a 160x144 color display. The Genesis, the closest equivalent to the TI-68K line, has at least a 320x224 resolution.

That means that we’re stuck with the CSE and CE. But wait! The CSE is notoriously slow, due to using the exact same Z80 processor found in all its predecessors for a 320x240 16bpp display! Guess we’re stuck with the CE.

So the TI-84+ CE has a 48Mhz eZ80 processor (basically a 24-bit Z80) with 256 KB of RAM and a 320x240 display. That might sound impressive, but unlike the Sega systems, the CE has:

  1. No graphics hardware of any kind.
  2. Wait states that cripple the effective clock speed to about 12–20Mhz.
  3. A file format that caps the size of programs to 64KB, which is way too small to hold any Sonic game without splitting them into multiple files.

All of these factors mean that porting any Genesis game would be a massive pain in the butt. If only there was a Sega console that uses the same architecture as the CE.

Oh wait, there is one: The Sega Master System. With a 3.5Mhz Z80 processor, a graphics processor known as the SEGA-VDP, 8 KB of RAM, and 16 KB of VRAM, it seemed like it was the perfect fit for my little project.

Now, we need to find a Sonic game that we can easily modify. Thanks to the people on Sonic Retro, there are full-fledged split disassemblies for several Sonic games.

Unfortunately, the only Master System Sonic game they’ve gotten to is 8-bit Sonic 2, a neat little game from 1992 that actually beat its Genesis counterpart to market by about a month.

With a target platform and game to port, I got to work modifying the game to run on the 84+ CE.

Part 3: Switching Assemblers

The 8-bit Sonic 2 disassembly targets WLA-DX, a general-purpose assembler for several retro architectures. That’s great for what Sonic Retro needs, but it doesn’t support the eZ80. That meant I had to switch assemblers.

I chose SPASM-ng, a Z80/eZ80 assembler tailor-made for TI calcs. That meant that I didn’t have to use any fancy stuff to turn the binary into a TI-84+ CE program or data file.

It also had a bunch of directives that were really similar to what WLA-DX had to offer.

Here’s what a little assembly program targeting WLA-DX would look like when targeting SPASM-ng:

;WLA-DX example
.include "defines.asm"

KillTails:
ld a, ~$00
dec l
jr nz, ++
inc a
jr +
++: add a, l
ld a, l
ld a, $20
add a, h
ld h, a
+: ret

;SPASM-ng example
#include "defines.asm"

KillTails:
ld a, $FF
dec l
jr nz, +_
inc a
jr ++_
_: add a, l
ld a, l
ld a, $20
add a, h
ld h, a
_: ret

As you can see, while some things, like local labels and includes, are shared between both assemblers, while stuff like the negate macro (~) isn’t.

Now let’s take that 11-line example and apply what I did to the 20,000+ lines of code that make up the disassembly. Sounds like a lot of work, right?

That ended up taking a really long time. But once it was done, I could finally start hooking up stuff like interrupts and palettes to their 84+ CE counterparts.

Part 4: General Hardware Differences

At this point, the game could assemble into a 32KB 8XP file. It crashed, of course, but that was because of one massive problem:

The TI-84+ CE is intentionally wired to reset on any I/O access.

The Master System has quite a few things wired to I/O ports, like inputs, interrupts, palettes, VRAM, and audio. The 84+ CE lacks speakers of any kind, so I just deleted any code that had that last part.

Luckily, TI’s not that stupid, so they added memory-mapped I/O to the CE. This includes all the stuff I mentioned, so I could just change all the I/O instructions in the disassembly to memory-mapped ones. All I have to do is add some extra routines to make everything look the same.

Palettes

First off, let’s start with the palettes. The Master System has a 6-bit color palette, with 32 of those 64 colors available at a time.

The Master System palette. Palette sourced from SMS Power!

By default, the TI-84+ CE doesn’t have a palette at all, but configuring the LCD manually allows you to access a mode with a 16-bit color palette, with 256 of those 65536 colors available at a time. That’s leaps and bounds beyond the Genesis, so all I needed to do was add a data table for each color in the Master System’s palette.

But that’s not what I did initially. See, I misread the documentation on WikiTI and thought that this palette was the only usable one in 8-bit mode:

The GraphX palette. Palette sourced from the 84+ CE C toolchain.

Long story short, I only realized my mistake after I already converted every palette in the game to work on the above one. Oh well…

Controller Input

Next up, we’ve got inputs, like button presses, the pause button, and the reset button.

The Master System stores all its inputs on one 16-bit bitmask. We can ignore one of the bytes, since it’s just used for two-player games, which 8-bit Sonic 2 isn’t. The pause button isn’t a part of said bitmask, because it instead triggers a non-maskable interrupt.

The 84+ CE has more than 8 buttons, so it has a 56-bit bitmask. The ones most people use for playing games are the arrow keys, 2nd, and Alpha, so that’s what I configured the D-Pad and buttons 1/2 to. Since the pause button works differently than all the other ones, I had to add some extra stuff to the input handler to get it working.

ROM Banking

The Master System has 64KB address space, and 8-bit Sonic 2 is 512KB in size. What’s up with that?

That’s where ROM banking comes in handy. There’s a special chip, called the mapper, in the cartridge that maps portions of the ROM to the CPU’s memory map. Each portion of the ROM is called a bank, hence the name, and 8-bit Sonic 2 has 31 of them, excluding bank 0, which is loaded at all times.

Banks 2 and 3 are sound-related, so I just deleted them, leaving me with 29 different banks that are:

  1. Supposed to occupy the same space in memory
  2. Too numerous to fit into RAM at once or bundle with bank 0
  3. In variable locations in flash memory, leaving me unable to just jump to the code without loading it somewhere first

That meant that the only way to actually get them working was to use TI-OS’ data management routines to locate the banks in flash memory and copy them into the same space in RAM each time they’re needed.

Needless to say, this was slow. Real slow. And as much as I’d like to say that I found a complete solution, I didn’t.

The best I could do was load the most important stuff stored in the banks, like collision data, into the 16-bit ranges $E000-$FFFF, which was mirrored memory on the Master System, but is completely usable RAM on the CE.

Miscellaneous VDP Features

Last up, we’ve got some VDP stuff, like interrupts and accessing VRAM.

Interrupts are simple.

On the Master System, the VDP triggers an interrupt on the CPU every time its done drawing to the TV. That’s 60 times a second.

On the 84+ CE, the LCD can be configured to trigger an interrupt every time it’s done drawing a frame. That’s 60 times a second.

For accessing VRAM, on the 84+ CE is simple. VRAM is just normal RAM. On the Master System, it’s a bit more complex. Two bytes get sent through I/O to the VDP. Those bytes are used as an address, and you can send data, byte by byte, through I/O to VRAM.

I basically replaced all OUTs with LDIRs, since every VRAM access in the game was for transferring large chunks of data.

With all that done, the game was running perfectly!

“Footage” of the intro at this point.

OK, that was a lie. Remember how I said that the 84+ CE has no graphics hardware of any kind? Well that means that there’s no rendering taking place at all. All that’s showing up is the intro’s background color, which is handled by the palettes.

That means that there was only one thing left to finish this project: building a renderer!

Part 4: Making the Renderer

Let’s start off with the ways the Master System and CE render a frame.

On the CE, all you get is an 8bpp framebuffer that takes up 75 KB. Take it or leave it. On the Master System, you have two rendered layers, the background (or Screen Map) and the Sprite Attribute Table (SAT).

The Screen Map has 896 16-bit bitmasks that each make a grid of tiles on the screen. This bitmask has 9 bits for selecting which tile to draw and 1 bit each for horizontal flipping, vertical flipping, which palette to use, and whether or not to draw it over the sprites.

Two VDP registers are used for scrolling. They are the only VDP registers the game actively uses, so I can make assumptions about the VDP’s configuration without facing any major bugs.

The Master System has 64 sprites, with the SAT consisting of a 256-byte block, with three bytes being dedicated to each sprite’s X position, Y position, and selected tile. The Y positions take up the first 64 bytes, with the X position and selected tile making up a sort of checkerboard pattern for the latter 128 bytes.

The first layer I wrote a renderer for was the SAT, since that one’s relatively easy to reimplement. Since the eZ80 added a new instruction for 16-bit multiplying (MLT), I could calculate stuff like the sprite’s position and where in VRAM the tile is a lot faster.

Here’s what the game looked like, now that SAT rendering was done.

With that, it was time for building a renderer for the Screen Map. Given that it’s a whopping 896 tiles, I couldn’t just convert the tiles on the fly like with the sprites, and thanks to the scrolling registers, I couldn’t just load it all onto the screen. At least, not the raw image.

My solution was to:

  1. Allocate a 56KB framebuffer in the CE’s VRAM to store the raw 8bpp-converted Screen Map, allowing proper implementation of tilemap scrolling.
  2. Store a cache for converted background tiles in normal RAM, which would cut down the cycles used for drawing them to the framebuffer to an eighth of what it was before.

That allowed for some really fast Screen Map rendering, which you can see down here:

I also added the Spin Dash. Thanks, pixelcat!

Part 5: The Final Few Features

With every major feature of the Master System (barring priority tiles) being re-implemented, the port was essentially complete! There were a few bugs to iron out, thanks to some mistakes made when I had to manually edit about a thousand local labels.

I also added the SEGA splash screen. That wasn’t actually in the original game. The SEGA logo is instead provided by the Master System’s BIOS.

I’d say it’s iconic, but I’m not an 80's kid.

I actually built the splash screen from scratch, using the same tools in the CE C Toolchain I mentioned earlier. It’s not the same image used on the Master System/Game Gear though. Instead, it’s the one from Sonic 3.

With all that, I could truly say I was the first person to get Sonic running on the TI-84+ line. It’s not perfect, but if you really, really want to get the full Sonic experience on a graphing calculator, just get an Nspire.

--

--