2020-10-19

GBA Entity Component System

A New Game

I’m writing a new game. Technically it’s an old game; it’s something I’ve been meaning to get around to for decades now, even if the plan has evolved over the years and what I’m trying to make now doesn’t have much, if anything, in common with my original ideas.

What I’m working on now is a Metroidvania-like platformer for the GBA. I have a few goals:

  • Like old LucasArts adventures, there should be no way to die. I’ve found that being forced to repeat the same few minutes of a game over and over until I get it right just isn’t interesting for me any more, and I don’t have time for it.

  • It should be possible to save anywhere. Castlevania, Metroid, and even more modern games like Bloodstained that really should know better, devolve into a frantic scramble from one save room to the next so that I can switch off the console and go deal with something more urgent.

  • The game should tell an interesting story.

  • The game should focus on exploration. Exploration should reward the player with new capabilities that allow access to new areas and more pieces of the story.

  • It shouldn’t be too large or complicated. No-one cares about GBA homebrew so there is no point.

I have the basics of story figured out, influenced too much by Greg Bear’s “Hull Zero Three”, Aldiss’ “Non-Stop”, Heinlein’s “Orphans of the Sky”, Galouye’s “Dark Universe”, and the original Star Trek episode “For The World is Hollow and I Have Touched the Sky”. Derivative? Yes. It’s even been done before in videogames. The Sega Genesis/Mega Drive game “Generations Lost” has an intro scene that is almost identical to the opening I’d envisioned for this game. I’ve got some original twists planned, though.

I don’t have a good answer for what the challenge is yet. There’s exploration; that’s good. And there’s a story; great. But what do you do in the game if there’s no way to die, and no monsters? Just wander around a big empty environment finding things to collect and chatting to NPCs? Is that a compelling game? Sounds a lot like Treasure Island Dizzy and that kept me entertained for hours - once I got the invulnerability cheat working - so maybe that’s enough if I throw in a few take-thing-x-to-place-y puzzles. Generations Lost is a more traditional platformer and has enemies and weapons, but that game only managed to keep my attention for a few minutes before I switched it off in annoyance. Is it a “walking simulator”? I’ve never played one, but it sounds like it might be. Perhaps I’ll start with that and see where I get to.

The other big concern is the game’s graphics. It takes me days to laboriously create terrible pixel art. The sub-Amiga-public-domain graphics in Professor Sinister are just about the limit of what I can do. I’m bad at pixel art and I don’t want to practice. Graphics are a chore that I’d rather someone else did.

The ECS Pattern

Coding the game is a different matter entirely. The game is an excuse for a fun experiment with the “entity component system” pattern. Anything that you’d think of as a “game object”, like the player’s character, NPCs, moving platforms, or collectible items, is represented by an “entity”. An entity is essentially just a container for a bunch of different “components”. A component is a struct that holds a set of basic information about some aspect of the entity. For example, a “velocity” component might contain the current x and y speed of the entity, the delta by which to mutate those values, and the maximum values that can be attained. Lastly, a “system” is a stateless function that iterates over all relevant entities (perhaps those with both a “velocity” component and a “position” component, for example), and mutates the state of their components using the components themselves as the inputs.

The ECS pattern is neat because giving game objects behaviors is simply a matter of giving the objects the relevant component(s). Want an entity to be affected by gravity? Add the “gravity” and “velocity” components. Now the gravity system will recognize the entity and mutate its velocity appropriately. It’s far more flexible than the approach I used in my unfinished shoot-em-up Chroma-X, which tried to achieve something similar with protocols/interfaces: a game object had a number of “sockets”, like an “engine” socket, and changing how the object behaved meant crafting a new class that implemented the appropriate interface and could be plugged into the appropriate socket. Want an enemy to move in a sine wave? Plug in an instance of the “sine wave” engine. Adding an additional behavior to a game object with that design meant modifying its class to support the behavior’s interface rather than just appending a component and letting the rest of the system do its thing automatically.

Professor Sinister used object-oriented C. Data was stored in reference-counted opaque types and everything had to be malloced. Dynamic memory management allowed me to create my own implementations of variable-length mutable collections like arrays, sets, hashmaps, strings, etc. This time around I’m going in exactly the opposite direction. There isn’t a single call to malloc anywhere in the code, unless I really want that chunk of memory to live in EWRAM.

