Aereven Advance Postmortem


Aereven Advance is a Game Boy Advance homebrew game I made in 2024 for a game jam.

It’s a 2D action-adventure game inspired by Zelda and a direct sequel to my 2020 game “Legends of Aereven: Lunar Wake”.

Development took a bit over two months with the main difficulty being programming for a retro game console for the first time, with unofficial documentation and tools (though the community has done an incredible job!).

In this postmortem I will look back on how it came to be, how it works and where I will take it next. This is an exercise I like to do when I complete a project, so I’m writing mostly for myself, but if you’re interested in the nitty-gritty of gamedev, you may find some interesting nuggets in there!

GBA Jam 2024

First things first: where does this game come from?

Last year I joined the Metroidvania Month 22 game jam to make the first version of Silent Paradise. It was my first jam in more than a decade so I wrote at length about this experience. Most of it applies to this jam too.

Something of note is that I mentioned game jams wouldn't become a regular event for me in the future… and yet here I am, joining another one only a few months later. The reason is simple: I just couldn’t resist the pitch for GBA Jam. Making a homebrew game for a console I loved, within a chill 3 months schedule? Sign-me up!

Still, for both jams, it was really important that I made first and foremost a “Dreamnoid game”. The constraints of the jams simply served as an interesting starting point, like the ‘abyss’ theme for MVM22 or the GBA hardware here.

And to be honest, this jam wasn’t a sure thing at all. Could I really make a “Dreamnoid game” in a few months for unknown (and underpowered) hardware? I gave myself a few “gates” to overcome in order to proceed, mostly technical ones. Can I display sprites on the screen? Can I have a big scrolling map? User input? Sound? Save? It’s only when I got everything somewhat working that I really committed to the jam. Until then, I gave myself the option to give up at any moment. It also meant that, until then, the jam was a lesser priority than my current project (a game that’s already been derailed by two jams!)

I submitted my entry on July 31, almost three weeks before the deadline, because I was leaving town for summer vacation. But I knew this was my real deadline long ago and even a 2-months jam is plenty of time to get something polished.

The plan

From Zelda 2 to 1

Because I didn’t know if I would manage to create anything good on a GBA, I initially decided against using any of my existing “IPs” for it (that sounds way too corporate for the scope I operate at, but you get the idea). I didn’t want people who enjoy my regular games to be disappointed by such an experimental project.

With that in mind, the initial plan was to make a small game inspired by Zelda II, with a JRPG-like overworld and side-scrolling towns, caves and dungeons.



But two things quickly became apparent. One: I was basically making two games at once. Two gameplay loops, two sets of tiles and sprites, etc. Second: I was spending more time working on the overworld than I was on the dungeons, a clear hint about which half of the game I was more inspired about.

So two weeks in, I scraped the side-scrolling segments in favor of a more classical Zelda experience, fully top-down.

I still kept the overworld because that’s something I absolutely adore in JRPGs, especially Golden Sun 2, and bumped the sailing mechanic from ‘Nice to have’ to ‘Must have’.

Two spin-offs and no main game

At first I wasn't going to use the Aereven universe for this game. Like I said, I didn’t want to overpromise. But another reason is that I always thought of Lunar Wake as a “prologue”.

To explain this one I need to go back in time 20 years. It’s 2004, I just joined highschool and the last big 3D Zelda was The Wind Waker. While it’s not my favorite entry in the franchise, I have a strong connection with its nautical setting as I grew up near the ocean myself. That’s when Aereven started to materialize as I worked on it with an internet-buddy. The word itself is supposed to mean “Ocean World” and one of the iterations we came up with was subtitled “Legend of Azura”, starring what is fair to call a gender-swapped Link, blonde hair and blue lobster shirt included. To this day, I see her adventures as the core of the Aereven universe.


(A 2004 attempt, already starring Lloyd, protagonist of Aereven Advance. Also a smol drake!)

So back in 2020, when I was planning what would become Lunar Wake, I decided not to ‘fire my best round’ on my first attempt, so to speak. (Not that the idea was particularly great, but because it meant a lot to me and I wanted to do it justice.) That’s why I chose Lloyd, protagonist from another iteration, to helm what would essentially be a spinoff from a game that never got made.

