mario3d
A byte-exact port of Super Mario Bros. World 1-1 from 6502 assembly to TypeScript, verified against a reference emulator every frame, with a button that switches the whole thing into 3D.
Two goals that pull against each other, kept in separate layers. 2D mode is the port: every routine transliterated from the disassembly into TypeScript, keeping its name, with all state in one 2 KB array at the original addresses. 3D mode is a deliberate fork, since the original has no Z axis and can’t be 1:1 with it.
Proving it’s exact
A cycle-stepped 6502 and dot-stepped PPU act as the reference machine. The CPU matches the nestest golden log across all 8,991 logged instructions. A TAS-style search with save states generates a playthrough, the reference machine records a RAM trace of it, and the port replays the same inputs and has to match every watched byte on every frame. Two routes through 1-1 pass: the normal run to the flagpole, and the one down the pipe into the coin room.
The bugs a byte-exact test catches are the interesting part:
- The carry flag crosses routine boundaries. An
sbc #$48that nobody cleared the carry for really subtracts 73, so goombas get culled a frame early. - Registers are arguments. The foot-collision check calls the same routine twice and relies on Y carrying over to test the second foot. Reading it as a plain function checks the left foot twice; fixing that took the port from 56 exact addresses to 305.
- Routines fall through.
GetBackgroundColorhas nortsand runs straight intoGetPlayerColors.
The 3D part
The 3D view reads the same RAM image. Nothing is modelled by hand. The disassembly’s names for all 101 metatiles decide what each block becomes: pipes turn into real cylinders, question blocks stand proud of the wall, coins become spinning discs.
Free mode adds a depth axis. You can step around pipes and goombas, stomp enemies with the game’s own jump and fall gravities, and use pipes and the flagpole. The game keeps running underneath: the 3D player’s position is pushed into RAM each frame and the emulator gets controller input, so it keeps scrolling and running its own enemies.
Sound comes from an emulated APU mixed through the hardware’s non-linear mixer, fed to an AudioWorklet through a ring buffer.