#1GAM January 2015: Balloon Spite [game, releases, postmortem, arm, asm]

Want the game? Here's the ROM. Press B to flap. Hit L or R in the select screen to choose an alternate palette. START skips most screens.

[Balloon Spite]

In December, I found out about One Game a Month (abbreviated #1GAM), which is a kind of personal challenge to finish and release one game every month for a year. (I am no stranger to ridiculous personal challenges.)

I read Christer Kaitila's blog post about #1GAM; it deeply resonated with me when I read, "I’ve started so many more games than I’ve finished in the last 20 years."

Even before I knew I wanted to be a programmer, I wanted to make games. I'm sure this is a stated goal as common and clichéd as "astronaut" for the children of the Atari age, but I did pursue it right through my childhood. After typing games in from magazines, I started writing my own, and from somewhere around age six or so onwards until my early twenties, I wrote games. A lot of games.

So where are they? Even the most promising projects, products of fertile collaborations with talented friends, were never finished.

I had thought about doing a personal "games retrospective" before, but I wasn't sure how to do it properly. #1GAM presented an opportunity to dredge up some past games in a time boxed manner, and to learn more about finishing things.

1. January

January got off to a rough start, though. I had a huge list of games I wanted to work on, and I became paralyzed by choice. After a week of regret and despair, I came back to it more systematically. I couldn't work on a game full-time, so it had to be something I could conceivably release on about a commit or two a day.

I dug through my archives. Unfortunately, the backups I had with me only had a smattering of working copies of CVS checkouts of some old projects, rather than the repositories themselves. One project looked like it met the criteria: Hyper Ballon Struggle.

[Hyper Ballon Struggle]

In 2002, I ordered a Gameboy Advance flash linker from Lik Sang, and RhombusSoft started working on GBA games. (I'll explain RhombusSoft in later #1GAM blog posts.) Hyper Ballon Struggle was a project to see how much GBA game fit in a weekend's worth of development. (We would often have weekend game making parties, which were basically game jams now that I think about it, although the concept was not known to us at the time.)

It was intended as a Balloon Fight / Joust clone featuring a roster of characters from other games we had developed, in the spirit of crossover/all-star games like Wai Wai World, Saturn Bomberman, or Super Smash Bros.

Once I figured out how to build the copy I had, however, I realized it was incomplete and out-of-date. I knew that the last version we built that weekend had a playfield (but no playfield collision), music, and a "great" rotscale checkerboard effect in the select screen. This version only had the core Joust-style mechanic and little else.

I made some calls to people still living in Newfoundland (where all this part of history happened), and a backup tape was discovered, dated right after we turned out the lights on our little game studio. I was thrilled, until I discovered there was no way to read the tape; a machine with an internal DDS-2 drive was dug out of storage, but the drive no longer functioned. Not wanting to ship the tape around, I decided to work with what I had.

2. What Balloon Spite is

I renamed Hyper Ballon Struggle to Balloon Spite, because I never pass up a cheap pun (Balloonacy was already taken).

2.1. As a game

[Sweating like a pig]

Because of the possibility that I wouldn't have time to draw new sprites, I decided to turn it into a kind of Street Fighter II-style one-on-one game, but with a balloon popping mechanic.

I added an exertion mechanic (indicated by the sweat drop) hoping it would provide some depth to the play, but the stamina values need some tweaking for it to be really useful. You can sometimes tire out the computer opponent if they try camping at the top of the screen, then get in a quick attack.

There are eight levels. The original intent had been to have a level for each character, plus two or more levels with special boss characters, and of course, different music for each level, with nods in the themes to the music from that character's respective game. Alas, as it is, there are only two short, irritating in-game songs, because the music was done in an afternoon.

The characters and what (unreleased) game they're from:

Character   Game
Harvey 2015-02-01-hbs-harvey.png AnimoCity
Rudolph 2015-02-01-hbs-rudolph.png Quest of Zo
Ralph 2015-02-01-hbs-alien.png Buckler Strife
Lopez 2015-02-01-hbs-lopez.png Greed'n'Magic
Pierce 2015-02-01-hbs-pierce.png Maelstrom
Greedy 2015-02-01-hbs-greedy.png Greed'n'Magic
Sam 2015-02-01-hbs-sam.png Convergence
Iceclown 2015-02-01-hbs-iceclown.png Fobwart

The iceclown isn't a playable character, but was intended to be a boss. (Lopez was supposed to be a boss, too, but for lack of KidThulhu (from the eponymous game) and Peter (from Demon of the Fall), he remained selectable.)

Myr was one of the artists on the team. There also was a sprite that I thought was supposed to be Retsyn, but he disputes this and removed it from the game. (It should be noted that half these games were written by Retsyn; Buckler Strife is the only one I had no involvement in, though.)

Melville is a reference to the Taito game Cameltry. ("Moby died in the Spinning Room.")

2.2. Technically

According to sloccount, it's about 2620 lines of ARM assembly code (ignoring another 1kLOC of tables and such). It's pretty buggy, but it does have a unique character to it. There were some bugs I consciously decided not to expend time on, like interpenetration resolution having the possibility of pushing a character into the playfield. In the final hour or two, copy-and-paste became the dominant coding paradigm. The code is on github.

GrafX2 was used for all pixels, both in 2002 and in 2015. Levels were created as PCXes (yes, PCX, even in 2015) and converted to tiles with a tool borrowed from Convergence. Music, such as it is, was created in emacs and compiled with mumble.

The original game did run on the real hardware, since we didn't have a usable emulator at the time! All my GBA development hardware is in storage, so no new builds have been tested on the real thing. That's okay; I will be testing it in the future, and maybe I'll patch it later. Most people will be playing on emulators, anyway.

3. What Went Right

3.1. Targeting the GBA

By producing a ROM image that could be run in any emulator, I made it much easier to send builds to my friends and get quick feedback. This was an unexpected benefit. Getting feedback, even on early, broken builds, was helpful in maintaining motivation.

I remember that iterating with the flash linker was a really slow process, so I have no shame in targeting an emulator where I can get nearly immediate feedback from a build.

3.2. What went right: Retsyn's great pixels

I was already blessed with some pretty great sprites:

[Old roster]

Or so I thought, until Retsyn came to the rescue at the last minute and revamped the sprites and palettes:

[New roster]

He also did the background for Myr's moon stage. (And, of course, the vast majority of the old Rhombus content was done by him, back in 2002 and before.)

It was inspirational, and kept me going in the final moments of the project. Of course, it also made me regret the time wasted that could have gone into polishing other aspects, like the music and sound.

3.3. What went right: Resolving to Ship

The Perfectionist's Handbook is one of the books that has been a critical part of my journey towards becoming a finisher. I realized I had to expose myself to criticism if I was going to get anything done. I had to stop seeing every piece of code as a reflection of my self-worth. And I had to stop trying to optimize (or elegantize) the hell out of everything.

A number of books draw the distinction between healthy and unhealthy perfectionism, but Szymanski's Handbook was the most useful in convincing me that I could let go of some things without losing the good things about perfectionism. Most importantly, it helped me understand that perfectionism, instead of causing me to release only flawless code, had caused me to withhold tons of good code from release, releasing only mediocre code under duress.

I stole a few tools from my other projects, most importantly tools for converting PCX files to GBA tiles. The code was often terrible – several of the tools were among the first programs I had ever written in OCaml, and it certainly shows. My instinct was to dramatically refactor them immediately, and I am a little proud that I resisted. Maybe I will go back and clean them up, but I recognized that it wouldn't further my goal. The Julian of 2002 would not have been able to bear that.

.section .ewram
.align 2
@@ XXX should use an overlay for this
.lcomm balloons, BALLOON_LEN*MAX_BALLOONS

There were so many opportunities for optimization that I avoided making. For example, separate screens ("activities" in Android lingo) could share the same region of memory for their local variables, using overlays in the linker script. Didn't do it. All the tile data, map data, music data, and even PCM samples (!) are totally uncompressed. Scandalous! Various structures in memory wasted bits or even bytes out of convenience. In 2002, I could never have endured that.

One thing I didn't know at the time was that the code in ROM would have been faster, in general, if it had been written in Thumb mode rather than ARM mode. I strongly considered rewriting my code to use mixed modes, but I reminded myself: I need to ship this in a matter of days. It's just a silly little game. YAGNI.

When it came time to implement normalization of vectors when computing contact normals for collision resolution, I did waste a bit of time thinking about the efficient implementation of reciprocal square root versus atan2. I had also been thinking about implementing the Sunderland algorithm for improving my trigonometric function lookup tables. YAGNI. Maybe later.

compute_contact_normal:
        stmfd sp!, {lr}
        @@ XXX Ideally, we'd use the reciprocal square root here.
        @@ There are great, simple algorithms for it.  But let's get the
        @@ slow way working first.
        mov r0, r7, lsl #PHYS_FIXED_POINT*2
        swi #8<<16              @ sqrt
        mov r7, r0
        mov r0, r8, lsl #PHYS_FIXED_POINT*2
        swi #8<<16              @ sqrt
        mov r8, r0
        @@ r7 = distance (12.4), r8 = penetration (12.4)

I used the slow BIOS division and square-root routines instead; the first time, my hands trembled. I think it's the first time I've written a division on a system like the GBA that wasn't a shift or a reciprocal table lookup. The second time, I stopped and wondered how many divisions I could survive in one frame. By the eighth time, I didn't even think about it. Ship it. Optimize only if there's a reason to do so.

3.4. What went right: I started to enjoy working on it

The most important thing that went right is that, to my surprise, I started to enjoy working on the game; I even started to enjoy playing games again.

It's been years since I've enjoyed playing videogames at all. I think the peak for me was when I was in my early 20s. (If I don't enjoy games, why am I writing them? There's a question to answer later this year. I'm still thinking about it.)

The possibilities of the idea, which seemed stunted as I began, opened up as I spent time with it. I didn't get to exploit any of those possibilities, really, but it was a good lesson.

4. What Went Wrong

Getting back up to speed with the GBA took a little while, and given the late start, it ate up days that would have really counted in the end.

I have to admit that when I started, I was so rusty that I wrote mvn r0, r0 rather than rsb r0, r0, #0 trying to negate an integer and similar kinds of mistakes, but it came back to me eventually.

I didn't have the same cross-compiler the original project was built with, so I dropped in the linkscript I wrote for Convergence and hoped it would work, but this actually resulted in a couple of days of debugging until I realized that the .data and .bss sections were silently being put in ROM. It turned out that, in Convergence, I had always indicated whether space was to be reserved in EWRAM or IWRAM, so I never added a generic BSS or data segment to the linker script. Once I figured it out, I was pretty surprised that ld hadn't complained, but these are the perils of reuse from other projects.

4.1. What went wrong: Making assumptions about tools available

I left the music til pretty late, because I can compose pretty quickly (as evidenced by Chip Weekend); the original music routine written for Hyper Ballon Struggle was missing, but I assumed that the music playroutine in Convergence would be fine, and that my archive of that would include tools to convert some format (probably XM or MIDI) to its internal format.

Imagine my horror when, on January 29th, I attempted to replace the awful test song only to discover that the music conversion tools for Convergence, if they were ever finished, weren't in the copy I had.

I decided I would add support for PH-1 (the Convergence playroutine) to mumble, a compiler for an MML-like textual music description to various playroutines that I wrote when I was working on Atari ST demos and then totally abandoned, circa 2004. It was pretty naïvely implemented, and very incomplete. There was another temptation to rewrite it, especially given how much my understanding of Common Lisp has improved since I wrote it, but at this point I knew there was no time.

;; Victory fanfare
A o3     a12aaa4     >c12ccc4 | c+12c+c+c+4 e12eee4  | e1
B o2 %i2 c+12c+c+c+4 e12eee4  | e12eee4     g12ggg4  | b1
C o2     e12eee4     g12ggg4  | a12aaa4     >c12ccc4 | g+1

I was planning to present Balloon Spite to some friends that evening, so I tried to focus on the straightest path to the goal, even if it meant a lot of compromise. I hacked in crude PH-1 support, although mumble's lack of support for drum kit-style bindings for the noise and PCM channels meant I couldn't quickly write percussion parts. This is one of the worst deficits of the music that ended up in the game, and it's one of the reasons it all feels terribly primitive.

Added to that, the playroutine design for Convergence sucks; there's no getting away from that. Music was left til late in Convergence, too, so the playroutine design was never battle tested. It was my second (or maybe third) chip playroutine design and suffered from me trying to do things differently, thinking that a more musical representation would be compact and efficient. (These days, I think the approach KB used for fr-08 is way better than something like this.) Also, many features were simply unimplemented: no arpeggios, no vibrato, no volume envelopes – those are the essential tools for making chip music that doesn't sound dead, that doesn't sound like the output of BASIC's PLAY statement.

4.2. What went wrong: Not following the McFunkypants Method

I was aware of the McFunkypants Method for finishing a game, but I didn't follow it as closely as I could have.

[The incomplete VERSUS screen]

I think the biggest mistake was not focusing relentlessly on the core gameplay until it was done. I was seduced by my overall vision, especially aspects that were more "tech demo" than game. The "versus" screen was supposed to have a rotscale spinning-out effect on the "VERSUS" text, for example. Levels were supposed to have different combinations of parallax scrolling foregrounds and backgrounds, et cetera. The select screen has its nauseating rotscale checkerboard effect (a recreation of an effect I remember from the original). Players can have alternate palettes (and palettes are pretty customizable in general). Various time was wasting doing these things or experimenting with them.

That time would have been better focused on the core gameplay. As late as last night, my final night working on the project, I was making major gameplay changes, and the build fluctuated between impossibly hard and incredibly easy. The final build is not as much fun as one of the earlier builds, but I accept that as a lesson about priorities and time management.

For February, I'm planning to follow the aforementioned method much more closely. Today I'll be making my storyboard since the game idea is already established and planning what is required to completely implement the core gameplay in the first week.

5. Lessons Learned

I think the summary of all that is:

Cumulatively, this was a full weekend of effort in 2002, plus about thirteen days of spare-time hacking and maybe two days of full-on work. Given that timeline, I am pretty happy with the result. Maybe next year I will revisit it and put out a "remix" version with the music and physics it deserves, but even if I don't, I'm content that I gave it a chance to finally see the light of day.

Onward, to February!

JS