Home › Blog › Halo on a Nintendo 64: How Halo 64 Fits Combat Evolved Into 8 MB of RAM

PORTS

Halo on a Nintendo 64: How Halo 64 Fits Combat Evolved Into 8 MB of RAM

We're expanding Game Mashups to cover wild cross-platform ports—starting with JackTulli's Halo 64, a clean-room N64 port compiled from decompiled Xbox C code that crams all 13 multiplayer maps, bots, Firefight, 4-player split-screen, and the opening of the 'Halo' campaign mission onto a real cartridge.

Up to now, this blog has mostly tracked two threads: two-game mashups that weld rival engines into a single executable, and perspective-shifting camera or VR mods that let you step inside a twenty-five-year-old world. Starting today, we're adding a third beat to the site: impossible cross-platform ports. When someone takes a game built for one generation of silicon and makes it run natively on hardware that should have no business booting it, the engineering rhymes with everything else we cover here.

Case in point: Halo 64, an open-source project by developer JackTulli that compiles Halo: Combat Evolved to run natively on a real Nintendo 64. Not a stylistic demake rebuilt in Unity, and not a GoldenEye 007 ROM hack wearing Master Chief armor—actual decompiled Halo C code running on the N64's 93.75 MHz MIPS CPU and 8 MB Expansion Pak, playable in an accurate emulator (ares) or off a flashcart (SummerCart64 or EverDrive-64 X7) on original 1996 hardware.

Halo 64 title screen and main menu running in 640x480i interlaced high-res
Halo 64's main menu rendered in crisp 640×480i interlaced high-resolution mode, featuring Multiplayer, Bots, Firefight, Splitscreen, Settings, and Credits. Screenshot via JackTulli/halo-64.

When Retro Dodo, Windows Central, and XDA Developers picked up the story after JackTulli posted the project to r/HaloClassicHub, the headline alone was enough to make people double-take. In the days since those initial write-ups went live, the repository has already shipped six releases (v0.1.0 through v0.3.2), adding a standalone campaign test cartridge for the second mission ("Halo" / a30.map), an optional Gearbox PC expansion builder with Banshees and Rocket Hogs, a streamed terrain megatexture, and aggressive frame-pacing optimizations.

I haven't built a ROM from my own Xbox disc yet, so everything below comes from reading the repository's architecture notes, its 1,090-line engineering history log, and the release commits. If you care about how decompilation Projects make the impossible routine, the internals of this port are fascinating.

Not a ROM Hack: Decompiled Xbox C on a 93.75 MHz MIPS CPU

Last week, when we looked at Black & White VR, we talked about how clean-room decompilations turn locked-down 2001 binaries back into malleable C and C++ source trees. Halo 64 rides that exact wave. It forks halo-ce-universal (now maintained upstream as OpenCommunityEdition/OpenCE, building on the bnunu/halo-1 and punpckhdq/halo decompilations of Xbox Halo build 2342).

Instead of rewriting Halo's gameplay by hand, Halo 64 compiles the original engine's C simulation directly for the N64's big-endian NEC VR4300 processor, pairing it with a custom renderer, menu system, HUD, and 12-voice 22 kHz VADPCM audio mixer built on the open-source libdragon SDK and tiny3d, plus Invader's Halo Editing Kit tag definitions for asset conversion.

SubsystemOriginal Xbox (Halo: CE, 2001)Nintendo 64 (Halo 64, 2026)
CPU733 MHz Intel Pentium III (32-bit little-endian x86)93.75 MHz NEC VR4300 (big-endian MIPS, 32-bit pointers)
System Memory64 MB unified DDR SDRAM8 MB RDRAM (requires N64 Expansion Pak; 4 MB base unsupported)
Texture CacheUnified system RAM4 KB on-chip RDP Texture Memory (TMEM)
Core Simulation30 Hz fixed tick (objects, physics, weapons, game)Same 30 Hz C simulation compiled from halo-ce-universal
Renderer & AudioDirect3D 8 + Xbox hardware MCPX audioCustom libdragon + tiny3d RSP/RDP pipeline & 12-voice VADPCM mixer

What stays intact from Bungie's simulation code is why the port feels like Halo rather than an approximation:

  • Kept from the Xbox engine: The 30 Hz gameplay loop, object pool, rigid-body physics, BSP collision, weapon firing and projectile ballistics, vehicles, game-type rules (Slayer, CTF, Oddball, King of the Hill, Race), HUD state logic, first-person weapon animations, and source/ai/path*.c pathfinding.
  • Cut or replaced for the N64: Full campaign actor AI state machines (replaced with a lightweight N64 AI controller), HaloScript (hs), network play, Xbox Direct3D shaders, and the original sound engine.
Blood Gulch in Halo 64 with AI bots, a Warthog, and the classic motion tracker
A match on Blood Gulch against AI bots, complete with a drivable Warthog, plasma grenades, motion tracker, and Halo's recently added terrain megatexture and blue sky gradient. Screenshot via JackTulli/halo-64.

What's Actually in the Cartridge Right Now