During development I decided Lloyd would be Azura’s son and had a little fun hinting at her untold adventures, something I continued in Aereven Advance (fun fact: NPCs tell you Azura saved the world 20 years ago, which for us would make it… 2004!).

Since then, most of my prototypes for new Aereven games have aimed for this fabled “Azura’s Quest”. I had no real plan to build upon Lunar Wake and continue Lloyd’s adventure.

But as I continued to work on this GBA engine, clearing my self-imposed gates one after the other, I realized something: Lunar Wake was already an experimental game. Sure, the experiment was simply “can I actually achieve this???”, but the whole point of introducing Lloyd’s side adventure was to make failure acceptable. If Lloyd were to front another experimental game, would it be so terrible if it ended up like a glorified proof of concept? 

Once I imagined this possibility, the ideas came naturally. “Aereven Advance” would be a direct sequel to Lunar Wake, a relatively low-stake adventure bringing Lloyd one step closer to finding his parents.

Of course, he wouldn’t reach them: right at the end, when Lloyd believes he’s finally reunited with his mother, it’s revealed it was actually an evil Shadow masquerading as Azura! This final boss was directly inspired by Dark Link from Zelda II, an idea I kept even after I cut the side-scrolling.


A technical journey

We really take floating point arithmetic, divisions and square roots for granted, do we?

Retro ambitions 

Before we get into the nitty-gritty, I have another story to tell.

Back 20 years (again, around the same time Aereven was first conceived), I got interested in making a GBA homebrew. I was a curious teen with more ambition than skill. I had just learned C++ and was thinking ‘how hard can it be?’

A seasoned french developer named Christophe Kohler frequented the same french-speaking gamedev forums I did and was nice enough to send me the complete source code for his 2001 GBA game Tank Zone for me to study. That’s basically when I had to admit I was in over my head.


For the longest time I felt like his generosity had been wasted on me, so this jam was a chance for me to fix that. (And this detailed postmortem is my attempt at paying it forward.)

That’s one of the reasons I made this game using only the DevKitPro C compiler and the libgba headers. Everything else is from scratch. Because the goal wasn't only to have a game of mine running on a beloved Nintendo handled, but also to prove I finally was the programmer I believed I was back then (to my defense, I was, like, 13).

The fantasy console that actually existed

The second reason is tied to ongoing R&D I’ve been conducting to help my craft. Back when I started making action-adventure games seriously in 2019, my main concern was to build big ambitious games quickly, by myself. It’s still the #1 concern, but as time passes and I release more games, I have to think about tech stability and porting to new hardware.

I’ve been thinking about building a “fantasy console” just for my games, to see if it would help in this regard. Building a game for the GBA is one way to research that. Keep it in mind as it explains a lot of my technical choices.

Tools

So, after DevKitPro I installed Visual Studio Code and a handful of add-ons. I do not like it. I do not recommend it. I never managed to make VSCode resolve my #include statements so most of the lines were underlined in red and the auto-completion suggested nothing. F12 wouldn't send me anywhere. VSCode did nothing for me that Notepad++ wouldn't have. But it’s okay, because I actually spent very little time in VSCode writing C, as we will see later on.

My emulator of choice was Visual Boy Advance, mostly because I already had it installed and associated with .gba files. Also I like its visual memory visualization tools. They were invaluable early on as I struggled to understand the console’s specs and get something on screen.

But otherwise I had no debugger, not even printf. I spent a lot of time poking at bugs that could have been understood in under a minute by placing a breakpoint and looking at the locals… (I also had no debug draw, which was similarly annoying.)


(The debug room, opened in the map editor)

The entire content pipeline was custom and written in C#. It’s part of what I will later refer as simply “the tool”, a IMGUI program that includes a tilemap editor. This is the only part where I could reuse my usual Dream engine, albeit only the tooling side of it.

Pressing F5 in the tool runs some code that will list all assets in the data folder and convert them to the appropriate format before writing them as C code in a generated source file. Then it calls ‘make’ to build the ROM and launch it.

