Six

There’s a quote that is usually misattributed to random geniuses of the past that says something in the lines of “What’s madness if not trying something again and again expecting a different result”. This blog post is about that and my obsession in cloning Mother 3’s battle engine.

Not as really pretty as Four but at least it shows what I need to see.

Current look of the project

Not as really pretty as Four but at least it shows what I need to see.

And it doesn’t stop coming and it doesn’t stop coming…

(Alternative take on the title : “Déjà Vu”)

People who know me will know that Mother 3 is probably (if not) my favorite game… And the paragraph that was there was just removed because we’ve been there in the previous post. See, I had a few sentences and a listing, but let’s change the plans and talk about the original game for a bit until going back to Six.

But first, let’s talk about the battle engine

It’s a classical turn-based RPG system with a twist : the player characters' life and PP (the game’s equivalent of mana, mind your mind) are changing in realtime instead of changing instantly after an attack. This little quirk allows for late game strategies like taking mortal hits and shrugging them off with a simple healing before the HP drops to zero. A critical phase of a fight turns from a fight to a choregraphy as the player hastily decides and selects the next turn’s actions.

It sounds simple, yes, but that small feature can add a good layer of complexity in a classic battle loop. If you want to program such engine, you’ll have to answers to questions like “What happens if the character you’re currently selecting the action for falls unconscious?” or “is the PSI attack still being casted after the caster is knocked-out?”.

If you look at the original game, it hides a few implementation details about that here and there, like adding a failure message on the casting textbox or halting an attack wave between two attacks. The engine is actually acknowledging pretty well the fact it’s happening in real time and I ended up loving that feature.

$ info bash

I will not talk about the standard bash attack which can be summed up as an enemy beatdown on the battle music’s beat as it’s still a kind of mystery for me to reproduce “perfectly” due to audio/video synchronization woes. Play the game in an emulator (as you probably will, Mother 3 being a Japan-only game) and if you are used to rhythm or fast games, you might feel the input/output desync and trip on the harder music beats (Strong One, my 5/4-5/8 love). It usually end up strategically skipped in my plans because I don’t really want to go deep in that kind of mess. Yet.

<Insert a Rust pun here>

So, I have been passively looking at Rust for a few years now, and only after a few attempts at groking the way its borrow checker works, I wanted to give it a chance in gamedev, because why not? Let’s look up at one graphics library that doesn’t look not too complicated to prototype with and let’s rock! Tetra by 17cupsofcoffee it is.

The experiment quickly turned into getting the hammer syndrome after looking a bit too much at the Rust enumeration types being an Algebraic Data Type, which is very roughly what a std::variant looks like if the type was fully integrated in the language or how a C union looks with built-in type flagging. A bit more precisely, an ADT is a sum of types where each of the enumerated types can be a parametric type, having constructor arguments to let them store extra data.

enum Card {
	Ace,
	King,
	Queen,
	Jack,
	Number(u32),
}

// Let's draw a hand.
// Adding color is left as an exercice for the reader.
let ace = Card::Ace;
let a_two = Card::Number(2);
let king = Card::King;
let a_four = Card::Number(4);
let another_king = Card::King;

Rust, inspired from languages like OCaml, also includes pattern matching to easily select and destructure or reference a structure’s fields. Those two concepts, coupled together, made me eye upon the idea of using enumerations as a base for state machines. And I ended up trying to make a small project to see how convenient (or not) that would be. I mean:

How could it possibly go wrong? – Bubsy, supposedly

The result? I think I currently have at most three (but actually 2 for the battle engine) levels of finite state machines for an infinite level of !!FUN!!: the game’s own states, the battle engine’s “macro states” (turn action selection, turn unroll, win, gameover, etc…) and the macro states’s own states (for instance the menu’s UI). I tried to formalize the possible state transitions to keep track on how it’d work, but it only described the macro states1. Fortunately, barring the action selection menu and the K.O. detection, the machine is quite straightforward: a turn always happen in a globally constant sequence:

00 - Select actions for turn
10 - Turn begins
20   - Take an action
21   - Unroll action
22   - IF actions remains GOTO 20
30 - GOTO 00

So, if the structure sounds easy to make, what went wrong?

The Good, the Bad and the Borrow Checker

Rust is more strict than C++ on the rules surrounding mutability. It’s not a secret, it’s a selling feature. And my biggest obstacle comes from the way I was structuring a lot of my prototypes: over-estimating what the interface requests and ending up locking myself in double borrows. Let’s have a look at a specific instance of that in my code.

A battle is divided in two teams of “Actors” with their own stats. Each turn we order them by their speed stat and the engine empties the action queue an action at a time. To simplify myself the future actions design part, I wrapped them behind a Trait (rough equivalent of pure abstract class/interfaces) that’d take the battle scene, a reference to a caster and a reference slice to the targets. I ended up with a function looking somewhat like this:

trait Action
{
    fn go(
        &mut self,
		// The battle engine's main structure
        scene: &BattleScene,
        caster: &Actor,
        targets: &mut [Actor]
    );
}

// ... Seen in the calling code
	let caster: &Action: scene...;
	let targets: &mut [Actor] = scene...;

	let action:Box<dyn Action> = ...;
	action.unwrap().go(&scene, caster, targets); // BOOM

BasicScene is a gather-all structure that contains notably two lists of Actors to represent both sides of the fight. And the compiler complains of simultaneous borrowing of the BattleScene and a part of its internal. And I’m lost on how to figure that one out for a while.

