NESFab Post-Mortem

Over the past week I have been spending my time building a small game project using NESFab, a new programming language for creating NES games. I’ve written this post to capture my thoughts on NESFab for other developer who are considering using it in their next project.

My Game Project

The project I created is a short “score-attack” style arcade game, where they player tries to kill as many enemies as possible in 60 seconds. It features 2-way scrolling, background graphics, player and enemy meta-sprites, animation and basic AI. It targets the MMC3 mapper, but does not make use of scanline interrupts. There is no sound/music.

My goal was the recreate the core movement set of Ninja Gaiden with great attention to detail, so that it feels exactly like the original NES games.

I worked on this project full-time for 4 days, putting in roughly 32 hours.

What is NESFab

NESFab is a new programming language for creating NES games. Designed with 8-bit limitations in mind, the language is more ergonomic to use than C, while also producing faster assembly code. It’s easy to get started with, and has a useful set of libraries for making your first — or hundredth — NES game.

NESFab (pubby.games)

I would describe it as living somewhere between 6502 Assembly and a high level language like C.

My Background

The NES games I have written are mostly C using CC65, but in my latest project (Super Sunny World) I’ve done a lot more CA65 assembly. For the most part though, I am looking at NESFab from the perspective of someone who would be switching from C.

What Went Right

A Language Built for the NES

NESFab isn’t just a programming language that happens to work on the NES. It is built for the NES, and only the NES. This means that the way games are built for the platform can be baked right into the language itself.

For example, Fixed Point Math is built into the language! If you’ve ever tried to make any sort of smooth gameplay on the NES, you will have run into the limits of the console lacking support for floating point numbers, and probably started working with Fixed Point Math to compensate. This can be awkward to use.

In C, for an 8-bit position with a fractional part, you would probably store a 16-bit number, and do some funny logic to add fractional values and get the whole part of the number:

// Create a 16-bit position, and give it a whole value of "100" by shifting
// "100" left 8 bits.
uint16_t position = 100 << 8;

// Add the equivalent of 0.5f to the position
position += 128; // 128 = 255 * 0.5

// Get the whole part of the number
uint8_t screen_position = (uint8_t)(position >> 8);

In NESFab this looks more like any language that actually supports floating point numbers:

// Create a 16-bit position, with an Unsigned byte and a Fractional byte.
UF position = 100

// Add 0.5f
position += 0.5

// Get the whole part of the number
U screen_position = U(position)

For me, the NESFab version is much more readable.

Fixed Point Types are just one of a number of language features that are custom built to target the needs of an NES developer. Some other language features that stood out during my time with NESFab:

  • Automatic bank swapping
  • Easy register access: {$8000}(128) // write 128 to address $8000
  • “nmi functions” treat NMI as a first class feature of the language

Simple Configuration

Picking a mapper and setting up memory layout in CA65 is super confusing to me, and I think for a lot of people it’s become mostly an exercise of “copy & paste from a project that seems to work”.

The entire memory configuration for my MMC3 project in NESFab is this:

mapper = mmc3

Runtime Performance

Unfortunately I haven’t had a chance to profile my project, and compare it to a similar C project, but if the NESFab creator’s tests are correct, NESFab is faster than every C compiler out there. I suspect this is going to be the main draw for most people so I wanted to call it out.

Standard Library and Examples

When a feature isn’t low level enough to be built into the NESFab language itself, the authors have supplied a huge set of standard runtime library functions, as well as wide range of example projects.

The standard library includes lots of functionality specific to game development. Things like object pooling, collision routines, compression libraries, mapper-specific registers definitions, common lookup tables, and more!

In addition to this, there are a dozen or so example projects. Each example is very short and specific to one topic, and usually are less than 100 lines of code. Almost every time I wasn’t sure how to do something in NESFab, there was an example project that demonstrated it.

I found the most useful example for me was the Platformer example, which covered a lot of the kind of stuff I wanted to do.

NESFab Platformer Example

What Went Wrong

Before we get into what went wrong, I want to preface this by saying I thought NESFab was great, and I think more developers should give it a try! There are always going to be problems with a new programming environment, and NESFab is improving constantly!