That whole process used to be pretty fast until I introduced background music. Then compiling the C program started to take a while, though I’m sure I could have organized that more intelligently in separate compilation units to avoid recompiling everything everytime. But it wasn’t painful enough for me to bother with it.

Graphics 

The tentative specs for my fantasy console planned for a single tileset and spritesheet to be used for the entire game, like I did for The Lightkeeper and Lunar Wake. That’s the approach I chose for this jam. Thing is, PC games can have huuuge textures while the GBA VRAM isn't all that big. That proved to be an issue halfway through development as I was running out of space for all the content I had planned. (And it would have been even worse had I kept the split between perspectives…)

Re-reading the specs, I did manage to get some extra rows for the tileset (that I barely used) but I struggled with the spritesheet until the very end. I had to give up on some of my ideas because they would require more sprites than I could afford.


(The entire VRAM near the end of development. I was so happy to free some space for the tileset that I took an actual picture to share my excitement around. Nobody cared.)

Of course, a quick run of The Minish Cap and Golden Sun in an emulator proved to me that VRAM upload was fast enough to be done each frame, but by that time the entire game had been architectured around indexes into a single spritesheet and it was too late to change it. (I could still have switched to 4bpp for sprites but juggling palettes seemed complicated given my very simple workflow.)

I also maintained two source images for the background tiles and the UI, despite them being sent to the same block in VRAM and using the same palette. That forced me to go through hoops to make sure their palettes were compatible (= that the tileset palette started with the UI palette). Thinking back on it I should just have merged the two in Photoshop and call it a day.

I only use Mode 3, with the first three layers dedicated to the terrain. The first two are below entities, the third one above. The last layer is for the UI, above all else.

The curtain effect during scene transitions is actually done by progressively filing the UI tilemap with a black tile.

The whole thing is redrawn 60 times per second. Even if the player doesn't move, I memcpy 20 rows of tiles from the ROM to VRAM, x3 for each layer. (For most of the development, those were actual memcopies happening entirely on the CPU. Late in development, when I started to experiment slow downs on the heavier maps, I switched to DMA3 copies. The slow downs went away.)

OAM sprites and the UI are also redrawn every frame. I basically worked like I would on PC with OpenGL and it was good enough. The GBA really hits that sweet spot of what you can do versus how hard it is to do it.

Audio

I don't know much about audio. All my games are pretty simple in that regard, using SDL_mixer to play sound effects or looping music at a given volume. I had never written my own mixer before and I dreaded the moment I would have to.

Thankfully, the great GBAdev community paved the way for me. I basically followed the first steps to this extensive tutorial by DekuTree and managed it.

It wasn't so bad! I felt so confident that I went back to my PC engine and replaced SDL_mixer with my own mixer! (Handling SDL_mixer dependencies has been an ongoing source of pain in my attempts to port my games to Linux, so I’m glad I can just slap stb_vorbis and some custom code in its place.)

Music for this jam is procedurally generated the same way as Silent Paradise. Some tracks downsampled well to 8 bits but others became very dissonant. That’s when I learned about audio dithering and how adding random noise to your samples somehow makes the whole track sounds better (because inaccuracies are now spread around instead of following the shape of the sound, and thus less noticeable, just like image dithering).

It’s still not sounding too great (lots of white noise) but believe me, it's better than the alternative.

Fixed-Point Troubles

Like I said: it’s easy to take floating points for granted. And honestly, if you’re not making a GBA game, they pretty much are granted.

Floats are too slow for practical use on the GBA. I knew some basic theory about fixed points, so my initial implementation was simply multiply or divide by 1000 to go from “display coordinates” to “physics coordinates” and vice-versa. But divisions are slow, too! So I traded 1000 for 1024 and used bitwise shifts instead.

And that’s how far I went. In the whole game, I never multiply or divide two fixed-points numbers together. Integrating velocity is done with two bit shifts, essentially dividing by 60.2, which is close enough to the GBA’s refresh rate.

This simplicity has an unfortunate consequence: I can’t normalize vectors. Moving in diagonal is faster than straight on. Projectiles can’t aim at you precisely. I believe it’s okay, as both issues are actually to the player’s advantage.

That’s why I never bothered digging deeper to fix it, choosing instead to spend time on the next part.