Achieving the loosely-coupled ECS panacea (in which an entity can contain 0…n components, each of which has a different size) in C is impossible in any sane way without using malloc() and variably-sized collections, so my solution gives all entities all possible components. Components include an “enabled” flag that systems look for when determining if an entity is relevant or not. It’s wasteful of memory but efficient in terms of processing time, as it eliminates searching through an entity’s components.

(On the subject of “loose coupling”, it’s worth pointing out that the ECS pattern doesn’t achieve it. For example, the jump system is responsible for determining if an entity can jump, and it needs input from numerous components to make that decision: Does the jump component indicate that the entity is already jumping? Does the input component say that the jump button is pressed? Does the duck component indicate that the entity is ducking? Does the environment collision component indicate that the entity is colliding with a tile above it? Systems are dependent on multiple components; those dependencies require that the components are updated in a specific order; and that ordering creates implicit dependencies between systems. Those dependencies can’t be enforced by a compiler, and they’re an impediment to multithreading the game logic.)

Deciding on the boundaries between components is interesting. For example, I have a “duck” component that tracks whether or not an entity is ducking. The component consists of two bools: enabled and ducking. The ducking system checks the state of other components (is the entity jumping, or falling, or climbing?) and, if they are in a good state, sets the ducking bool to match the state of the down button on the d-pad. Meanwhile, running is implemented not as a separate component but as part of the velocity system. If left on the d-pad is held down then the entity will move left; if B and left are held, then the entity will move left more quickly. Should there be a separate component for the run state of the entity? A separate component feels wrong but I’m not entirely sure why, other than the additional overhead it would incur; possibly because it’s a modification of the walk function rather than a separate capability?

Similarly, it’s tricky to decide on which system should update the components. For example, if an entity is flying and grabs hold of a ladder, which system is responsible for setting the state of the flight component from flying to inactive? The climbing system? Or the flying system? Perhaps an appropriate rule is “any system can read a component’s state, but only the main system for that component can mutate that state”. In our example, the flying system mutates the flying component. That rule feels like it will quickly collapse under the weight of too many edge-cases.

Platforming Engine

The platforming engine is coming along nicely. It supports climbable tiles (ladders), solid tiles, tiles with solid edges (any or all of the edges can be solid, so it’s possible to walk into the tile from the left but then hit its solid right edge), tiles with one-way edges (you could jump up through the tile from beneath and then land on its solid top edge), and tiles with a height map. Height maps are expressed as an array of 8 values between 0…8 that specify the height of the tile at a given x co-ordinate. They allow me to implement slopes with arbitrary gradients, bumps, dips, etc. This page was particularly helpful when getting slopes to work:

http://higherorderfun.com/blog/2012/05/20/the-guide-to-implementing-2d-platformers/

Destructible Tiles

Map tiles can be marked as destructible. The player can stand next to destructible tiles and press a button, which results in the tiles being removed from the map. The GBA’s hardware limitations made that feature more complex to implement than you’d expect. Possible approaches I considered were:

  • Mutating the level data (this isn’t desirable because it would require copying the level data from ROM to EWRAM, which is limited to 256K);
  • Storing a dictionary of destroyed tile co-ordinates (this isn’t desirable because looking up the data in the dictionary would not necessarily be fast; each co-ordinate would require a couple of uint32_ts to store; and the maximum size for the dictionary would either need to be the size of the map (see previous problem regarding RAM limits) or some arbitrary smaller limit, which would mean we could unexpectedly run out of space in the dictionary);
  • Create a small ring buffer of destroyed tile co-ordinates. Tiles would be overwritten automatically with newly destroyed co-ordinates when the buffer wraps, which means the oldest tiles would heal themselves like Lode Runner when new tiles were destroyed (not exactly what I wanted, but it would work);
  • Create a bit array in which each bit represents the destroyed state of a tile in the map.

After playing around with the first idea for a while I realised it wasn’t going to work with the way I was representing tiles. Each tile was a complex struct that stored a large amount of metadata. I rewrote that to use something like the flyweight pattern. I maintain a palette of unique tile definitions and represent the map with a second array of indexes into the tile palette. That shaved about 20% off the size of the ROM, which currently only has a couple of mostly-empty test maps.

