Attribution solution - GBA Rust adventures, part 5
8 minute read Published: 2020-12-31I am not a lawyer, and this post does not constitute legal advice.
Complying with intellectual property law is hard. Surely we can automate it, right?
I am not a lawyer, and this post does not constitute legal advice.
Complying with intellectual property law is hard. Surely we can automate it, right?
Well, after the third post in a row that's been primarily about low-level graphics programming, it's finally time to take a break from all that and think about music and sound mixing. Anodyne has a lovely soundtrack to help drive the mood of each distinct world in the game, and much of it is subtle, ambient, and effects-driven. It's really important to me that we do it justice in this port! In the original game's assets, the MP3s used in the game take up about 53MiB, all stereo and in the ballpark of 100kbps. We're clearly not going to use those directly--even if we wanted to and had a fast enough decoder, our cartridge bus can only address 32MiB of ROM! Let's take a look at our options.
Yes, "Affects." Anodyne is an atmospheric, dreamlike game, and while proving that we can fit all its levels' tile data into the GBA's background VRAM may be a nice goal to have achieved, we're not really done representing them properly until we can give them the right humidity. For instance, just look at the header image above: On the left, I see what looks like a body of water, but that land above it is clearly sun-blasted and bone-dry! By contrast, the official game on the right looks like being in a damp forest shaded by tall trees feels. Porting a game is more than its assets and engine, you've gotta translate the feeling over as best you can too!
In our previous episode, we talked about some of the basic concepts we met along the path from initial project conception to the first point of drawing-stuff-on-screen milestone. We also ended on the fact that the quick-and-dirty approach taken to get there wasn't going to be sustainable even in the short-term due to some limitations of the approach I was taking, especially when the prospect of modifying a relatively sophisticated C program and writing an entire asset pipeline around it in Python would surely be a death sentence for a free-time project that was meant to be an excuse to write some low-level Rust.
In addition, while grit
was doing a decent job of producing the graphic and palette data I needed, the tasks of managing the increasingly-complex CLI flags I needed to pass it and writing GBA-side code to use the binary data I was producing with it and Python (both coming with challenges of troubleshooting cases where the output of one or the other wasn't what I was expecting) were both forcing the issue.
So what do we do when grit
isn't enough?
We apply some elbow grease and Rewrite It In Rust™️ in an afternoon!
Back in April, one of my favorite games got open-sourced: Anodyne, a Gameboy-era-Zelda-inspired game originally made in Adobe AIR and Flixel by the creative and talented duo that make up Analgesic Productions. It has a low-res, 16-bit pixel-art aesthetic, and by my own judgement looks absolutely beautiful within those self-imposed constraints.
Since the pandemic had just gone viral a month prior, I decided to keep myself occupied by spending some of my free time seeing how much of this game could actually fit within the hardware limitations of a console with 16-bit graphics - specifically the Gameboy Advance, which is a two-decade-old handheld computer powered by a 16MHz ARMv4T CPU and a custom tiles-and-palettes-oriented "Picture Processing Unit" (PPU), with under half a megabyte of RAM and up to 32MiB of cartridge ROM.
A persistent fascination (and habit) of mine seems to be making weird new ways to experience older video games, for instance building new co-op mechanics into single-player games or writing a program to path-find its way through them. Recently, I've made another entry in the list of things to which I've bolted an emulator: The ActivityPub fediverse (colloquially "Mastodon").
We'll talk about that in a moment, but also: I've been wanting to build a way to let other folks in on all the fun I've had doing these sorts of things, without everyone having to have as deep a background in all the different disciplines necessary in order to navigate all the technologies involved effectively enough to do so. Video games can be art, and are indisputably important to our shared culture, but are usually much harder to sample and remix and otherwise recontextualize compared to traditional visual arts, written word, and music. I'd like to use the scaffolding I've built for all these weird projects to change that. For this, I would need an elegant way to compose pieces of complex functionality together in a way that won't make the programmer overheat.
So we'll talk about that too, and the high-level design thereof--but first, let's look at some fun robots!
Inspired by the recent "emu-coop" project by Andi McClure, my friend Viv and I took the old codebase of the Optiness project (primarily for its Python FFI bindings to the libretro API and its abstraction over accessing the emulated system's RAM) and repurposed it to implement something similar, which we called sync-or-sim.
The realization of the previously mentioned tool-only speedrun generator idea is now well underway, thanks to the handy libretro project(s) spun off from bsnes's "libsnes" that implement emulators for retro game consoles in terms of a general-purpose (though heavily SNES-influenced) API.
The basic idea, to summarize the story so far, is to treat the emulated game as an implicit graph, and use one of a number of graph searching methodologies on the "savestate space." Suppose we have a "successor" function NextFrame(Sn, I) that takes the savestate Sn and the input state I, and returns the result of loading Sn in the emulator, running the game for a fixed amount of time with input I, and saving a new state Sn+1. Here's an example of the expansion of an initial state S0 with all the input possibilities of a simplified controller with only an "A" button that makes Mario jump, and a "right" button that makes Mario move to the right.
To express this in (Python-like) pseudo-code, we might say:
B = {'A', '>'} # the set of buttons to consider.
def Expand(S):
for I in powerset(B):
yield NextFrame(S, I)
2020 note: This was rescued from an old WordPress blog I kept during college, and was written with an intended audience of classmates pursuing STEM majors that aren't necessarily computer-related.
A line I picked up from one of my high school Computer Science teachers: "Graphics are memory hogs." Computers represent pictures as a big grid of colors. Each cell in this grid is called a pixel (short for "picture element"), and usually has an ordered triple of numbers representing what color it should be. When these pixels are very small and numerous, the picture looks less like a collection of squares, and more like the picture represented. A similar effect is used to shade and color graphics in newspapers; look closely enough and you'll notice that the colors are made up of lots of tiny colored dots. Let's look at an example...
Some time ago, navaburo and I were hanging out in my dorm room, and we ended up thinking about the concept of writing an AI to find the best (shortest) path through a video game. A couple days later, we went down to our CS department's Linux Lab and fleshed out the idea on paper.
2020 note: This was written in 2006, before the R4 and such existed, and is generally severely outdated. I'm putting it here as a bit of history for anyone curious about that sort of thing, as many of the resources out there are probably biased toward the off-the-shelf solutions that came afterward. I think I originally wrote this to try to explain to various classmates and online pals back then what was going on with the whole "running my own programs on the DS" thing without pointing them anywhere sketchy.