Gameplay

This is where the fun begins.

So, I'm making a Zelda-like, right? You can swing a sword, throw a boomerang that will come back to you, lay bombs that explode after a while, sail a boat and fight many different kinds of enemies with their own unique behavior… How much of that do you think can be found in the C source code?

That's right: none of it.

So what does the engine actually know about? 

The C engine knows about entities, hitboxes and triggers and that’s basically it. Everything else is implemented through a bytecode interpreter.

The world is split in maps. Each map is made of 3 layers of tiles, a list of rectangles dividing regions (like the rooms of a dungeon) and a list of spawners. A spawner is basically a position and an index into the global bytecode array. When entering a new region, all the spawners contained inside the region will spawn a new entity and assign it the associated script.

That means every entity is a virtual machine, or an independent process. In some cases, the script will only set up the entity a certain way and return, in other cases it will enter an infinite loop that will continue to drive the behavior of the entity.

(Instructions can yield during execution, to be called again next frame. That’s how OP_WAIT and OP_PRINT are implemented, for instance.)

(Multiple spawners can reference the same script, which makes it a prefab. Though quite a few prefabs actually generate unique bytecode for each spawner.)

Entities are basically an array of integers called ‘variables’. Those variables have meaning for the engine, like the amount of HP, current sprite index, etc. The script sets those values and the engine reacts to them. And vice-versa, the engine may set flags for the script to query.

Some variables associated with an entity are what I call ‘events’: if a script index is set on this variable, it will be called when the corresponding event is triggered. Examples of events are when the entity's HPs reach zero or when it goes over a pit, or water, etc. This is a similar concept to hardware interrupts:

Likewise, items have scripts. Multiple ones, even. One for use in the inventory (that’s how the Potion prompts a confirmation and then heals you when most other items only display a small message) and another on the field. When the player presses the button associated with an item, the script is executed on the player’s virtual machine (the player is an entity like any other).

If we take the Boomerang as an exemple, using the item will stop the player, make it play a little animation for a while and spawn a new entity for the projectile. This projectile has its own script that will set up the hitbox, initial velocity and animation. Then the script waits for a few seconds and changes the velocity to make it go back to the player. But we also registered the ‘come back’ script to the ‘OnCollides’ event, meaning it will instantly skip to this second phase if it collides with a wall. The rotation animation is done by changing the FlipX and FlipY flags of the sprite every few frames. And there you go: a boomerang item implemented purely through scripting, on a GBA.

(Fun fact: the save command is also implemented through an item! The only dedicated piece of code for that feature is the OP_SAVE opcode that copies the current save structure to SRAM.)

Everything works like this. Items, enemies, puzzles, NPC interactions, boss encounters, music changes, everything.

Even the small key counter displayed on the bottom right of the screen in dungeons! After all, the engine has no concept of a ‘small key', but it does have ‘scene variables’, similar to entity variables. Two of those variables describe the icon to display for the counter and the save flag used for the amount. When you enter a dungeon, an invisible entity sets those two variables. Likewise when you leave, those are reset by the overworld.

Now, this isn’t a perfect system. Or rather my (mostly improvised) implementation is imperfect.

Spawners could be optimized out of the runtime entirely if maps had their own script triggered when you change regions, responsible for spawning the other entities. That would also remove the need for flagging some spawners as ‘global’, meaning they’re spawned only once when entering the map, no matter the region.

But the main issue is that, while the virtual machine is stack-based, very few opcodes use it. The OP_SETENTITYVAR opcode will take the variable index and the value from the constant parameters associated with the opcode (A, B or C) and not from the top of the stack. That restricts the system somewhat because all values have to be known at compilation time (aka ‘in the tool’) and can't be computed on the fly. It’s not without benefit as it’s faster to read the next word from the program than to push and pop from the stack constantly, but it does lead to some duplicates in the opcodes to handle different situations. I decided against changing it, but if I were to choose a similar approach for a PC game, I would use the stack for everything but pushing literals.

That’s one reason why ‘free variables’ are almost useless. The idea was that the variables table for each entity (and the global scene) would have extra space left for variables that weren't interpreted by the engine. Scripts would both write and read from those. In practice, only the flute uses them to store the target of a warp, and even that is useless considering only one such warp made its way into the jam version (it could have been hardcoded).