Even with that change, the bit array option was far more efficient. A single screen map consumes a minimum of 600 bytes if copied to RAM (assuming one byte per tile entry). The equivalent bit array consumes 75 bytes.

Fixed Point

Initially I had everything implemented with integer math, but it became increasingly clear that the engine needed to use fixed-point math instead. The jump was too stiff; gravity was too aggressive; and running was too fast. Making the switch introduced an exciting selection of new problems and bugs, most of which were related to rounding problems. For example, maxX() used to be implemented like this:

return rect.origin.x + rect.size.width - 1;

If I have a rect at point (0, 0) with width and height both equal to 2, maxX() will give me a value of 1. That’s what I’d expect: the maximum X coordinate contained within the rect is 1. I can use the rect functions to step through the columns in the rect like this:

for (int i = minX(rect); i <= maxX(rect); ++i)

That falls apart with fractional values, though. Suppose I have a rect with a width of 0.5. The integer version of the function would return a maximum X coordinate of -0.5, which is clearly wrong. If we were to retain the decision to have maxX() return the largest X coordinate that falls within the rect the correct implementation would look like this:

return rect.origin.x + rect.size.width - FIXED_EPSILON

…where FIXED_EPSILON is the smallest magnitude value representable by a fixed point number.

It was more effort, but less surprising for users of the function, to change maxX() so that it simply returned the first possible coordinate outside the bounds of the rectangle:

return rect.origin.x + rect.size.width;

Stepping through the columns in the rect then requires this alternative iterator:

for (int i = minX(rect); i < maxX(rect); ++i)

Performance Surprises

Writing anything for the GBA requires time fixing performance problems. The most significant cause of problems so far, and the most surprising, was the Div BIOS function. I’d taken care to use the BIOS Div and Mod functions everywhere thinking that they were the most performant way to divide on the system. Given that I’ve already experienced bugs in the DS’ BIOS (*cough*CpuFastSetcough) you’d think I’d know better. In any case, ROMs built using Div performed noticeably worse than ROMs using the standard C divide operator.

The Testing Dance

Testing ROMs requires an elaborate dance with three laptops and a GBA:

  • New laptop to write and build the code;
  • Old laptop with an SD slot to copy the ROM to an SD card;
  • GBA to test;
  • Very old laptop running Windows to check no$gba’s CPU meter.

Grabbing the REG_VCOUNT value gives me a reasonable estimate of CPU usage and should mean I don’t need no$gba, but my first attempt at writing a profiler wasn’t accurate enough. mGBA’s logging functions (which I was calling to see the contents of the profiler) are expensive. If I improve the profiler hopefully I won’t need the Windows machine again.

2020-09-09

RG350M

My first open source handheld was the original GP32. It had a reflective LCD screen that could only be used in ideal lighting conditions; crackly audio; and an awful mini joystick that only worked if I shoved a folded piece of paper into it.

Despite the flawed hardware, it was a system designed to enable, rather than inhibit, homebrew software; and for a time its quirky combination of amateur hardware and amateur software made it my favorite console. It was the first in a long line of homebrew-supporting handhelds that includes the GP2x, Dingoo, GCW-Zero and Pandora.

My last venture into open source handhelds was the Dingoo A320. Somehow a sizable homebrew scene grew up around this tiny machine. It gained a Linux port, multiple SDKs, and dozens of emulators and game ports. I threw mine out when the screen cracked, and I lost interest in the open source handheld world.

The RG350M made me take another look. It’s a direct descendent of the Dingoo in that it runs a fork of the Dingoo’s Linux port, OpenDingux. It can run GCW-Zero software without recompilation.

It’s a great looking machine with an aluminium shell, 640x480 IPS screen, two SD card sockets, two USB-C ports, two analogue sticks, and four shoulder buttons. The screen is by far the most vibrant and clear I’ve seen on a handheld. The shell is solidly built. The d-pad is perfect. The other buttons are responsive and clicky.

As is typical with open source handhelds, there are problems. The device is well designed but it has abysmal quality control. Mine has a misshapen right shoulder button. A little chunk of plastic on the button sometimes catches on the inside of the shell, preventing it from working reliably.

Internet complaints about other issues abound, including DOA units; pressure damage to the screen; screen discoloration; loose screens; flickering screens; dead screens; dead batteries; malfunctioning buttons; incorrect buttons; unreliable SD cards; bent shells; badly-soldered components; audio artifacts; nonfunctional speakers; components detaching from the device; and the device allegedly overheats and damages itself if charged whilst powered on.