It's easy to assume an N64 port of Halo would be a tech demo where a lone Spartan walks around an empty gray box at 9 FPS. Instead, the feature list as of v0.3.2 reads like a full party-game cartridge—plus a second experimental ROM for campaign:

  • All 13 Xbox Multiplayer Maps: Battle Creek, Sidewinder, Damnation, Rat Race, Prisoner, Hang 'em High, Chill Out, Derelict, Boarding Action, Blood Gulch, Wizard, Chiron TL-34, and Longest—all supporting weapons, drivable vehicles, teleporters, and water.
  • Eight Classic Modes: Slayer, Team Slayer, Slayer Pro, Team Slayer Pro, Capture the Flag, King of the Hill, Oddball, and Race.
  • Up to 4-Player Local Split-Screen: One to four controllers on a single console, with no networking layer needed.
  • Up to 8 AI Bots: Using Halo's native navigation pathfinding across Easy, Normal, Heroic, and Legendary difficulties. In the initial release, bots pulled from a list of 302 N64-era names (Banjo-Kazooie's HUMBA WUMBA famously dictated the 299-pixel scoreboard column width); as of v0.3.0, thanks to a pull request from contributor @kaboggs, bots use Halo's authentic random profile names—Hambone, Caboose, Noodle, Sleepy—plus two 1-in-32 special Easter egg bots (ChaoticSP1 on Legendary and KodeBrkr16, either of which can spawn with active camouflage).
  • 1–4 Player Firefight: Wave-based survival with shared lives across four difficulties against Covenant Grunts, Jackals, and Elites imported from the campaign's a30.map.
  • Standalone Campaign Cartridge (halo-campaign.z64): Added after the first wave of press articles, the ROM builder now emits a second .z64 cartridge dedicated to the opening half (first structure BSP) of Halo's second mission, "Halo" (a30.map). You can walk out to the cliff overlook, grab the Warthog by the Marine base, and fight distance-streamed Covenant squads with checkpoints and four difficulty settings (Marines, cutscenes, scripts, and the second BSP transition aren't wired up yet).
  • Optional Gearbox PC Expansion (halo-pc-expansion.z64): For users building from source with local Invader and FFmpeg tools, @kaboggs also contributed a PC expansion pipeline that converts the six Gearbox PC-exclusive multiplayer maps (Danger Canyon, Death Island, Gephyrophobia, Ice Fields, Infinity, and Timberland) along with flyable Banshees and Rocket Warthogs, plus fun modifiers like Big Head Mode, Paintball, and Golden Style aiming.
Four-player splitscreen on Battle Creek in Halo 64
Four-player local split-screen on Battle Creek (Beaver Creek), with independent HUDs and first-person weapons. Screenshot via JackTulli/halo-64.

Squeezing a 64 MB Xbox Engine Into 8 MB of RDRAM (and 4 KB of TMEM)

How do you shrink a game designed for 64 MB of unified RAM down to an 8 MB N64—where the operating system, code, stack, audio buffers, framebuffers, game state, and loaded map all have to coexist without fragmenting the heap?

The port/n64/NOTES.md and port/n64/HISTORY.md files lay out a series of brutal, clever compromises:

1. The 2.8 MB "Map Home" Arena and Borrowed Menu Buffers

On Xbox, a compiled .map cache file is tens of megabytes. Halo 64's asset pipeline (repack_map.py) strips each map down to its essential gameplay tags and converts every struct in-place from little-endian x86 to big-endian MIPS. Because loading and unloading maps repeatedly across matches was fragmenting malloc until large maps like Sidewinder (2.63 MB repacked) crashed with out-of-memory errors, the port claims a permanent 2.8 MB "map home" arena at the very bottom of the heap on boot.

Here's the clever part: the main menu runs in crisp 640×480i interlaced high-resolution, which needs 1.8 MB of framebuffers and Z-buffer. While you're in the menu, no map is loaded—so the menu borrows the bottom of the 2.8 MB map arena for its 480i framebuffers, then hands that memory right back when a match begins. Meanwhile, the engine's runtime game-state allocation was trimmed from 1 MB down to ~404 KB actually used, leaving ~1.7 MB of heap headroom for 3D meshes and audio.

2. Surviving the N64's 4 KB Texture Cache