Another construct that was barely used: functions. OP_GOTO will simply change the program counter, so I later introduced OP_CALL and a stack of program counters, and changed OP_RETURN to pop the stack and only stop execution when the stack became empty. 99% of scripts used only GOTO and I’m sure the last 1% could have, too, especially considering how this bytecode is generated.

Where does this bytecode come from, you ask? How is it built? You may have already guessed by looking at the previous screenshots…

The first versions (back when it was half a platformer) had hand-made scripts, but I quickly moved to something auto-generated by the tool. The same process responsible for generating the byte arrays for palettes, bitmaps and sounds is also responsible for building the global list of instructions and remembering where each script begins.

This is the simplest compiler you’ve ever heard of. It may even be more accurate to call it an assembler. I have a list of instructions (opcode + parameters) and the different elements (prefabs and items) in the game take that list one after the other and add their own instructions to it, after first taking note of the current instructions count.

I do have shortcut functions for complex but often used constructs, like branching following the result of a condition. In this situation I use the very useful IDisposable pattern available in C# to define a scope. I can generate a placeholder NOP instruction at the beginning of the scope and when the scope ends, I can count how many instructions were found in between and replace the placeholder with the appropriate skip instruction (a relative GOTO that evaluates the top of the stack first):

The whole compiler works with 32 bit integer words. I don't use floating points, so fixed point decimals are still represented as integers. Strings are allocated in a fixed array of const char* and the index in this array is used to represent them everywhere else. Those strings are listed by the compiler (and sometimes processed, for example by adding automatic line breaks when I detect text would overflow from the dialog box) and written in the generated C file, right before the array of instructions.

I later added a “data segment” array, similar to the “text segment” array. I used it to store sequences of integers I wanted to point to in entity variables. The only use case for this are hitboxes rectangles, but it would have been a nice place to store tilemaps and region bounds, had I thought about it earlier in the dev cycle.

And that’s about it, I believe. There's some trickery involved, of course, like resolving circular dependencies between scripts that are registered one after the other. One example of that are the mines that spawn a timer when they explode, which will in turn spawn a new mine after a while. This situation is resolved much like the ‘IfTrue’ construct, by emitting a placeholder and replacing it when the index of the later script is known.

But otherwise it’s a very simple but extremely powerful system. The little complexity there is is relegated to the tool and not the runtime. There’s no need for serialization at runtime!

My implementation includes some validation, but it’s a bit lacking and something I would have invested in a bit more for a longer project.

For instance, save flags are listed in a text file that the compiler loads as a string array and the index of a given flag is obtained with flags.IndexOf(flag). There's nothing checking if the flag is actually in the list and I spent quite a bit of time investigating a bug caused by a typo in a flag name. And it still didn't work after fixing the typo because the flags list was exceeding the max amount allocated by the C engine, causing an out-of-bounds read that always resolved to ‘true’...

But the good thing about this compilation step is that it traverses the entire game at once. All the little parts and pieces of the game can be checked together before the game actually runs. That’s very helpful for quality assurance.

Emergency patching from the cloud

As I said earlier, I had to submit to the jam a bit early, due to summer vacations. I prepared everything, pushed the release button on Itch and left home, thinking I was done.

I wasn't. Turns out that at least one user encountered an issue with the sword not working reliably, or even not at all with a specific emulator. That’s something I had encountered at some point but believed fixed when I did some optimization work.

It’s because checking for a button press compares the current state of the input with the one from the previous frame. At the end of each frame, I store the current state in the variable meant for the previous state. Simple double buffering, hard to mess up, right? Can you guess why it wouldn't work when the CPU gets too busy? It’s because the VBLANK interrupt will stop the update process right in the middle of its execution if it's not finished on time. It’s not too big of a deal if it interrupts the audio mixing, you will barely notice, but it can prevent the double buffering of the input, meaning button presses won't be detected.

So the fix was simple enough: move that to the beginning of the frame instead. It would have taken all of five minutes… had I been home with access to my computer.