The OS is solid. It boots quickly (under 10s) and is easy to use. It has a built-in SFTP server that’s accessible via USB, which makes copying files to the device simple. I’ve tried a handful of emulators and games:

  • Gambatte (GB/GBC; excellent);
  • Genesis Plus GX (Mega Drive/SMS; muffled audio);
  • Picodrive (Mega Drive/SMS; good but some compatibility/accuracy issues);
  • SMS Plus GX (SMS; poor frame rate);
  • UAE4All (Amiga; noticeably slower than real hardware);
  • UnrealSpeccy (ZX Spectrum; excellent);
  • Fceux-RG350 (NES; excellent);
  • Handy (Lynx; slower than real hardware);
  • GPSP (GBA; some screen tearing);
  • Doom (too slow at max resolution; too fast at lower resolutions);
  • XRick (unplayably slow; slower even than playing the original in UAE, which is very odd).

It even has a port of MilkyTracker. It’s impractical to use for composition without a keyboard, but it works well for playback. It has a dual-pane file manager, too.

Here’s a list of RG350M-compatible software, and here’s where to get the latest firmware

In terms of emulator performance the RG350M seems to be roughly on par with a PSP. I don’t think there’s anything that the RG350M can do that the PSP couldn’t do equally well.

Overall, it’s easily the best designed open source handheld that I’ve used, with great controls and an excellent screen. However, the lack of quality control is a major concern, and its capabilities aren’t a noticeable step up from the PSP despite the 16 years between them.

Obscure Handhelds has a much more in-depth review, but it makes no mention of the QC issues.

2020-09-05

Server Changes

I spent a productive morning revamping the Simian Zombie web server. Goodbye Ubuntu; hello FreeBSD.

Every other time I’ve upgraded Ubuntu things stop working or it refuses to boot at all, including my most recent attempt to move from 18.10 to 19.something. 20.04 has been out for months and the server was way behind. If I was going to waste hours tediously fiddling with abstruse config files I figured I might as well learn something new.

Along the way I ditched the Super Foul Egg and Woopsi websites. Neither code base is maintained, and the 32-bit build of SFE available from the site doesn’t run on recent releases of macOS anyway.

Also, I’ve enabled SSL. Last time I tried that it was a disaster, but this time the setup was pretty easy.

2019-11-13

GBA IPS

There’s been an explosion in the variety of screens available for retro handhelds. Here’s a handful of the most interesting:

The FunnyPlaying IPS screen for the GBA was top of my list to try out. Fitting the screen is supposedly trickier than the 101 mod that I managed to screw up so I decided that this time I’d get a premodded console.

I have a few sources for GBA hardware:

  • RetroModding (expensive but reliable)
  • God of Gaming (cheaper and, for me at least, reliable)
  • eBay (cheaper still but you takes your chances)
  • AliExpress (really cheap but you takes your chances and it takes a month to ship from China)

RetroModding currently charge almost 50% more than God of Gaming for an IPS GBA. Both use the improved second version of the IPS adapter cable that fixes the diagonal screen tearing problem experienced by the first. On eBay and AliExpress it’s hard to tell which version of the adapter cable the modder used. I didn’t want to end up with a v1 by mistake so I procrastinated until God of Gaming had a Halloween sale.

It’s a beautiful screen. I’d include a photo but they really don’t do it justice. As you’d expect from a modern screen, the physical pixels in the display are too small to be individually discernable. It has four times the pixel density of the 101 screen yet it has almost the same physical dimensions. It upscales the GBA’s output at an exact 4:1 ratio so that the image isn’t stretched or distorted. The screen is bright and vibrant, and it is evenly lit across its entire surface. I haven’t noticed any of the ghosting and interlacing that plague the 101 screen. Now that 101 screens are becoming scarcer and more expensive, the IPS screen is a cheaper alternative.

Nothing is perfect, especially when modifying old consoles with new parts, and the IPS screen is no exception.

Most significantly, the screen drops a frame every 1.5s or so. I’d guess that this is the price paid in order to eliminate the tearing that affected v1 of the adapter cable. I’d further guess that the root cause is a mismatch between the refresh rates of the console and the screen. It isn’t terribly noticeable in games like Mario World that don’t scroll smoothly, but in horizontally-scrolling areas in the Castlevania games it is painfully obvious.