Doing a mistake and learning from it

That’s a mistake I tend to make and make again. Here’s my (C++) flawed logic : I have a const reference to scene and caster, so the code in Action cannot affect them, that’s the contract. But the thing is targets can contain the Actor referenced by caster and I cannot have an immutable reference over something that’ll mutate entierely or partly, so here I cannot borrow scene, even immutably, not because the code won’t impact the structure through scene, but it might through targets.

That’s a trick to learn if you want to migrate from C++: the way I grok it is that immutability is two-way : I cannot alter the value behind caster because it’s immutable but I cannot borrow scene as immutable either because it’d break the immutability contract on the other side of caster. There are a few solutions to that, like reducing the scope of the borrowing or copying instead of borrowing. For the caster, I decided to copy its stats instead, as they’d act as a snapshot of the actor’s required stats before any alteration during or after their action.

That’s just a simplified version of the issue I went through a few times so far. I find quite interesting the way that Rust’s own design pushes naturally towards some specific constructs. After a few rounds of tripping on this tripwire, I got to accept that I’ll do some copies here and there as it both yields code easier to support and read and taxes less my mental health.

Enumerate your ways to overdo it

Just to go back on enums for a small paragraphs or two. The state machine should have been an alert but I still dug deeper and deeper until I met my own Balrog. Please don’t make the same mistake than me and end up using them at every occasion. They’re working pretty well to to build types of the category Either or to build a sort of inheritance/grouping on otherwise distinct types but at one point they can become ludicrous.

Just to give you a small example out of the codebase:

pub enum Target {
    Single((Team, usize)), // Actually the tuple is a named alias. 
    WholeTeam(Team),
}

impl Target {
    pub fn get_index(&self) -> Option<usize> {
        match &self {
            Target::Single((_, id)) => Some(*id),
            Target::WholeTeam(_) => None,
        }
    }

    pub fn get_team(&self) -> Team {
        match &self {
            Target::Single((team, _)) => team,
            Target::WholeTeam(team) => team,
        }
		// Oh, yeah, I'm both impressed that it works and disgusted of my own code.
        .clone()
    }
}

The idea of either aiming for a single target or a whole team might be semantically correct, I had to write helpers to extract common data out of all types because it’d have instead translated into having those matches every time I needed them. I belive something like a struct with the team factorized out of the ADT could make my life easier, I have yet to do the migration (I have other tasks with lower priorities to do).

I’m reading up some reverse-engineered documentation on the structure of battle actions, well the game uses a more complex enumeration of the different target schemes an action will use (n random targets, only first ally slot, etc…).

The question of the trait TargetSelection

That trait there is probably a point in the project where I felt very smart but later am questioning my own decisions.

In Rust, traits can be seen as the equivalent of Java’s interfaces or C++’s pure virtual classes. They can define a contract as any implementation on a struct will enforce at least implementing all the methods to implement. Also, in a way, they can be used to gather common code behavior, simulating a one-level-deep polymorphism (the famous “dyn Traits”). Deeper inheritance can be realized by aggration (“has-a”) where the child class will have its parent as a member. Such method is rather clumsy in Rust as it forces the child to reimplement all the “public” inherited methods.

In my situation, I already overuse static variant dispatch by abusing enumerations, so I don’t need inheritance to provide common code or a type to hide the other ones. I’m using a trait what it feels like the opposite way one would use.

The target selection submenu is just a way to select the targets of an action. You can select one or many targets, aim both teams or skip K.O. characters. And somehow, I got the idea of creating multiple structures sharing the same base code (mostly the navigation and input handling) and making them only implement a few tidbits like providing the navigation settings.

See it with a shortened example:

trait TargetSelection {
    fn current_target(&self) -> &Target;

    // Pass the "moved the cursor left/right" event to the "child" class to let it accept
    // the state change.

    fn cycle_selection_left(&mut self, possible_targets: &[Actor]);
    fn cycle_selection_right(&mut self, possible_targets: &[Actor]);

    // By default aim enemies. Special cases (like healing techniques) will override this.
    fn get_targets<'a>(&self, _allies: &'a [Actor], enemies: &'a [Actor])-> &'a [Actor] {
        enemies
    }

    fn update(&mut self, ctx: &Context, allies: &[Actor], enemies: &[Actor]) -> Transition {
        // Draw the rest of the owl
    }
}

And an implementation would just implement the small methods and everything would work fine and dandy. Code repetition is minimal and I have two separate states for the action target selection! But now that I’m writing this, two question rose in my mind:

  • Why not just merging both classes seen that there’s only a marginal difference?
  • Is it really good code? I’m using traits to implement an advanced altering behavior. I mean, the meat of the sausage is there, not in the children.

And after looking the reverse-engineered data, well, this would be a bit on the lacking side. I might need to either rewrite the classes or drastically restructure them. Over-engineering is my passion.

But are you a programmer?

At least, I feel like I am one. I’m progressively getting my shoes fit in the language and am trying to iterate through bad ideas until I get just what’s needed to make it work. I’m keeping a trace of the changes and the bad ideas a bit like a post-mortem except I’m still deep in the creative process. Like the people who film themselves doing dangerous actions, I’m writing a “been here done that, don’t do it.” post and maybe less people will do the same mistakes as me?

Also, been a while I haven’t put my over-thinker cap for personal projects, I almost missed that feeling.


  1. I did hack some bits on the realtime character detection and I somehow fell into what I described, I am shocked. ↩︎