The first issue was that I couldn't reproduce the bug. The only two pieces of hardware I had access to at the time were my Android phone and my SteamDeck, and both could emulate the game without a hitch. Any fix would be a leap of faith, though the solution was simple enough that I decided it would be beneficial in any case.

Using the GitHub website, I tweaked the code from my phone and pushed to main, hopeful it would compile.

But now… how to actually compile it? I first tried to install DevKitPro on my SteamDeck but it’s no secret that I hate Linux at the best of times and SteamOS isn't your average distrib. The whole filesystem is read-only, preventing the usual package managers to work, and of course there was no Flatpak for DevKitARM. I tried to get it to work with Docker but quickly gave up.

Plan B was a bit more costly: order a VPS (virtualized private server) from my usual web host. The scrappiest and cheapest option would do. For around 6€ I got access to a full-on Linux machine living in the cloud. I first asked for a Fedora installation, thinking installing DevKitPro would be easier, but I was severely mistaken and I quickly switched to Ubuntu.

It took a while. It was frustrating. I did not bring my Bluetooth keyboard with me so I had to type everything using the phone’s touchscreen (or worse: the SteamDeck on-screen keyboard). I wanted to cry and hunt Linus Torvalds for revenge.

But I managed. I finally got my .gba file and after a quick regression test with mGBA on the Deck, I pushed it live through Itch’s butler. Of course I didn't know if it actually fixed the issue I never managed to repro in the first place, but thankfully the user who reported it was super nice and checked it for me and confirmed it was indeed fixed.

Phew!

This whole ordeal illustrates to me how difficult gamedev can be on the go, which is a problem for a busy adult like me who’s not gonna be any less busy as time passes. This experience will definitely inform my choices when it comes to the ‘fantasy console’ I’m imagining: I would really like to move some of my tools to the web, but that tech stack isn't exactly great either. To be continued…

Design

Structure

The overall structure of Aereven Advance is that of a JRPG: you explore a gated overworld, one region at a time, to find towns and dungeons, including a big one that leads to the solution to reach the next region.

I very quickly chose to have “only” 3 major dungeons for this Jam game, though that’s already plenty and there was a time where I was a little worried I wouldn’t manage to finish them all. Which is kinda true as far as the fourth, optional dungeon is concerned, though we will discuss that later.

In the initial side-scrolling version, every region had one town, one major story-related dungeon and one minor dungeon with two levels. The second level would require the ability you got in the next region (backtracking!) to explore and find a health upgrade. Some of that is still in the game, but less formulaic.

Towns

Towns are for me some of the most fun things to design in such a game, thanks to the NPCs. Drawing those little 16px² guys is so fun and their single line of dialog is basically the only writing and world-building you can find in those games, so I cherish it. The main vibe I’m aiming for with Aereven is melancholy, but the NPCs are voluntarily a bit on the silly side to act as counterweight.

One regret I had with Lunar Wake was the lack of diversity for NPCs sprites, especially people of color. I did add a couple of black characters with the woodcutter and his son and gave them a big role in the story by making them the family of Jenna, former Pirate Queen (now happily retired) and best friend of Lloyd’s parents, but it was a bit too little too late. They ended up being the only characters of color in the game, and due to their profession they live outside town, which did not really paint the picture I wanted…

So this time I made sure to have diverse sprites for the NPCs, despite the VRAM limitations. The addition of the Dragonkins that didn’t appear in the first game also helped with that. I also made sure to sprinkle NPCs of different tribes in each town. Aereven is about the living fighting the dead, not each other, so it was important to me to depict that correctly, memory constraints be damned.

Dungeons

I feel like this game finally taught me what an Aereven dungeon is.

When I set out to make Lunar Wake, I thought I would just ape top-down Zelda’s dungeons. Especially the mix of puzzle and combat you can find in A Link to the Past onward.

But those never were my favorite dungeons in the Zelda series. What I like best are the ‘puzzle box' dungeons mostly found in the 3D games. Dungeons like the Water Temple, where you have to manipulate the environment back and forth to carve yourself a path through it. But props to Aonuma-san, they’re very difficult to design. Doubly so in a 2D game with a locked top-down perspective.