Secondly, the IPS screen causes the already noisy GBA audio hardware to buzz more loudly than the 101 screen. It’s possible that this is an issue solely with my console; it’s hard to tell exactly where the fault lies with modified and refurbished equipment.

Thirdly, the crispness of the screen makes it very easy to see the pixels in the upscaled image that the GBA is pushing to the screen. If you’ve ever seen an SD UI running on an HD laptop screen you’ll have a good idea of what to expect. It exposes the low resolution of GBA games as effectively as the aging tech in the 101 screen hides it. Sometimes the crisp image can accentuate a game’s graphics, but it can also ruin tricks like dithering.

Lastly - and only allegedly, as I haven’t tested this out for myself yet - the IPS screen has higher power requirements than the 101 screen and reduces battery life.

Which would I choose? The IPS screen is clearly superior in terms of price, brightness, resolution, and definition, but the jarring frame drop problem is hard to overlook. Nintendo shipped the original GBA with a screen that was mostly impossible to see, but I doubt even they would have used a screen that dropped frames.

Belated edit: The screen is a knock-off. The official Funny Playing screens don’t have the frame drop problem.

2019-05-31

Chiptune 5

Another chiptune:

I wrote this a while ago as a guitar tune:

I was never very happy with most of the original tune. It has a soaring melody that’s really the middle of a musical idea. It needs a solid buildup that I couldn’t come up with, so I settled for a descending baseline copout. This chiptune version removes the unsuccessful parts and adds new ideas, but it doesn’t solve the original problem. It loops, hence the abrupt cutoff in the MP3 version.

2019-04-24

Proportional Fonts in GBA Tile Modes

I wanted to create a system for showing text with proportional fonts on the GBA. Typically on tile-based systems you’ll see fixed-width text with characters that align to tile boundaries. This is easy to do and efficient for the hardware. It’s exactly what I did in Professor Sinister. The new project I’m working on is going to have significantly more text and legibility is crucial, so I wanted to use proportional fonts instead of fixed-width.

First, some information about the GBA’s video system. The GBA’s screen resolution is 240x160px. When in tile mode, the screen is divided into a grid of 8x8px tiles. The hardware supports up to 4 layers of these tile grids overlaid on top of each other. In 4-bit color tile mode, in which each byte holds two pixels, ~19K of VRAM would be required per layer to store a full-screen bitmap in tile form. In addition to the tile bitmaps each layer requires a map to tell the video system how to lay out the tile bitmaps on screen. Each entry in the map is 16 bits wide. That’s another ~1K required per tile layer. Presenting a unique full-screen bitmap in all 4 tile layers simultaneously would require 75K of VRAM for the tile bitmaps and ~4.6K for the maps. The GBA has 64K of VRAM.

The upshot of this is simple: the GBA has nowhere enough VRAM. It can’t possibly show 4 full-screen bitmaps in tile mode. The best it can do is ~3.5 screens of tiles (2048) in 4bpp tile mode or 1.7 screens of tiles (1024) in 8bpp tile mode, but neither scenario leaves room for the tile maps.

In bitmap modes the video system is even more limited: two bitmaps (mode 4; 8bpp; 37.5K each) or one bitmap (mode 3; 16bpp; 75K). These modes eat into sprite memory and sacrifice sprites for bitmap data.

Keeping all of that in mind, how do you use a tile-based layout to show proportional font glyphs? Why, you ignore the benefits and constraints of the tile system and treat it like a bitmap, of course.

The font rendering system I used in Chuckie Egg et al has its roots in the rendering system I created for Woopsi, but with the warts removed. Give it a bitmap object to render into and the renderer will happily use the bitmap’s setPixel() method to draw text. The bitmap itself translates from the screen co-ordinates supplied by the renderer into co-ordinates that match its internal structure. That’s how I got graphics showing up on the 3DS with its weirdly-oriented framebuffer: I created a new bitmap class specifically for the 3DS.

