Lots of people are still interested in this so here's a quick answer to the inevitable question:
Why ~~can't it be~~ was it so difficult to emulate?
- We use custom microcode. I wrote the 3D rendering microcode and Todd wrote an *entire MPEG player including custom microcode*. Todd wrote a piece on gamasutra about his journey getting the FMV to work on the console, but it looks like it's ~~vanished, sadly.~~ [available on the wayback machine](https://web.archive.org/web/20080331015735/http://www.gamasutra.com/features/20001004/meynink_01.htm).
- I reverse assembled the operating system, modified it, then reassembled it. My version used the full 64bit capabilities of the R4300, and it handles task switching differently. I built a modified GNU compiler just for the N64's R4300.
- When video is playing, we effectively turn off the operating system by hijacking it completely. Debugging that, plus a well timed Onion article, and lack of sleep, led to about thirty minutes of hysterical laughter. I won't tell you what that was, because it's just not funny to anyone who wasn't there, even though, twenty+ years later, it still makes me laugh.
- We assume the output is a CRT and we *race* the raygun to the bottom of the screen by starting to clear the display buffer *before* that screen has finished being displayed.
I'm giggling right now. We did so many mad things to make this game work. It's no wonder none of the emulators have got it working.
## The Story
This is essentially unedited. I'm autistic. This is an info dump. I hope you like text. Also, I didn't *know* I was autistic *then*, so imagine what fun that was for everyone.
Chris and I were roommates - we shared an apartment, and we shared an office at Angel Studios. Sometime in '97 or '98, we realized that we really didn't know how the N64 worked. We were just using other people's libraries, or an in-house one that ran really well on half-million dollar SGI Reality Engines. After joining Angel Studios, I'd worked on "unnanounced nintendo game" and when that got cancelled, then I worked on another "unnanounced nintendo game", and I noticed that there appeared to be zero consequences for failure. Chris and I figured that we did in a day what some other programmers were doing in a week. So we figured that if we did just one day of what we were supposed to be doing, then we'd be no more at risk of getting fired than these other people. And that's what we did. And then the other six days of the week we, essentially, reverse engineered the Nintendo 64 and played Age of Empires.
One of the things we did was start digging into the debugger that came with the SGI. It was clear that the debugger *could* debug microcode - it just wouldn't. But if it could, then it has the opcodes and the way to decode them. Dave really helped here when he chipped in "just run strings on it", which we did, and hey look, here's all the opcodes for the RSP. In lucky timing, some folks from NTD (I think) were visiting, and we showed them what we were working on, and we were like "ok, look, we're gonna figure this out, but it'd be better for everybody if you just had us sign the NDA and give us the development kit." And they did! That was a really great day.
When it came to the CPU, it frustrated me that we didn't have great timing information about what was going on with our code (any of Angel Studios code). Its only got a single CPU core, so it's preemptively multithreading, and Angel's engine had a bunch of threads. Timers said that the physics thread was taking 40ms, which is far too long. But how can we trust this timer? Something could be interrupting it. Rather than sprinkle the code with logging, I decided to go to the source. I found the piece of code that handles switching threads. And then I wrote a reverse assembler to turn that into an assembly file that I could modify and then re-assemble. I added a simple hard-coded ring buffer to store timing and thread IDs whenever the code switched threads. This was invaluable information. And then, Patrick took the ring-buffer and *rendered it on the screen*. This was a game changer. The data we recorded showed that it was the *audio* thread that was running for 30ms, interrupting the physics thread. But *seeing it on the screen* just gave everyone an undeniable, visual experience. Thinking about it, this must have been before the second unannounced game because I remember it really helping with the Ken Griffey Baseball frame-rate: you could turn and look around the stadium and see the render thread jump as really complex geometry came into view. I'll also never forget the CTO telling me to stop working on it because it was a *waste of time*. That was about a week before Patrick got it on the screen and then it was just permanently on everyone's screen as they worked. I love people telling me what I'm doing is a waste of time, right before it turns out to be amazing. (I don't. It just gives me the fear. Only later does it feel good.)
Another thing that was annoying was the SGI C compiler. It had two compiler targets. One was for the R3000, a 32bit CPU, and the other was for the R4000+, 64bit CPUs. The two compiler targets were either 32bit or 64bit - and you got either 32bit or 64bit pointers. The Nintendo 64 was 64bit, but with a maximum of 8MB of memory, it would never need 64bit addresses: using 8 bytes to store a pointer would be a massive waste of (haha) memory! If we used the 32bit compiler, though, then we can't use 64bit registers when we want to, and we lose out on the extra floating point registers. I wanted my cake. I grabbed the GNU C++ compiler, and figured out how to configure it to give me 32 bit pointers, 64 bit integers, and all 32 (31?) floating point registers.
Now we had a new problem. With the Nintendo 64, the code runs in the CPUs operating system mode - and you link your game code with the operating system. Well the operating system wasn't compiled with my shiny new compiler. *It* thinks its running on an R3000. And so when it is saving floating point registers, it's trying to save two 32bit registers, when it only needs to save one 64bit register. (I actually have honestly forgotten the details at this point, otherwise I'd have no problem boring you with the details). I do remember the solution though: use the reverse-assembler to read each operating system object file into memory, and modify how it handles floating point registers, so that it is now compatible with the GNU C compiler I've built, and then write it back out. Yes. I ran a code transform on the Nintendo 64 OS object code. Now we've got a true 64bit operating system using *all* the 64bit capability of the R4000-based CPU. (This *might* also be another reason emulators ~~don't~~ didn't work). This bit was so mad that I'm not sure we actually shipped with it, TBH. This was 30 years ago now, so IDK.
The R4300 was something of a weirdo. The manual for it (which I got for free from a super helpful lady at MIPS) states on the front cover that its basically an R4000 with a nerfed FPU. Angel Studio's game engine was super floating point.
We were sure we wanted to write a new game engine. As we learned more and more about the Nintendo 64, at some point we had the idea to treat the whole system as a dependency graph, with the CRT raygun as the arbiter of time.
This idea was huge. It was literally game changing. I don't remember which of us had the epiphany, but it doesn't matter. Chris and I were operating as a single brain at this point - so *my* brain remembers that I said it first, but genuinely it could have been Chris who said it first, and my brain just remembers that is "me". So, it was our idea. Some of the things we can point to concretely as I did most of that or Chris did most of that, but I think this breakthrough that we arrived at was both of us, as were most of the important ideas.
At the time, games had one of those "this is how it works and everyone knows this". At the start of a frame, you read the controller, and then you update all the enemies, and then you create a display list to draw the things on the screen. Then, you wait for the vblank, i.e. you wait for the *next* frame, and *then* you send that display list to the graphics hardware to start drawing it to the back buffer. There are two "frame buffers". The front buffer is the one that the hardware is sending to the CRT. The "back buffer" is the one you are working on, to be displayed on the next frame. While that graphics hardware is rendering this new frame, you read the controller again and start calculating a new display list. On next vblank, you flip the back buffer to the one being displayed by the CRT, and give the graphics hardware your new display list to start drawing to the new back buffer. It's called "double buffering". If your game is running at 30Hz, then there are two 30Hz frames between when you read the controller, and when you display it on the screen. That's 66ms. That's kinda forever. Some games had triple buffering!
What we did, is we identified three key moments in the render loop: when we read the controller, when the CRT raygun reaches to the top-left corner of the screen, and when the CRT raygun reaches the bottom-right of the screen. When CRT reaches the top-left of the screen, you'd better be done rendering the frame that its starting to display. Thinking about it this way led to a number of insights.
We wanted to minimize the amount of time between when you read the controller to when you see the result on the screen. Or, put another way, to maximize the time you have as a human, with your slow human head, to react to what happened on the screen, or in the audio (because that is a thing). Reading the controller later means the human has more time, but the computer has less time to draw.
So we identified the dependency graph of "what needs to know about human input". Turns out, in a 3D game, it's "just the camera and the player position". The AI can totally work with what you did in the previous frame - because, hey, maybe the enemies are slow humans and we're just simulating reaction time! Look, we made our game more realistic!
The other thing we realized was that bit about the CRT. Did you catch that? At the time, people thought about the frame buffer swap as an instant in time. You're displaying this frame buffer, then the vblank happens, and you swap to the other frame. But when you break out of abstractions, what is physically happening is a cathode ray gun is beaming electrons horizontally, moving down the screen one line at a time. And it takes 16ms to do that. There is no "frame flip". There is a single beam pointing somewhere on the screen, and it moves in a specific pattern.
That means, that if it takes you say 1ms to clear the screen to black, you can start that *before* the ray gun has reached the bottom of the screen. You can start erasing the *front* buffer while it's still, conceptually, on the screen. But it isn't.
Now hold on a minute. Surely that means that you less time to draw the previous frame, and in fact you've effectively delayed its display, which is the opposite of what you just said. ==And that would be true if every frame took precisely 16ms (or 33ms) to render.==
Leveraging this breakthrough, however, was entirely Chris. He built a working demo of this concept. The demo ran it 60Hz, and felt even more responsive than that, because of *when* it read the controller, vs updated the AI.
He also presented it masterfully. We got all the programmers at the company in conference room, and presented the idea. The response was unanimous from all the wise engineers in the room. It can't possibly work. Here are all the mistakes you've made and things you haven't thought of. Of course Chris could then reply, "well, that seems that way, because that is how it works in the old model, but in the new model this is how that works." and then there was more nay saying. It was great. And then Chris announced, "And here it is working. Who wants to play?"
In that one meeting, Chris invited all the nay-sayers to commit to their saying-nays, and they did so with vociferous glee. Then he showed everyone the nay-sayers were wrong. Without a doubt, Chris' engineering, coupled with his masterful reveal, is what caused this story I'm writing about to happen. I am a massive nerd. Chris is a massive nerd who also gets people.
Shortly after (like in my mind it was the next day, but it must have been weeks?), Diego Angel showed up in our office, with a choice. Did we, Chris and I, want to work on a licensed super-hero title, or a port of Resident Evil 2 for the Nintendo.
Obviously, we should work on a new super-hero game! Who wants to work on a *port?* Eww. But there was a problem with a new super-hero game. We were two *programmers*. As gamers ourselves, we knew that we would need great game designers, and Angel Studios didn't have any of those available. The amazing game designers they had were hard at work on their *second* unannounced, and also eventually cancelled Nintendo game.
Now, at that moment in time, I had gotten involved with Landmark Education. I had just finished a course about setting specific, distinct, and measurable goals. I had created a goal for my next project, printed it out in like 40 point font, and stuck it to our door. It said:
>By October 1999, we will have published a game that people love. We will know that they love it because it will sell one million copies.
Before leaving us to think about our choice, Diego told us that Capcom wanted the port done by October 1999. He left, closed the door, revealing my goal hanging there like a prophecy.
Resident Evil 2. Suddenly, this wasn't a port. This was a game where all the game design was done! All the graphics were done! The music was done! The *only problem* was that *everyone* knows that Resident Evil 2 will *never run on the Nintendo 64*. Seriously. On the 1990s equivalent of twitter (usenet), the console wars had a mic drop in any argument about which was better: "You'll never get Resident Evil on the Nintendo". Resident Evil 2 is *two* full CDs, each 650MB of data. The biggest Nintendo 64 cartridge was 32MB. Two CDs is *forty* times bigger than that. It's impossible.
Impossible you say? Flashback: one of the published (in a magazine!) reviews of my first game includes this line, describing the graphics: "This is impossible - you can't do this on this computer!" *Impossible* is my bag, baby.
I think the thing that tipped it was that we knew the Playstation didn't have great video compression. It was M-JPEG not MPEG, so basically just frames of JPEG. And by that time, we knew that the RSP had a dedicated instruction used for video compression. So I thought we could get much better compression, and so *maybe* we could get *some* of the movies on the cartridge? (I mean this might not even be how the Playstation worked, but we *believed* it.) I'd also been playing around reading and implementing papers from 1986 by McAuley and Quatieri and thinking we could probably really compress the audio too. (We didn't use it but I was right about compressing the heck out of it).
We also heard about the two previous attempts. There are youtube videos saying we had two years to make the game, but Capcom didn't come to us until nearly a year after RE2 was released. We only had a year, but we had two paths already eliminated for us by the previous trailblazers.
So we said yes to Resident Evil 2. Chris got the title of Project Director ([check it out](https://www.mobygames.com/game/3714/resident-evil-2/credits/n64/a) on mobygames). I got the title of Lead Programmer. This was perfect. Everything was perfect. Oh, but shit, it's October 1998 and we got a year to do the impossible.
### Everyone wanted to make this happen
I think here is a good time to write about how *everyone* wanted to make this happen (ok, maybe not Sony). Nintendo really wanted this to happen. Capcom really wanted to make this happen. And others wanted to be a part of making it happen.
### Making the game
So this is real now. By Christmas we needed a working demo of the game up to the scene in the gun shop where the owner gets mauled. We have a bunch of CDs and DAT tapes (they aren't DAT tapes, they are backup tapes, but in my brain they will always be DAT tapes, because I bought a [Sony DAT-Man](http://www.thevintageknob.org/sony-TCD-D3.html) with my very first US paycheck).
Anyway, that was our first problem. We didn't get the source code. We got *both* source codes. Both? Yeah. Both. We got backups of the two programmers' computers that had the source code for Leon's CD, and the source code for Claire's CD. I'm laughing about it now. We were counting on most of the video being the same. We didn't realize that we'd have to hope that most of the source code was the same! Fortunately, *most* of the code was the same. I would not be surprised if someone can find a discrepancy from one of the original disks because we didn't catch all the places where the code was different, and we might not have found all the places where the videos were different. (Ink Ribbon found a spot in the FMV where you always hear Claire's voice, even when playing as Ada, so we missed that one!)
Next problem: the source code is C, but the comments are in Japanese. And then some of that is actually katakana (phonetic) for borrowed words.
This is what we have. We've got till Christmas.
### The initial programming team
The first programmer we hired was this strange german dude, named Alex. I don't think we recognized at the time that Alex was a genius, but we knew that he was determined, and possibly mad. Perfect! Welcome to the team! Alex knew all about the Playstation, far more than me, and Chris had never worked on one. Alex was the kind of person that would mod a playstation and turn it into a devkit, if that was legal, which it might not be so that certainly didn't happen.
Our second lucky hire was a recent grad from UCSD. Todd's final year project was an MPEG player. We need an MPEG player! Do you want to come and work on Resident Evil 2? He did! Thank goodness. So we hired him, and gave him an Indy N64 devkit and the microcode development kit. I told him what I'd figured out, and how I thought we could use it to make an MPEG player, particularly given that the RSP had a CPU instruction for helping. Then Todd spent the next few months discovering that I was completely wrong, and instead implementing what anyone would say was a completely insane, ass-backwards approach but that also happened to *work*. It was brilliant and creative engineering.
Late in the project, Capcom came to us to ask if we could add some new features for the Nintendo version. We negotiated an additional programmer. But we are getting ahead of ourselves.
### Planning and communication
One of the advantages of a port, is that we already know everything that's in the game. So while we had no idea how we were going to achieve some key parts of it, like the video, a lot of the work was a known quantity. We didn't know how we were going to handle rendering the backgrounds, or getting the levels working, but we knew how many backgrounds there were, how many enemy types, etc. So we had a pretty detailed plan. And we set out at a blistering pace. Milestones where every three months, and Chris and I worked six days weeks for three weeks, and 7 day weeks for the next six. Chris bought two sofabeds for the office. There's probably a whole story to write about that too.
We had a giant office, and initially we each staked out territory. We communicated using Microsoft Team Manager 97 (I don't remember that, but Todd [wrote it down](https://www.gamedeveloper.com/programming/postmortem-angel-studios-i-resident-evil-2-i-n64-version-)). That just wasn't working. We needed to communicate all the time. We moved our desks into a semicircle. Todd writes, quoting me:
> "Alex would sometimes interrupt me with all my balls in the air to tell me some really stupid joke, but I guarantee that the time lost was nothing compared to the gain in efficiency of having everyone right there". -- me!
And now I get to clarify 25 years later, that "balls in the air" is a juggling metaphor for thinking about very complex problems and holding the space in your head, and when someone interrupts you, all of it falls to the floor and you're stupid again and have to start from scratch.
He writes,
> In the end, I think we saved months: "It crashed" \[points]. \[Turns head] "Oh yeah, that's the licker bug -- don't press the B button when it's doing that \[points] -- and I'll be checking in a fix in ten minutes."
That's an actual conversation that happened, and it was possible because we sat in a semicircle. We had nothing distracting in front of us, so could concentrate, and then when we needed to communicate, we could all see each other's screens.