The Nintendo 64's Reality Display Processor (RDP) has no texture cache in main RAM; every texture must fit inside 4,096 bytes of on-chip TMEM.

  • Map and model textures are downsampled to 4-bit indexed (CI4) or intensity (I4) formats at 32×32 or 64×32 resolution so a single tile load fits comfortably in TMEM.
  • For Spartan armor, where player color tinting normally requires a two-cycle RDP combiner pass, build_models.py splits each 16-color CI4 palette in half: indices 0–7 hold un-tinted base grays and visor colors, while indices 8–15 hold pre-multiplied armor shades swapped per player in C before upload—keeping Spartan rendering at one RDP cycle per pixel.
  • First-person weapon textures (originally 512×512 atlases) are diced into 64×64 windows and remapped per triangle; if a triangle straddles a window boundary, the offline converter splits the triangle along the seam so it never wraps in TMEM.
  • For outdoor maps like Blood Gulch, v0.2.0 introduced a streamed RGBA16 terrain "megatexture" alongside a four-tier Terrain Detail toggle (Full, High, Medium, Low) and a four-tier 3D Resolution selector (Chunky 160w, Medium 256w, Standard 320×240, and High Detail, which runs at 480i or 640×240 at 240p—twice the horizontal pixels of Perfect Dark's famous hi-res mode).
Two-player splitscreen on Hang 'em High in Halo 64
Two-player horizontal split-screen on Hang 'em High, showcasing the 4-bit indexed BSP textures and custom HUD scaling. Screenshot via JackTulli/halo-64.

3. Single-Precision Math, Throttled Idle Props, and NaN Traps

The VR4300 CPU can execute single-precision float math fast, but C's default double promotion costs extra cycles on every call. The port sweeps the hot simulation loops to replace sqrt, sin, cos, and double literals with single-precision equivalents (sqrtf, sinf, cosf), and even shortcuts atan2(0, 0) in objects.c, which was eating roughly 2.8% of total CPU time just computing zero angles for stationary objects.

To keep the 30 Hz simulation from drowning the CPU on maps with vehicles and bots:

  • Unheld weapons, flags, oddballs, and parked vehicles skip physics on 3 out of every 4 ticks ((id + tick) & 3).
  • Active vehicle suspension and collision run every 2nd tick.
  • Faraway or off-screen bots and Covenant throttle their pathfinding and animation updates based on distance and PVS visibility (v0.2.0–v0.3.2).

And because the N64 uses strict MIPS floating-point traps where x86 silently shrugs, testing uncovered classic console-port landmines: a 0.0f / 0.0f division in units.c whenever a player crouched while stationary, and an fmod(x, 0.0f) exception in the HUD weapon bar whenever a player climbed into a Warthog turret (which has a reload time of zero). Both caused hard SIGFPE crashes on N64 hardware until squashed.

"Four-bit textures, a 4 KB texture cache, 8 MB of RAM with the Expansion Pak, and one small fan-less CPU: Halo has no business being here, and yet." — JackTulli, Halo 64 README
Firefight mode against Covenant on an N64 in Halo 64
Wave-based Firefight mode pitting Spartans against Covenant Grunts, Jackals, and Elites imported from the campaign's a30.map. Screenshot via JackTulli/halo-64.

"The User Tests": What the Commit History Reveals About AI-Assisted Porting

There's a second story inside the repository that Retro Dodo and XDA Developers both highlighted: Halo 64 was co-developed with Anthropic's Claude.

You don't have to guess how that workflow operated, because port/n64/HISTORY.md preserves the coding agent's running lab notebook across dozens of sessions from October 8 to October 16. Throughout the file, the narrator refers to JackTulli in the third person as "the user":

  • "Built, not run here: the user tests."
  • "The user asked for a night of optimisations and fixes, every map tested each time; runs in ares on a hidden display."
  • "First real-console test (SummerCart64) crashed immediately on picking any map... Root cause: rspq_init() allocates a 16-segment × 512-word (32 KB) default ring buffer. For a heavy frame (terrain + BSP + skinned models + HUD), our RSP command stream overruns this before the RSP drains it."

Reading HISTORY.md end-to-end is one of the clearest public artifacts we have of what serious human-plus-agent systems engineering looks like right now. It wasn't "type a prompt, get a finished N64 ROM." Instead, it was a tight, relentless feedback loop:

  1. Automated headless emulator harnesses: The agent booted ROM builds inside the cycle-accurate ares emulator on a virtual X11 display (Xvfb :97), injecting controller inputs via xdotool to navigate menus, spawn into each of the 13 maps, and scrape the on-screen FPS counter over 45-second soaking runs.
  2. PC-side sampling profilers: Using a custom script (tools/n64_profile.py), the setup sampled the emulated VR4300 Program Counter at 60 Hz via process_vm_readv and resolved addresses against halo.elf's symbol table to find exact C functions burning frame time.
  3. Hardware ground-truthing by a human: Whenever the emulator let something slide—like an RSP command buffer overrun that only manifested on a physical SummerCart64, or a controller stick curve that felt wrong on a real N64 trident controller—JackTulli tested on hardware, diagnosed the feel or crash, and directed the next engineering pass.

Why Cross-Platform Ports Belong Here

Whether someone is putting a VR tether onto Lionhead's Black & White, stitching Ocarina of Time and Majora's Mask into a single randomizer, or squeezing a 2001 Xbox launch titan backward in time onto a 1996 cartridge with 4 KB of texture memory, the impulse is identical: refusing to accept that a classic game is frozen forever in the hardware assumptions of the month it shipped.

Even down to the little details—like translating the Xbox controller's dual variable-speed rumble motors into frame-by-frame duty cycles on the N64 Rumble Pak's single on/off motor, or mapping crouch and reload onto GoldenEye-style C-buttons—Halo 64 treats the Nintendo 64 as if Bungie had quietly shipped a sister cartridge in late 2001. We'll be covering plenty more ambitious ports like this alongside our usual mashups and mods.

Links & Resources

Related

Latest posts

All posts →