Making tile mode on the GBA behave like a bitmap is a similar problem, but it requires some more setup. The first thing to do is to decide how many tiles to use. Using the full size of a tile layer isn’t advisable because, as we’ve seen, the number of tiles available is severely limited. I wanted the text to pop over the bottom of the screen like the conversation text in Pokemon. I figured I’d need 5 lines of 10px tall text. Add a couple of pixels spacing between each line and that gives me 60px; round it off with a couple of pixels padding above and below the text and I need 64px, which neatly fits into 8 vertical tiles. The screen is 30 tiles wide, so I need to reserve 240 tiles solely for the use of the text renderer. That’s a sizeable chunk of the 2048 tiles available in 4bpp mode, but at least we’re not using the 600 tiles we’d need for a full screen of text.

I’m keeping the first (zeroth) tile bitmap in VRAM empty, so that means I’m reserving tile bitmaps 1 through 241. At this point I can set up the tile map for the tile layer I’m using to display text: rows 12 to 19 need to contain tile bitmaps 1 to 241. Note that the layer is actually 32 tiles wide, not 30, but as two columns of tiles are offscreen and I’ll never scroll the layer I’m leaving those extra columns empty. Also note that the tile bitmaps must be sequential in VRAM or you’ll introduce an extra layer of complexity.

With that done I can feed VRAM+32 (ie the second tile bitmap) into my new bitmap class as its bitmap origin address and teach it how to translate screen co-ordinates to tile bitmap co-ordinates. This is how pixels are laid out in VRAM:

+-----------------+-----------------+  +-----------------+-----------------+
| 000 001 002 003 | 004 005 006 007 |  | 064 065 066 067 | 068 069 070 071 |
+-----------------+-----------------+  +-----------------+-----------------+
| 008 009 010 011 | 012 013 014 015 |  | 072 073 074 075 | 076 077 078 079 |
+-----------------+-----------------+  +-----------------+-----------------+
| 016 017 018 019 | 020 021 022 023 |  | 080 081 082 083 | 084 085 086 087 |
+-----------------+-----------------+  +-----------------+-----------------+
| 024 025 026 027 | 028 029 030 031 |  | 088 089 090 091 | 092 093 094 095 |
+-----------------+-----------------+  +-----------------+-----------------+
| 032 033 034 035 | 036 037 038 039 |  | 096 097 098 099 | 100 101 102 103 |
+-----------------+-----------------+  +-----------------+-----------------+
| 040 041 042 043 | 044 045 046 047 |  | 104 105 106 107 | 108 109 110 111 |
+-----------------+-----------------+  +-----------------+-----------------+
| 048 049 050 051 | 052 053 054 055 |  | 112 113 114 115 | 116 117 118 119 |
+-----------------+-----------------+  +-----------------+-----------------+
| 056 057 058 059 | 060 061 062 063 |  | 120 121 122 123 | 124 125 126 127 |
+-----------------+-----------------+  +-----------------+-----------------+

Each large box represents a tile bitmap. Each cell in the box represents a u16. The numbers represent the index of each 4-byte pixel.

To translate from screen co-ordinates to VRAM index, locate the tile that contains the point:

const int tileSize = 8;
const int pixelsPerTile = 8;
const int tilesPerRow = 30;

int tileX = point.x / tileSize;
int tileY = point.y / tileSize;

Figure out the index of that tile:

int tileOffset = (tileX * tileSize * tileSize / pixelsPerTile) + (tileY * tileSize * tileSize * tilesPerRow / pixelsPerTile);

Find the pixel within the tile:

int x = point.x % 8;
int y = point.y % 8;

Find the offset of the byte containing the pixel:

int offset = tileOffset + x + (y * tileSize);

That has far too many mods and divides. We can take advantage of the fact that almost every calculation is a power of 2 and use some bitwise magic to produce this faster but mostly-illegible one-liner:

#define offsetForPoint(x, y) (((x) & ~0x7) << 3) + ((((y) & ~0x7) << 3) * 30) + ((x) & 0x7) + (((y) & 0x7) << 3)

The define above, coupled with the VRAM address we supplied to the class when we created it and the knowledge that VRAM can only be addressed as u16s, we can create a setPixel() method that the font renderer can write to.

What’s particularly neat about reusing the existing rendering routines is that they can efficiently clip to a rectangle, allowing me to avoid the jerkiness of text rendering seen in something like Pokemon. That game renders text to the screen one character at a time, so the speed at which text appears varies with the width of the characters being rendered. With this rendering system I can smoothly slide the clipping rect across the screen and rely on the renderer to draw fractions of a character - or multiple characters if necessary - at a consistent pace.