I never even tried to make one like that. Not in Lunar Wake and certainly not in Advance.

Instead, I looked back at my own work and realized the best Lunar Wake dungeons were basically mini-metroidvanias. They’re split into two different parts: in the first one you have to find a path amidst locked doors and seemingly insurmountable obstacles until you recover the dungeon item and the ability it grants. This ability removes the obstacles found in the dungeon, leading to the second part where you explore more freely to find your way to the boss. You have the same recontextualization you get in a Metroid game when you find a new movement ability.

In Aereven Advance, the Lake Palace is probably the purest example of this formula. You have to explore the four corners of the dungeon, avoiding deadly water, to find the keys that will open the path toward the item that allows you to swim. New paths are now open to you, mostly shortcuts but one of them leads to the key required to face the boss and complete the dungeon.

I really like this formula. It makes use of the experience I got making metroidvanias while still feeling Zelda-like. And at the same time, it moves Aereven slightly away from its inspiration and toward its own identity.

Sailing and post-game content 

Obtaining new means of traversal and getting to explore a vast world is one of the hallmarks of SNES-era JRPGs, but my chief inspiration was Golden Sun The Lost Age. I absolutely adored this game as a kid and a big reason why is the Lemurian Ship and the freedom it grants the player. With Aereven literally meaning “Ocean World”, it’s no surprise I wanted to reuse this mechanic. That’s the main reason why the overworld remained after I pivoted away from the Zelda 2 inspiration.


I wanted the boat and exploring the nearby islands to be optional but still important. Which is not exactly easy.

In a JRPG like Golden Sun, visiting optional locations reward the player with options and power ups for the turn-based battles: new weapons, armors, spells or summons. An RPG is a numbers game so it’s pretty easy to create all this extra content and make it at least situationally useful. But how to do that in an action-adventure game with a lot less items? I didn't want to always reward players with health upgrades, at least not with the “1 upgrade = 1 extra HP” model I used for this game. If an extra HP required four power ups, maybe… But even then, you need those extra HP to be rare enough to feel rewarding and not trivialize the game’s difficulty.

One solution was to add the quest for the three Hero Souls in order to gain the improved sword. But I knew it wouldn't be enough. Also, I was weary of players finding the improved sword too late in the game, long after defeating the final boss, making it useless. I had the same issue with Lunar Wake but it was alleviated by the addition of the Ancient Forge extension: with a second quest tackled after the final boss, you now had plenty of time to enjoy your fancy sword.

I basically chose the same solution for Aereven Advance.

Through the game, attentive players can find lore tablets. They serve three purposes: first, it’s another cheap way to reward exploration. Second, they plant the seeds for the twist at the end of the game, when the sleeping figure turns out to be a monstrous Shadow masquerading as your mother. The tablets tell the story of the dark sorcerer Vithra, how he ended as this unkillable Shadow and how your mother recently defeated him. But more importantly, they act as a hint toward a second ending.

When you first defeat the game, you get the sense you didn't do much better than the Three Heroes before you and that the Shadow will come back, one day. I should probably have left it at that, but I was afraid players would miss the hints so I took the coward’s way out and changed the assertive “The End” for “The End…?”. I feel it’s a bit heavy-handed but I didn't have time to properly reinforce the hints and playtest it. This makes the second ending quite a bit less secret.

One issue that made me choose this solution was that it can be difficult to understand the story told by the tablets if you missed some of them or found them wildly out of order. To remedy that, I want the PC version to have a menu listing all the discovered lore entries, similar to what I did for Silent Paradise. That way you can see what you’re missing and re-read the tablets in order. When I think about it, it’s quite similar to the Memories system in Breath of the Wild.


There’s one bit of content that was ‘cut’ from the game, or rather ‘never fleshed out as it should have been’, and that's Vithra’s Fortress, where you find the item required to unlock the second ending. My plan was to design this optional dungeon around the flute, an item used to warp around. Due to that, the flute is a complex system where target tiles will register themselves on spawn so that playing the flute can send you to the right place. But in practice the only warp target in the game is the one that leads inside the Fortress and then you instantly find the reward item. I have no idea what kind of teleporting puzzles I could have had in there. And it's precisely because I had no clear idea that I left this space unfinished. I will have to come up with a real solution for the PC version.