Bugs

I would describe NESFab as a “work in progress”. It has reached the 1.0 version, but I don’t think it has been pushed very hard yet, so there are a lot of little kinks to work out still.

To give a sense of the types of bugs I was seeing, here are a few that I saw in my time with NESFab:

  • Labels are pointing to the wrong address.
  • Bug in MMC3 mapper support, causing a crash when writing to register sometimes.
  • Assigning a value just doesn’t work.

Now, that being said, every issue I had was fixed almost immediately, so that was amazing, but if you pick up NESFab there is still going to be a lot of time spent debugging stuff that might actually just be a bug in the language itself.

Unreadable Assembly

The code generated from NESFab can be very hard to decipher. Even more so than CC65. With CC65, there is very little optimizations done, and while the assembly it generates will often be doing strange things, you can at least map it 1 to 1 back to the original source. With NESFab, that isn’t the case. Your source code is dramatically different from the final output, because of the optimizations it performs.

Instead, the way to debug NESFab at the moment seems to be mostly using old fashioned “trace” or “print” statements to try and see what is going on. This gets very difficult for more complex bugs.

I think improving the debugging workflow would be a major improvement to the library at this time.

The Library is a Black Box

When something goes wrong with NESFab, you aren’t just debugging your code. You will be debugging the NESFab itself. Your game could crash because you wrote some bad code, or it could crash because the code NESFab generated is bad. NESFab uses a custom assembler, so at the moment this basically means asking the author to debug your problem, which doesn’t seem sustainable. I also worry about what happens if the author just stops working on the project.

Steep Learning Curve (Especially with Data Definitions)

NESFab really is a unique language; more so than any language I’ve ever used since learning C++ in university. It’s not like going from C to Python. I would still describe NESFab as “C-like” but it also isn’t in a lot of ways, which can be a little tough to grasp at first.

This is most prevalent for me in defining read-only data.

Here is an example of defining a meta-sprite for my game, and then declaring a pointer to access that data:

// data is read only data in ROM, and /sprites 
// is the name of a "group" it belongs to.
data /sprites
    // Here's where we define our metasprite, comprised of eight 8x8 tiles:
    [] ryu_01
        (make_metasprite(0, Ms{}(
         Ms(1,  0,$02,0),
         Ms(9,  0,$03,0),
         Ms(1,  8,$12,0),
         Ms(9,  8,$13,0),
         Ms(0, 16,$22,0),
         Ms(8, 16,$23,0),
         Ms(0, 24,$32,0),
         Ms(8, 24,$33,0))))

// constant array of 24-bit pointers (bank + address) 
// to data found in the /sprites group
ct CCC/sprites[] meta_sprites = CCC/sprites[](
    @ryu_01,

For me, this is quite hard to understand, and I am not sure I fully do yet. Of course, pointers are always one of the more complex parts of any language, so maybe that’s expected. It’s also important to note that I am defining essentially “binary data” in human readable format, which is pretty cool!

Not Portable

As mentioned, a strength of NESFab is that is it built for the NES, and only the NES. This means that the code you write will not be portable to other platforms. For people using assembly, that’s mostly par for the course, but I use C, which has allowed me to port the same game code from the NES to the Game Boy and to the Genesis, with relative ease.

Conclusion

Overall, I really enjoyed using NESFab over the last week! I’ve been really happy with how much I’ve accomplished in a relatively short period.

Now the question is, will I use NESFab for my next project?

For now, it’s a maybe. I don’t think I would jump into a large scale project with NESFab at this point, without doing a more full-featured smaller project first. Ultimately my main interest in NESFab is to gain significant performance improvements over C, while keeping the C-like programming style (especially for gameplay code).

Building a medium sized project that I can benchmark a bit more, and really figure out how much faster NESFab is will be the determining factor. If it’s a 5% performance increase by I lose portability and stability, it’s probably not going to be worth it for me.

Thankfully, the 2023 nesdev competition is just around the corner, and NESFab has a custom build pipeline to target the requirements of the competition! This is a perfect opportunity to take my experiments with NESFab further, and I hope more of the nesdev community will join me on that!