2019-04-21

Professor Sinister Tips and Tricks

With the game finally complete, I can share some playing tips for flying around the levels in Professor Sinister like a pro.

Climbing Ladders

While approaching a ladder, hold Up (or Down if you want to descend). The Professor will grab the ladder and start climbing as he passes it. There’s no need to try and line yourself up with the ladder before pressing Up.

Quickly Ascend Ladders #1

Hold down Up and Jump at the same time while climbing a ladder for a little vertical speed boost.

Quickly Ascend Ladders #2

While approaching a ladder, jump towards it. Hold down Up when you get close to it. The Professor will grab the ladder and start ascending, bypassing the lower rungs of the ladder.

Quickly Ascend Ladders #3

While climbing a ladder, with Professor Sinister’s head roughly level with the platform you’re trying to reach, hold Left or Right and press Jump. The Professor will quickly jump off the ladder right onto the platform.

Jump Between Ladders

It’s possible to jump off one ladder while holding Up and grab another nearby ladder. You can’t complete level 7 without mastering this.

Fast Descent

You can jump from one platform to a lower platform instead of descending a ladder or dropping down (which would eliminate horizontal movement).

Bouncing Descent

You can bounce off platforms or the level walls to get quickly from one place to another.

Jump Through Platforms

You can bypass much of level 7 by jumping through the little platform to the right when you start. Walk up to it, hold Right and press Jump. Now you can skip to those last few eggs.

Cheats

The tricks above exploit the way the game’s engine works in order to zip around the levels more quickly. These are techniques that you are encouraged to use, particularly in later levels as the game gets increasingly difficult.

The cheat codes here are not encouraged and may spoil your enjoyment of the game.

To enter a cheat code:

  • Start a game;
  • Press “Start” to pause;
  • Enter the code;
  • Unpause.

A message will appear on screen every time a code is recognized. Entering the code a second time will remove its effect.

Konami

  • Code: Up, Up, Down, Down, Left, Right, Left, Right, B, A.
  • Effect: Prints a message.

Level Skip

  • Code: Up, Down, A, B, A, Down, Up, Left, Right, Left, A.
  • Effect: Enables level skip. Press Select to end the current level and skip to the next.

Slow Motion

  • Code: L, L, R, L, L, R, L, L, R, R, R.
  • Effect: Enables slow motion mode. Hold R for slow motion. Hold L and R for extra-slow motion.

Invulnerability

  • Code: Down, Up, L, L, A, R, Down.
  • Effect: Enables invulnerability mode. Robots and the floating eye cannot hurt Professor Sinister (but falling in holes will).

Backwards Konami

  • Code: A, B, Right, Left, Right, Left, Down, Down, Up, Up.
  • Effect: Prints a message.

2019-04-15

Professor Sinister Updated Again

In celebration of Simian Zombie’s 12th birthday, here’s an updated version of Professor Sinister and his Marauding Mechanicals:

Changes:

  • Added ladder mount/dismount assist to make it easier to use ladders.
  • Added cheat codes for level skip, invulnerability and slow motion mode.
  • Added raster effect on title screen.
  • Added high score table with persistent scores.
  • Fixed robot position when they collect power packs.
  • Fixed memory leaks.
  • Fixed crashes.
  • Fixed graphical glitches when switching between scenes.
  • Levels reset correctly when the game loops around.
  • Game loops through correct set of levels.
  • Updated title screen scrolling text.
  • Optimizations.

2019-03-14

GBA Modding

I had to try out the GBA backlight mod for myself. I’ve been slowly collecting the pieces necessary to perform the mod:

  • SNES-style reproduction GBA shell (eBay; $13);
  • Glass Famicom-style screen lens (RetroModding; $5);
  • GBA with a 40-pin motherboard ($30);
  • AGS-101 screen to 40-pin motherboard type “B” adapter cable (God of Gaming; $8);
  • AGS-101 screen (God of Gaming; $78);
  • 25 watt Weller soldering iron with a fine tip (Lowe’s; $20);
  • Precision X-acto knife (Home Depot; $4);
  • Electrical tape (Lowe’s; $4)
  • Tri-wing screwdriver (possibly Lik-Sang; I’ve had it for years)