Another issue is that the final sequence isn't very different between the two endings. I first envisioned a second phase to the final boss, but I had no clear idea and no time for it (also my system only allows 16px² sprites and I would have really wanted a bigger monster for the occasion). I may still do that for the PC version, but otherwise having some illustrations at the end would be a good and relatively cheap first step.

Playsession

Getting volunteers to playtest my games is pretty difficult for me. It really shouldn't be, considering I work in the industry… But I’m on the shy side and can't bring myself to ask my coworkers.

I still got my partner to play the game, just before my self-imposed deadline. She's not exactly a big Zelda head, having only played The Minish Cap and the first hour or so of Ocarina of Time 3D, but that's exactly why watching her struggle was so helpful.

Yes, some parts of the game were too unforgiving, others lacked some quality of life features, but really most of the issues fell under the ‘signage’ and ‘tutorialization’ categories. After completing a bazillion Zelda games, we take a lot of its “language” for granted, but that’s an acquired literacy.

The Fae Forest was too easy to miss until I added a flower path to guide your eyes to it. Skeletons and mummies were the same color and easy to mix. The pyramid looked like a mountain on the small, barely-lit GBA screen, until I added pillars in front of it. What was your quest again? Why can't I kill mummies? And on and on…

Every interview I read from the Zelda designers mention Shigeru Miyamoto’s uncanny skill for seeing a game from a fresh player's perspective and at this point I’m convinced it’s the source of his genius.

Anyway, this first playsession did wonders for the game. This time there's very little written down in my Post-Launch To-Do list!

Conclusion

How did it go?

It was really fun. Both the initial research to get the tech up and running and then making as big a game as I could with it, were a joy.

What started as a simple, no-pressure proof of concept ended up becoming a real game that needed to hold up to my quality standards, and it mostly did! The hardware limitations did hamper the result and constrain the scope somewhat (due to my lack of experience and investment in it, mind you. I have yet to make a game that rivals The Minish Cap or The Lost Age, even on PC), but I believe it holds its own as a sequel to Lunar Wake.

Like I said in the postmortem for Silent Paradise, I don't join jams to win. For me, victory is adding a new game to my catalog. I’m writing this postmortem during the rating period so I have no clue how well the game will rank, but I know it’s been well received and that’s really all that matters to me. People having fun with it and engaging with it is all I ask for. I was even surprised by the number of downloads, considering how niche it is. I believed it would only reach a handful of players, but the GBA homebrew player base is bigger than I thought.


Where will it go?

Aereven Advance will be back.

You may have noticed on the box art and title screen the subtitle “Sleeping Azure” and wondered why I never used it when talking about the game. The reason is simple: while I always knew I would be making a PC version later, I knew I couldn’t name it “Aereven Advance” as well. It would make no sense. And I really wanted to keep the “Advance” in the name. Seriously, if I had made a Nintendo DS game instead, you can be sure the initials would have been “DS”. A SNES game? “Super Aereven”. This is too fun a trend to pass on. But that meant I needed a real name for the PC version, and as you guessed it, “Sleeping Azure” is that name.

So yes, “Aereven 2” will come back as “Legends of Aereven: Sleeping Azure”, on PC. At first I wasn’t sure if it would be a simple port or a complete remake, or something halfway. I’m still not sure where the cursor is, but it won’t be a straightforward port, that’s for sure.

For the series itself… Well, Azura’s shadow still looms large. I still want to make that big ‘mainline’ game I imagined 20 years ago. I said as much in 2020 in Lunar Wake’s postmortem and it’s still true. No idea when, or what it would look like.

What's not in the cards is making another GBA game. This was a fun programming and design exercise but not what Dreamnoid is about. It was more of a  “Do it once before you die” bucketlist item, not really something I’m interested in pursuing seriously. So that probably won’t happen again anytime soon. Never say never, of course.

And there you have it. Another game, another postmortem. Hopefully you got something interesting out of it!

See you soon (hopefully) for the release of Sleeping Azure!

Get Aereven Advance

Leave a comment

Log in with itch.io to leave a comment.