I ordered the shell just over a year ago when the SNES design first appeared, and got the matching screen lens about 6 months ago. I found a cheap GBA at a local retro toy fair last month. The most expensive part was the screen; they were half the price last year. The rumor is that the screens are no longer being produced, pushing up the prices, but I wonder if Donald Golf’s trade war is contributing to the inflating cost.

The mod instructions are simple:

  • Disassemble GBA;
  • Cut away excess plastic from top of shell so that larger screen fits;
  • Connect the screen to the adapter cable;
  • Connect the adapter cable to the motherboard;
  • Solder additional wire from adapter cable to the motherboard;
  • Assemble GBA.

Cutting the shell to size was easy enough. Many sites recommend a Dremel instead of a X-acto knife, but I found that the plastic used in the reproduction shell was soft enough to make cutting through it fairly easy. At one point the knife snapped in half, pinging a razor-sharp chunk of blade across my desk, but fortunately the half-blade was better suited to slicing up the plastic than the pointed tip had been.

The first time I put the GBA together I didn’t bother soldering the wire. A few online guides suggest that this is optional if you don’t mind a dimmer display. Having used other modded GBAs I thought that the dim screen was disappointing, so I decided to open it back up and solder it. This was the part that concerned me: I don’t mind ruining a cheap reproduction shell, but I really didn’t want to damage a GBA motherboard. I haven’t done any soldering in years and never anything as fine as required here. Surprisingly this part of the mod also went smoothly and I got the wire soldered on my first attempt.

The electrical tape comes in handy in a few places:

  • Taping around the edges of the screen to eliminate light bleed;
  • Insulating the area where the cartridge slot pins poke through the back of the motherboard;
  • Insulating the soldered wire from other components around it.

With all of this done I loosely screwed the GBA together and gave it a try. It worked!

I screwed it together properly. It didn’t work.

I unscrewed it a little. It worked!

Hmm. If I squeezed the bottom of the GBA the screen switched off. Looking closely, it seemed that the screen was still working but the backlight was off. Time to unscrew it fully and figure out what’s going on.

Some diagnosis later - disassembling everything I’d just put together, including removing the soldered wire - I found that there was a tiny rip in the ribbon cable that connects the screen to the adapter. This cable is part of the screen and cannot be replaced. Any damage to the cable means the screen is dead. I have no idea where the rip came from, but now I needed a new screen. Remember that I said the screen was the most expensive component in the mod?

Nuts.

Another $78, another week, and finally a new screen arrived. In the meantime I’d re-soldered the adapter cable’s wire; of course it detached again whilst I was working on the GBA so I had to solder it a third time. Each solder was messier than the last, but I somehow avoided damaging the motherboard. This time there were no rips and I got the GBA assembled. I didn’t bother taping up the screen with electrical tape as I’d subsequently heard that it doesn’t look that great.

The last piece was the Famicom-style screen lens. The lens came with glue already in place. All I had to do was peel the backing off the lens, ensure the screen and lens were clean, and stick it down. After checking both surfaces and sticking the lens down I switched the GBA on to discover that there was now some sticky goop between the lens and the screen. I have no idea how that got there. One quick complete disassembly, clean and reassembly later and I had a fully modded GBA.

Lessons learned along the way:

  • The SNES shell is very gray, and I think a white shell might look better with the multicolor buttons and Famicom lens;
  • An X-acto knife is good enough to mod the shell, but beware breaking blades;
  • You absolutely must solder the adapter wire for the best display;
  • Soldering the wire isn’t as difficult as you’d think;
  • You’re not treating the screen’s ribbon cable carefully enough, even if you take into account this lesson;
  • The light bleed around the screen edges is annoying, but not annoying enough that I want to open it up again (RetroModding sell a “backlight bleeding protector” which might work better than electrical tape);
  • The tools to do the mod weren’t as expensive as I’d expected;
  • Screens and adapter cables can be ~20% cheaper on eBay and AliExpress if you don’t mind waiting for delivery from China;
  • You can probably buy a pre-modded GBA for around the price of the individual components;
  • You can definitely buy a pre-modded GBA for considerably less than the price of the individual components if you damage your first screen and have to buy another.

The most important lesson I learned was this:

  • I should stick to software.

Here’s the finished product:

GBA-101