The Nintendo Gigaleak preserves a very substantial Super Mario Kart source archive under other/SFC/ソースデータ/MarioKart.
Unlike the F-Zero leak, this is not neatly split into Game and Tools.
It looks much more like a live working directory copied straight out of development, with assembly source, prebuilt .rel objects, regional variants, editor code, backup-RAM routines, and even Super Famicom disk support code all sitting side by side.
This archive is especially useful because it preserves several layers of the Super Mario Kart project at once:
.rel object files that make the folder look like an active build workspace, not just a source backupIt also preserves a nice timeline of how the project grew:
The extracted MarioKart folder is flat rather than neatly nested.
That alone is revealing, because it makes the archive feel closer to a live programmer workspace than a cleaned-up archive prepared for handoff or release.
At the top level the file types break down like this:
| Type | Count | What it suggests |
|---|---|---|
.asm |
145 | Main 65C816 source modules, editor code, and support libraries |
.rel |
144 | Prebuilt assembled objects kept alongside the source |
.def |
6 | Shared definitions for labels, work RAM, objects, and regions |
| No extension | 6 | Register maps and helper include files such as rp5a22, rp5c77, and D77C25 |
.lib |
1 | sfxdos.lib, likely a bundled library artifact for the DOS-side support layer |
That near one-to-one source/object pairing is one of the strongest clues in the whole archive. This does not look like a source-only historical export. It looks like a directory that was actively being assembled and relinked during development.
The pairing is not completely perfect, though. A few files stand out:
TITLE.asm exists without a matching TITLE.rel, while the actual regional title modules such as title-j.asm, title-e.asm, and title-p.asm do have object filessfxdos.asm, sccdrv.asm, fdcdrv.asm, and ppidrv.asm have source but no sibling .rel, which makes them look more like shared support code copied into the directory.rel files such as kart-check.rel, kart-ctrl.rel, se.rel, and sos.rel survive without matching source, which hints that part of the build graph still lived outside this copied folderThis looks much closer to a real working Super Mario Kart source snapshot than a token sample.
The strongest signs in its favour are:
kart-main.asm, kart-init.asm, kart-drive.asm, kart-calc.asm, kart-bg.asm, kart-ppu.asm, kart-enemy.asm, and kart-apu.asm.rel object beside itThat said, it is still safer to call this a near-complete working source snapshot than a guaranteed self-contained rebuild package.
What is still missing or uncertain:
../join, ../../SFX, ../../DSP, and ../../../kimura, which implies this folder originally sat inside a larger tree.rel objects survive without source, so not every dependency is visible hereSo the right claim is not “everything needed to rebuild the shipped game is definitely here”. The better claim is that this looks like a very strong working snapshot of Super Mario Kart’s development directory, with most of the game-side code and a surprising amount of editor and support infrastructure still intact.
The most interesting thing about this leak is how little separation there is between different kinds of work.
In the same directory you can find:
kart-main.asm and kart-init.asmBattle.asm, Pause.asm, Result.asm, Final.asm, Record.asm, and Scene.asmCompress.asm, ISPK-j.asm, System.asm, Object.asm, and Sub_sound.asmmapedit.asm, maped3.asm, maped4.asm, edit_1.asm, edit_2.asm, edit_3.asm, runed.asm, runed1.asm, and runed2.asmsfxdos.asm, sccdrv.asm, fdcdrv.asm, ppidrv.asm, condrv.asm, and fileio.asmThat mix makes the archive much more useful than a clean “final source” export would have been. It preserves some of the actual production mess: game code, runtime data, editor logic, disk routines, and old helper libraries all living together in one place.
The timestamp spread reinforces that impression.
Files like sfxdos.asm, ccp_main.asm, condrv.asm, and fileio.asm sit back in late 1991, while the core racing code and regional branches were still being updated through summer 1992.
The central control flow becomes clear very quickly once the core modules are opened.
kart-main.asm is the real top-level dispatcher.
It defines Reset_entry, NMI_entry, IRQ_entry, COP_entry, and a main loop that waits for NMI, then jumps through a Process_address table based on the current game mode.
That one file alone shows how broad the game had already become by July 1992. It pulls together:
kart-init.asm plays the matching role on the transition side.
Its Selection_address table includes:
Title_initialKart_select_initialWorld_select_initialBattle_initialEdit1_initialEdit2_initialEdit3_initialCourse_select_initialFinal_initializeThat is one of the clearest low-level development details in the leak. The editors were wired into the same mode-selection and initialization framework as the real game screens, not treated as completely separate tools.
The work RAM definitions in work.def make that structure even clearer.
They document shared state for game_status, game_mode, game_level, game_selecta, game_index, irq_index, world_number, map_pointer, race_init, pause_status, camera state, fade state, sound state, lap counters, and ranking buffers.
What makes kart-main.asm especially useful is that it does not just define one generic update loop.
It preserves the actual scheduler that the rest of the game was written around.
After Reset_entry finishes the hardware-side setup, Main_loop does three things in a very tight cycle:
Selection_process to see whether the game is transitioning into a new stateNMI_flagProcess_address using game_indexThat says a lot about how the project was organized. Super Mario Kart was built around a mode dispatcher, with each major screen or tool owning its own main and NMI paths.
The Process_address table makes that explicit:
| Game index | Main handler | Meaning |
|---|---|---|
00 |
Playing_process00 |
idle or placeholder state |
02 |
Playing_process02 |
race |
04 |
Playing_process04 |
title |
06 |
Playing_process06 |
kart select |
08 |
Playing_process08 |
world select |
0A |
Playing_process0A |
driver’s point screen |
0C |
Playing_process0C |
ending flow |
0E |
Playing_process0E |
battle |
10 |
Playing_process10 |
editor 1 |
12 |
Playing_process12 |
editor 2 |
14 |
Playing_process14 |
editor 3 |
16 |
Playing_process16 |
course select |
18 |
Playing_process18 |
final sequence |
1C |
Playing_process1C |
record screen |
The editors and record screen were not awkward add-ons. They were first-class runtime modes with the same scheduling status as race, battle, title, and ending.
The second useful table in kart-main.asm is NMI_address.
It mirrors the main-mode structure and gives each major state its own VBlank-side handler.
That split shows how Nintendo kept the heavy per-frame simulation separate from VRAM, OAM, and HDMA work. For example:
NMI_process02 handles the race-side VRAM and OAM transport path, then calls Race_checker, Set_HDMA_parameter, Screen_control, Demo_camera, and a common package that handles sound and controller scanningNMI_process04, 06, 08, and 0A do much lighter menu-style work, mostly Set_OAM_screen1, mode-specific NMI code, and the shared input and sound packageThe game is effectively written as a pair of dispatch tables:
Even before looking at the subsystems, the overall design already looks clean and deliberate.
If kart-main.asm is the scheduler, kart-init.asm is the state-transition layer that feeds it.
Initialize_process is broader than a normal startup routine.
It:
DOS_INIgame_selecta, fade_control, and fade_speed valuesThat is already enough to show that startup was not only about the retail game. The boot path still expected the DOS-side support layer and wider dev environment to exist.
The real architectural center, though, is Selection_process.
That routine watches game_selecta, waits for the fade state to reach the right point, disables interrupts, clears the current mode state, and then jumps through Selection_address to run the correct initializer for the next mode.
That table is one of the clearest summaries of the whole project:
| Selection value | Initializer |
|---|---|
02 |
Race_initial |
04 |
Title_initial |
06 |
Kart_select_initial |
08 |
World_select_initial |
0A |
DP_initial |
0C |
Record_initial |
0E |
Battle_initial |
10 |
Edit1_initial |
12 |
Edit2_initial |
14 |
Edit3_initial |
16 |
Course_select_initial |
18 |
Final_initialize |
So game_selecta is really the transition request register, while game_index becomes the active runtime mode after the transition is complete.
That is one of the clearest low-level examples in the leak of how Nintendo separated “what we want to become next” from “what we are running now.”
work.def is easy to skim past, but it is one of the most valuable files in the entire archive because it names the shared RAM contract that all of the major modules are using.
The definitions fall into a few clear groups:
| Category | Examples | What they control |
|---|---|---|
| Global mode state | game_status, game_mode, game_level, game_selecta, game_index, irq_index |
Which broad mode is active and how the scheduler should interpret it |
| Frame and race state | frame_counter, race_status, race_init, over_flag, lap_number |
How the current race or sequence is progressing |
| World selection state | world_number, map_pointer, map_number, map_type, game_mode_stock |
Which course family and specific map are loaded |
| Pause and fade state | pause_status, pause_index, fade_control, fade_speed, fade_data |
Mode transitions and paused execution |
| Camera and scroll state | scroll_h, scroll_v, center_x, center_y, camera_distance, camera_pitch, camera_zoom, camera_direction, camera_mode |
The Mode 7 camera model and screen positioning |
| Audio state | sound_port, bgm_flag, bgm_pointer, bgm_status, finallap_counter, doppler_driver |
Sound command flow, BGM state, and race-specific audio behavior |
| Persistent or score state | player_coin, win_count, official_flag, rank_index |
Results, lap progression, and player-facing game state |
Reading this file alongside kart-main.asm makes the codebase feel much less opaque.
The main dispatcher is not passing around complex structs.
It is coordinating a shared WRAM workspace that every major module knows how to read.
If work.def is the RAM contract, label.def is the ROM content atlas.
The file does much more than define a few constants. It maps out where major gameplay and presentation data actually lives in ROM:
Title_BGM, Battle_BGM, Circuit_BGM, Ice_BGM, and Ending_BGMDEMO_MARIO, DEMO_PEACH, DEMO_KINOPIO, and the other prerecorded demo inputsMAP00_SCR through MAP18_SCRCIRCUIT_CHR, OBAKE_CHR, GRASS_CHR, CASTLE_CHR, ICE_CHR, DART_CHR, SAND_CHR, and STAR_CHRThe most revealing part is how systematic it is.
Rather than giving every map an entirely unique content stack, label.def shows the game reusing themed asset families across multiple courses and battle maps.
For example:
*_area and *_target definitions hang off one shared Drive_data_address, which makes the course logic look table-driven rather than hardwiredThat makes label.def one of the strongest “how this was really built” files in the whole archive.
It is the bridge between symbolic game logic and the actual packed data layout in ROM and RAM.
The course-loading side becomes much clearer once kart-init.asm and label.def are read together.
Set_map_number does not hardcode a cup flow in logic.
It computes an index from world_number and map_pointer, then looks that index up in World_map_data.
That table preserves the actual cup ordering:
| Cup | Map order |
|---|---|
| Mushroom Cup | 13, 12, 10, 11, 07 |
| Flower Cup | 0D, 0A, 02, 03, 0F |
| Star Cup | 01, 0C, 0B, 09, 00 |
| Special Cup | 04, 06, 05, 08, 0E |
That is a good example of the project’s overall style. The game flow is controlled by lookup tables rather than lots of map-specific branching.
The same pattern continues with Map_type_data.
Once map_number is chosen, Set_map_type turns it into one of the shared environment families:
circuitobakegrasscastleicedartsandThat in turn drives Set_maps, which is split neatly into:
Set_map_characterSet_map_screenOpen_character and Open_screen pull compressed data into work RAM, while Set_character_data, Set_screen_data, and Set_item_BG push the decoded results toward VRAM and the background buffers.
That is where label.def becomes especially valuable.
Its MAPxx_CHR, MAPxx_BCH, MAPxx_BSC, MAPxx_COL, and MAPxx_OBJ aliases show that each map is assembled from a handful of themed asset families rather than a single monolithic “course blob”.
The same is true for the drive data.
The MAPxx_area and MAPxx_target labels all hang off one Drive_data_address, and the later MAPxx_ARE and MAPxx_TRG labels point to tightly packed per-course data blocks in ROM.
So the course system is not only theme-driven. It is also highly table-driven:
That is one of the clearest examples in the leak of how Nintendo kept Super Mario Kart’s content scalable without inventing a unique code path for every course.
The regional structure in this archive is much broader than a simple Japan-versus-export split.
The source files show a mixture of suffixes:
| Suffix | Likely meaning | Examples |
|---|---|---|
| Base | Shared or default branch | kart-init.asm, Final.asm, Pause.asm |
-j |
Japanese branch | Final-j.asm, Ending1-j.asm, title-j.asm |
-e |
European/export branch | Final-e.asm, w-select-e.asm, record-e.asm |
-p |
PAL-era branch | Final-p.asm, Pause-p.asm, ISPK-p.asm |
-d |
Debug or German-specific variant | Debug-d.asm, Meter-d.asm, title-d.asm, BGunit_set-d.asm |
Some files only branch once or twice, while others preserve full regional stacks. The heaviest examples are:
Final.asm with -e, -j, and -p variantsEnding1 and Ending2 with -e, -j, and -p variantsc-select and w-select with base, -e, -j, and -p variantskart-init with base, -e, -j, and -p variantstitle with base, -d, -e, -j, and -p variantsThat regional layering matters because it shows Super Mario Kart as a live branching project rather than a single monolithic source tree. The game was still being locally adapted screen by screen, mode by mode, and system by system.
title-j.asm is a good example.
It still contains explicit debug helpers for setting player, world, and rank state right from the title sequence.
Final-j.asm is equally rich, preserving the full ending flow with award logic, moving clouds, paper effects, and different behavior depending on finishing rank.
One nice thing about having so many parallel files is that it becomes possible to tell the difference between a true branch and a lightly relabeled copy.
So far, the menu and ending code looks split into two broad groups:
| Branch pattern | Examples | What it suggests |
|---|---|---|
| Near-structural clones | title-p.asm, Pause-p.asm, Final-p.asm |
The branch still exists as its own source file, but the overall logic and state-machine shape remain extremely close to the Japanese or base versions |
| Behaviorally distinct branches | w-select-e.asm, c-select-p.asm |
The branch still carries front-end logic around unlock state, replay state, backup-RAM state, or screen-specific restore behavior |
title-p.asm is a good example of the first category.
It keeps the same Backup_Sam_Check, Back_up_clear, rom_checker, and SET_debug flow as the Japanese and debug title branches, and it still writes game_selecta and fade_control in the same broad way.
That makes it feel like a maintained branch, but not a radically different title implementation.
Pause-p.asm looks similar.
It still has the same big pause-state structure: Main_debug, RAM_editer_B, Save_replay, Display_VSnext, Display_GPnext, Retry_check, Change_course, and Change_kart are all still there.
So the PAL pause layer appears to preserve the same “pause as front-end bridge” design rather than introducing a substantially different pause model.
Final-p.asm also stays very close to the Japanese ceremony logic.
It still runs AWARD_SET, Rank_Check, Message_set, Zannen_pose, Pukupuku_1 through Pukupuku_3, KUMO_move, Paper_fall_set, and Final_end_set, with Rotate_mode7 still sitting in the exit path.
That suggests the award and celebration flow was stable enough by this point that the branch mainly exists to preserve a separate regional implementation, not to carry a different ending architecture.
w-select-e.asm is the file that stands out most strongly in the other direction.
Unlike the simpler world-select path, it keeps Old_world_number, updates the old and new cup labels separately in NMI through MOJI_Set_1, and pulls in backup-facing routines like Back_up_set, Backup_Sam_Check, Back_up_clear, and Rank_Check_sum.
It also checks the stored Mushroom, Flower, and Star Cup completion bytes before allowing Special Cup to remain selected.
That is a real front-end rule difference, not just translated text.
So the regional split in this archive is not uniform. Some files are effectively parallel maintenance branches, while others still carry genuinely different front-end behavior around save validation, unlock conditions, or menu state restoration.
The menu-side files are much richer than a simple “press start” layer.
Taken together, title-j.asm, c-select.asm, c-select-j.asm, w-select.asm, k-select.asm, and Pause.asm read like one continuous front-end state machine that can move the player from attract mode into race setup, then back out again through retry, course select, or kart select.
title-j.asm does far more than animate the logo and hand off to the next screen.
Its TITL_init path calls PPU_INT_SET, Backup_Sam_Check, TENSOU_SET, PPU_title, and OBJ_ERASE, then explicitly initializes debug-facing variables like Personal_player and Rank_set.
That becomes much more concrete in SET_debug.
That routine draws the current debug values into OAM, lets the player step Personal_player, world_number, and Rank_set, and then copies Test_rank_data into the live point_rank table.
In other words, the Japanese title branch still preserves a real title-side shortcut for forcing cup and rank state before jumping onward.
The main loop is also broader than a plain menu handler.
TITL_main runs Random, KEY_CHECK, and FLAG_CHECK, while the visible menu path itself goes through WINDOW_SET and GAME_SELECT.
That makes the title file feel less like a self-contained title screen and more like the first front-end controller in the wider runtime scheduler.
c-select.asm is effectively the race-setup router.
Its dispatch tables split the screen into two families:
| Mode family | Dispatch entries | What it handles |
|---|---|---|
| Race | select_cup, select_course, map_read, map_yesno |
Cup choice, course choice, and the map-loading confirmation path |
| Battle | select_battle, battlemap_read |
Battle-map choice and the matching load path |
That separation is important because it shows battle mode was not bolted onto the side of the race UI.
It had its own selection flow, its own map_battle table, and its own branch through the same front-end infrastructure.
c-select-j.asm then makes the picture even more interesting.
Its init path does Check_backupRAM, Check_replay, WINDOW_INIT, WINDOW_CONTROL, Init_meter, and DMA_meter, and it also carries Erase_timeRAM, world_lock, and keySRAM_world / keySRAM_map handling.
So the Japanese course-select code is not only choosing the next track.
It is also the point where replay state, key save state, and backup-RAM checks are brought back into sync before the race starts.
w-select.asm is really the cup-select screen, not just a tiny intermediary menu.
Its init path builds the whole scene through WORLD_PPU and WORLD_TENSOU, then writes cup labels, trophy sprites, frame graphics, and colors directly into the menu buffers.
Several details stand out:
WORLD_TENSOU converts the source art into the screen format the menu needs rather than just pointing at a prebuilt tilemapgame_level allows it, Special CupCup_change.set changes world_number, calls WAKU_change_set, restores the previous cup color, then applies the new one through Color_changegame_level, so the menu logic itself is enforcing progression rulesThe handoff is also very explicit.
NEXT_SET clears race_init, triggers sound effect 002Eh, writes #02 into game_selecta, calls BGM_fade_out, and sets fade_control to 8f00h.
That is a good low-level example of how menu code and the main runtime scheduler talk to each other: the front end does not launch races directly, it writes the next mode request and the transition parameters, then lets the core loop perform the switch.
k-select.asm is far more dynamic than a static character grid.
Its main path runs VS_com_set, SELECT_SCROLL, Cursor_Move_set, Small_kart_set, Pikupiku_set, CURSOR_FLASH, KART_CHECK, NEXT_CHECK, and OBJ_MOVE every frame, while the NMI path updates demo kart DMA, cursor erasure, cursor draw, back-screen stop markers, and flashing state.
That gives away a lot about how the screen is built:
DMA_demokartSelect_Player and Swing_select tables drive the actual carousel order and swing offsets used by the chosen kart arttimeattack_flag is threaded deeply through the code, affecting one-player versus rival setup and even the second cursor pathNEXT_CHECK makes the transition logic especially clear.
Once both move_flag values settle and next_counter reaches 0040h, the code commits the selected kart IDs into driver_number, optionally mirrors one of them into timeattack_rival, then converts the current game_status into the next runtime mode.
Grand Prix jumps to #0008, while VS, Time Trial, and Battle all route through #0016, after which fade_control is set to 8f00h.
So even the “pick a driver” screen is not a dead-end UI. It is already performing game-state setup, rival assignment, and front-end transition scheduling.
Pause.asm may be the single clearest proof that Super Mario Kart’s menus and race code were designed as one connected system.
PAUSE_MAIN branches through open_select, every_select, and exit_select, with separate pause-index families for Grand Prix, VS, Time Trial, and Battle.
During the active pause state, Every_pause can do much more than wait for resume input:
RAM_editer_BMain_debugDisplay_GPnext or Display_VSnextpause_cursor, pause_index, replay_flag, and ghost_flagThe exit side is even more revealing. The retry tables can jump into:
| Destination | Routine | What it means |
|---|---|---|
| Retry current race or advance | Retry_check |
Chooses between RACE_RETRY and NEXT_RACE |
| Return to title | Retry_start |
Calls Start_title |
| Return to course select | Change_course |
Calls Start_C_select |
| Return to kart select | Change_kart |
Calls Start_K_select |
That is not just a pause menu.
It is a live branch point back into the rest of the front end.
The same pause layer also saves replay state with Save_replay, has special handling for ghost/replay flags, and can redraw the menu differently for VS/TM/BT through Display_VSnext versus GP through Display_GPnext.
Once that file is read alongside the title and select code, the overall design becomes much clearer.
Super Mario Kart was not built as a hard line between “menus” and “gameplay”.
It was built as a shared mode system where title, cup select, kart select, race, battle, retry, and debug all talk to the same scheduler through game_selecta, game_index, and fade-control state.
demo-j.asm is also worth reading as part of the front end, because it shows the attract sequence was not a cheap prerecorded movie.
It is a real gameplay-side scene with its own object setup, transport path, and camera math.
The clearest clues are:
Rotate_mode7 talks directly to the DSP triangle routine to build a zoom-and-rotation matrixDMA_demokart increments demokart_frame and then calls the normal object transport path through OBJ_transportSet_demokart clears trans_counter, resets camera_direction, steps through active objects, and uses Set_trans_data when transfer slots are availableInit_demokart mounts demo_kart objects through Mount_A, initializes their positions and pose values, and then seeds extra coins and shells through Init_demokameThat is a great low-level preservation detail. The title-side demo is not just “watch mode”. It is a miniature runtime environment built out of the same object, sprite, and projection systems as the real race code.
The menu files also show that front-end work was still branching late into development.
title-d.asm is especially revealing, because it keeps the same Backup_Sam_Check, Back_up_clear, rom_checker, and SET_debug structure as title-j.asm.
So the title-side debug path was not unique to one isolated Japanese source file.
At least one other branch was still carrying the same skeleton.
c-select-p.asm is equally useful because it makes the replay and ghost path very explicit.
It still runs Check_backupRAM, clears replay_flag, and then calls both backup_replay and check_replay during init.
Those helpers read keySRAM_world and keySRAM_map, mark selected entries in the course buffer, and use ghost_flag to distinguish backup or replay-driven state from a normal manual selection flow.
That means the front-end branching was not only about translated text or PAL timing. At least some of the regional menu work was still carrying different save, replay, and debug expectations right inside the selection code.
This is where the Mario Kart leak really separates itself from a normal source drop.
The editor files are not toy leftovers. They are a layered development environment with front-end shells, specialized editing back ends, supporting data files, and disk I/O bridges.
The cleanest way to read them is as four connected parts:
| Part | Main files | What they appear to do |
|---|---|---|
| Editor front ends | edit_1.asm, edit_2.asm, edit_3.asm |
Boot different editor modes and route control/NMI flow |
| Map and object editors | mapedit.asm, runed.asm, runed1.asm, runed2.asm, maped3.asm, maped4.asm |
Handle point editing, area editing, object placement, save/load, and battle-map work |
| Editor data | edit_data.asm, edmap2.asm |
Provide UI text, OAM layouts, HDMA tables, file-name tables, and object patterns |
| Disk bridge | ed_dos1.asm, ed_dos2.asm |
Turn editor save/load requests into SFX-DOS file operations |
That split matters because it shows Nintendo was not just carrying one debug menu. They had a small editor framework with reusable parts.
edit_3.asm is the best high-level overview of the whole toolset.
It exports ED_init_3, ED_main_3, and ED_nmi_3, then dispatches through a large editer_select table.
That table is unusually revealing. It can boot all of these modes from one place:
select or debug-mode menumap_init and map_mainSo edit_3.asm is not just another editor file.
It looks more like an internal launcher for multiple test and content-production tools.
The file also preserves some nice implementation details:
title_screen, sound_screen, and select_screendebug_rom_flagThat makes it feel closer to a small internal test shell than a single-purpose editor.
The select menu in edit_3.asm is more concrete than a generic “debug mode” label makes it sound.
select_main draws a text menu from select_data, tracks the current cursor in edit_point, and exposes a set of directly launchable actions:
| Menu path | What it appears to do |
|---|---|
select_world |
Change world_number |
select_course |
Change map_pointer |
start_rase |
Jump straight into a normal race setup |
select_battleobj |
Adjust battle-object selection state |
select_battlemap |
Choose the current battle map |
start_battle |
Jump straight into battle mode |
edit1 |
Launch the first editor shell |
edit2 |
Launch the second editor shell |
m7_char |
Launch the Mode 7 character test |
cg_map |
Launch the color/CG test |
map_check |
Launch the map editor itself |
demo2 |
Launch the object demo |
sound_test |
Launch the sound test |
That matters because it shows the menu was not only for isolated graphics tests. It was also a front door into the real gameplay and editor states.
The jump targets are especially clear in the code:
start_rase sets race_init, game_selecta, fade_control, and fade_speed, then pushes the game into a normal race pathstart_battle writes the chosen battlemap into map_number, then jumps through the battle-mode setup pathedit1 and edit2 reuse the same battlemap selection and then jump into the first and second editor families through game_selectaSo edit_3.asm is not just browsing internal demos.
It is actively driving the same global state machine used by the real game.
The shell also preserves how Nintendo staged editor UI screens in RAM.
Three fixed screen buffers sit in WRAM:
| Buffer | Address | Use |
|---|---|---|
title_screen |
7FC000h |
Title or title-test display data |
sound_screen |
7FC800h |
Sound-test menu display |
select_screen |
7FD000h |
Main debug-mode menu |
The paired nmi_select table is small but revealing:
DMA_demokartThat suggests the shell was designed around a few heavyweight UI screens and several simpler test states.
The sound test inside edit_3.asm is much richer than a single “play sound” stub.
soundtest_init:
sound_screenInit_demokartThen soundtest_main turns the menu into a live APU-port editor.
It:
sound_cursor2140h through 2142hThat is a very strong preservation detail. The developers did not just have a menu of named tracks. They had a low-level sound-port monitor/editor for directly poking the SPC communication registers.
The other test modes are also more practical than decorative.
m7test_init clears Mode 7 VRAM, initializes the Mode 7 registers, writes a test screen, and turns the display on.
cgtest_init does the same, but also fills test character data so the team could inspect how graphics and palette data behaved in Mode 7.
The larger bigtest path is the most interesting of the graphics tests.
It:
Buffer_write, M7flip, and sp_modeThe data tables here are excellent low-level evidence.
The file still names source ROM regions like OBJ_mario, OBJ_koopa, OBJ_peach, OBJ_cong, OBJ_luige, OBJ_noko, OBJ_kino, and OBJ_yossy, then assigns palette IDs and conversion parameters to each.
That means edit_3.asm is preserving an internal graphics workbench, not only a generic map viewer.
Another reason edit_3.asm matters is that it keeps a whole bundle of reusable editor helpers in one place.
The file contains routines for:
Conv_m1m7This is easy to underestimate, but it is one of the clearest signs that the editor shell was a maintained internal platform. The Mario Kart team was not writing every test mode from scratch. They had a shared mini-library for menu rendering, debug cursors, VRAM setup, and Mode 7 conversion.
mapedit.asm is the most self-contained editor in the archive, and it is much richer than its plain filename suggests.
Its setup path shows a full in-engine workflow:
Set_map_numberSet_ROM_to_bufferSet_BG_itemConvert_m1m7mode7_char, mode7_screen, and mode7_char2 blocks into VRAMInit_mode7point_edit and save_loadThe hardcoded DMA blocks are especially interesting because they show exactly how the editor expected its working data to be laid out:
| Block | Destination | Source | Size |
|---|---|---|---|
mode7_char |
VRAM 0000h via 1900h |
7f4000h |
4000h |
mode7_screen |
VRAM 0000h via 1800h |
7f0000h |
4000h |
mode7_char2 |
VRAM 3400h via 1900h |
7fc400h |
0400h |
That is excellent preservation evidence because it turns the editor from an abstraction into a real memory map.
The two-process split is just as useful:
point_edit handles cursor movement, address calculation, zoom, and direct editing of map pointssave_load handles backup and restore using a backup_point area at 085c800hSo even at this level, the editor appears to have been built around reversible editing rather than “type once and hope for the best”.
edit_1.asm and edit_2.asm are small compared to the heavier files, but they explain how the toolchain was assembled.
edit_1.asm looks like the front end for the first editor family.
Its main loop switches between three edit modes:
AREA_DEITCODE_EDITPOINT_EDITFrom there it chains into shared worker routines like AREA_SET, NUM_SET, POINT_SET, S.LMODE_SET, SAVE_SET, LOAD_SET, and CLEAR_SET.
edit_2.asm plays the same role for the second editor family.
It wires together:
YAKDAT_SET1, YAKUSCT_SET1, and YAKIDSP_SET1HOUKOU_SET1FILE_SET2, DISK_LOAD, and DISK_SAVEThat makes the structure much easier to understand:
the small edit_* files are menu shells, while the much larger runed* and maped* files carry the real editing behavior.
runed.asm and runed1.asm are where the first editor family becomes much more concrete.
runed.asm exports the first-wave setup and editing routines used by edit_1.asm.
Its symbol list shows the main concerns very clearly:
The file also contains explicit SAVE LOAD MODE SET comments and direct calls to SAVE_FILE1 and LOAD_FILE1.
That means the editor was not only modifying data in RAM.
It was designed to write changes back out through the SFX-DOS layer.
runed1.asm then goes deeper into specialized editing operations.
The comments preserved in the file are unusually useful, because they show the kinds of content Nintendo expected to edit without opening a different program:
BG1,2 SAVE LOAD SETBG1 LOAD SETSPEED LOAD SETBG2 LOAD SETThat tells us the editor was not only for placing single points or objects. It also had higher-level terrain and background editing tools, including shape-based fill or placement helpers.
If runed.asm and runed1.asm are the first editor family, runed2.asm, maped3.asm, and maped4.asm look like the heavier support side for the second one.
runed2.asm exports low-level interaction routines like:
POINT_SETERASE_SETPOSI_MOVE1POSDSP_SET1SPEED_SETThat makes it feel like the moment-to-moment editing layer: move the cursor, place data, erase data, update the display, and change parameters like speed.
maped3.asm is much more presentation-heavy.
It builds a large HDMA-driven editor display, exports NMI_SET1, and holds a long run of higher-level editor helpers like SAVE_SET1, MENU_SET1, LOAD_SET1, CLEAR_SET1, and DAT_SET.
The most interesting part is how visual it is. It sets up multiple HDMA streams, dynamic VRAM writes, and color-window effects just for the editor UI. That suggests Nintendo wanted the content tools to be comfortably usable on real hardware, not just technically functional.
maped4.asm complements that with the disk-facing and battle-facing side.
Its symbol list and comments make three things stand out:
DISK SAVE and DISK LOAD routinesBATTLE_LOAD, BATTLE_SAVE, and BATTLE_ERASEDSFN_SET2 file-name display helper that writes selected disk filenames into OAM-backed UI textThat is one of the best low-level clues in the whole editor stack. Battle content was not just another map. It had its own save/load path and its own dedicated editor support.
maped4.asm is where the battle editor stops looking like a small variation on the normal course tools and starts looking like its own workflow.
The first clue is that the file carries two overlapping save/load systems.
The generic DISK_SAVE and DISK_LOAD paths still exist, but both immediately branch into battle-specific handlers when map_number is 14h.
That means the battle map was already being treated as a special case at the top level, not only later when data got written out.
The normal path looks like this:
7f8000hDISK_SAVE serializes that RAM into a 1c00h block and hands it to SAVE_FILE2DISK_LOAD calls LOAD_FILE2, rebuilds the editor’s working buffers, and then redraws VRAM with DAT_SETThe battle path is different almost all the way through:
7f8200h7f9300h, not the normal 1c00h blockBATTLE_SDISK and BATTLE_LDISK bypass the generic helper wrappers and set up the cop 16h and cop 15h file calls directlyThat split is exactly the sort of low-level detail that makes this leak so useful. The battle maps were not being stored as a trivial variant of normal track data. They had their own object list, their own staging buffer, and their own disk transfer path.
The live editing routines in maped4.asm are just as revealing as the save/load code.
BTTLE_SET is the core placement routine.
It builds battle-object patterns from yakidat, derives width and length values from PTADAT, and has a special water placement path through Water_Set11 and Water_Set1.
The important part is what happens after the visual placement work.
When the editor is not in save/load mode, BTTLE_SET records the placed battle object into 7f8200h.
So the battle editor is maintaining its own persistent object list in RAM, rather than treating VRAM as the only source of truth.
BATTLE_ERASE confirms the same design from the opposite direction.
It searches that 7f8200h list by bgadd, clears matching entries, updates erasedat and eraseflg, and recalculates the object dimensions through witcnt and lthcnt.
That is a stronger implementation than a simple tile paint tool. The editor was tracking discrete battle objects with metadata, placement state, and erase logic, not just rewriting background cells.
The internal battle save/load pair is one of the clearest clues about the actual data flow.
BATTLE_SAVE walks the battle object list in 7f8200h and serializes it into bank 11h, starting at 11:8200.
BATTLE_LOAD does the reverse: it clears VRAM and the live object list, rebuilds the placement data from that serialized block, then replays BTTLE_SET to redraw the resulting objects.
That replay step matters. The battle editor was not only restoring a raw tilemap dump. It appears to have been restoring a structured object list and then regenerating the visible map state from it.
The disk-facing battle path uses a second stage:
BATTLE_SDISK copies battle data into 7f:9300cop 16h0c00h, which is much smaller and more fixed than the generic course-editor save pathBATTLE_LDISK mirrors that process on load.
It pulls the stored block back into 7f:9300 with cop 15h, rebuilds the 7f8200h object list, then redraws the editor state by calling BTTLE_SET again.
So the battle workflow seems to have three layers:
7f8200h11:82007f:9300That is far more elaborate than “save the current map.” It looks like a deliberately separate battle-object pipeline living alongside the normal course tools.
maped4.asm also preserves a small but important validation routine in SUM_CHECK2.
That routine computes a checksum over 701000h and 702000h, then compares the result against 700ffeh.
In context, it looks like editor-side RAM validation for the loaded battle or map work area, probably to catch corrupted or mismatched buffers before the rest of the tool tries to rebuild display data from them.
Even that tiny detail is useful. It shows the editor was robust enough to expect failure cases, not just a one-shot internal script that assumed every load would always succeed.
The editors are backed by two compact but revealing data modules.
edit_data.asm is the small shared data pack used by edit_3.asm.
It contains:
select_datatitle_datasound_dataconv_data0set_m7scr screen-layout dataThat is what makes the debug hub able to present multiple test modes cleanly instead of just dumping raw graphics.
edmap2.asm is even more obviously UI-facing.
It contains:
OAM2 and MSB2 sprite/OAM layouts for editor text and cursorsYAKI_NO pattern tables for placeable object setsSELECTDAT1, SELECTDAT2, and MENUDAT1 text datasave_numb2 tables for save slots or save areasdisk_file2 and file_number2 support data for the file selectorsThe object pattern tables are a particularly nice detail. They include groups for things like question blocks, smiley or face-style tiles, arrows, walls, oil, jumps, and multiple coin frames.
So the editor was not only moving abstract data around. It had a real in-engine palette of placeable course objects.
The last important layer is ed_dos1.asm and ed_dos2.asm.
These files show how the editors talked to the DOS support layer without every editor file needing to know the hardware details directly.
ed_dos1.asm handles one family of editor files:
FILE_SET1 cycles through save slots or file entriesSAVE_FILE1 builds a filename, sets address/count fields, and triggers cop 16hLOAD_FILE1 does the same for cop 15hed_dos2.asm does the same for the second editor family, but also preserves:
INIDOS_SET, which selects drive and media modePOINT_SET8, which copies stack or work pointers between RAM areasDOS_INI and DOS_INI_1, which are mostly commented out here but still show how the editor originally expected to enter the SFX-DOS environmentThe two bridge files are also a good reminder that the editors were not all saving the same kind of payload. They are preparing different address and size fields before they ever reach the DOS layer:
| Editor path | Save area | Transfer size | What it suggests |
|---|---|---|---|
SAVE_FILE1 / LOAD_FILE1 |
0800h |
0400h |
The first editor family writes a larger structured block |
SAVE_FILE2 / LOAD_FILE2 |
1c00h |
0080h |
The second editor family writes a much smaller compact object/map buffer |
BATTLE_SDISK / BATTLE_LDISK |
7f:9300 |
0c00h |
Battle editing bypasses the generic wrappers and stages its own larger transfer |
That is useful because it clarifies the architectural boundary.
The main editor code manipulated map, object, and UI state.
The ed_dos* bridge files then translated those requests into the cop-based save/load calls used by Nintendo’s SFX-DOS layer, with different transfer layouts for different kinds of editor data.
Together these editor files make one larger point very clearly: Super Mario Kart was not just developed by editing source and rebuilding the game. Nintendo also had a substantial hardware-facing editor environment for placing objects, shaping course data, previewing Mode 7 output, and saving work back out through floppy-backed dev tools.
The older 1991 files reveal another part of the setup: a disk and I/O support layer for Super Famicom development hardware.
sfxdos.asm describes itself as a Super Famicom Disk Operation System special version, programmed by Y. Nishida on 29 October 1991.
Together with fileio.asm, fdcdrv.asm, sccdrv.asm, ppidrv.asm, condrv.asm, and ccp_main.asm, it preserves a real SNES-side operating layer for floppy access, keyboard and serial input, printer output, file management, and text-console interaction.
That part of the leak is broader than Mario Kart itself, so it now has its own article:
For Mario Kart specifically, the important takeaway is simple:
ed_dos1.asm and ed_dos2.asm were calling into a real disk operating layercop 15h and cop 16h path was still sitting on top of the same broader SFX-DOS environmentSuper Mario Kart’s Mode 7 pipeline is unusually visible in this archive.
The ISPK and Compress files are the key.
ISPK-j.asm and ISPK-p.asm preserve the actual decompression logic for a custom ISPK format, with entry points like Decode_7E_X and Decode_7F_X that stream data from ROM into work RAM.
Compress.asm then builds on that.
It exposes routines such as:
Decode_M7copyDecode_M7_BFDecode_M7_buffDecode_mode7Its comments make the intended pipeline unusually explicit: compressed Mode 7 character data is unpacked into RAM buffers and then transferred onward into VRAM.
That sits neatly beside the location tables in label.def.
The file maps out where music, map screens, character graphics, battle graphics, color data, and object graphics live in ROM.
It names concrete resources like:
Title_BGMBattle_BGMMAP00_SCR through later course screen entriesCIRCUIT_CHR, OBAKE_CHR, GRASS_CHR, CASTLE_CHR, ICE_CHR, DART_CHR, SAND_CHR, and STAR_CHRThat makes label.def much more than a generic include.
It is effectively a hand-written ROM content index for the project.
The other extensionless files play a similar role:
| File | Role |
|---|---|
rp5a22 |
SNES CPU register definitions |
rp5c77 |
SNES PPU register definitions |
D77C25 |
DSP command definitions for the math/Mode 7 support hardware |
label |
Older raw register-label include used by the editor-side DOS files |
label_1 |
Save/load editor workspace definitions |
label_3 |
Larger editor workspace definitions for map/battle editing |
The main race path in kart-main.asm is worth calling out on its own, because it shows how the game’s heavy per-frame work is ordered.
In the normal race state, Playing_process02 runs in a fixed sequence:
WINDOW_CONTROLSet_positionCamera_controlBOBJ_setBOfficial_BSet_trans_bufferBCamera_controlASet_trans_bufferAOBJ_setAOfficial_ARace_controlColor_effectThat is not a random collection of calls. It reveals a two-screen or two-pass mental model where camera, object setup, and transfer preparation are handled separately for A and B contexts before the race simulation finalizes world state.
Then the NMI side finishes the transport work.
NMI_process02 performs the VBlank-safe half of the pipeline:
OBJ_transportBG_transportRace_checkerSet_HDMA_parameterScreen_controlDemo_cameraNMI_packageThat split is one of the best low-level architecture details in the whole archive. The race engine is not only “gameplay code plus rendering.” It is a carefully staged pipeline:
The battle loop mirrors that shape rather than inventing a separate engine architecture.
It swaps in Battle_objsetA, Battle_objsetB, and Battle_control, but the overall ordering stays recognizably the same.
That makes the codebase feel much more like one shared runtime with different mode-specific control modules than a set of unrelated mini-engines.
kart-bg.asm and kart-ppu.asm make the hardware-facing side much clearer.
They show that Super Mario Kart was not only preparing one Mode 7 screen and then throwing sprites on top.
It was actively staging two camera contexts, two OAM states, and a timed IRQ sequence to change how the PPU was configured mid-frame.
The camera side lives in kart-bg.asm.
Camera_controlA and Camera_controlB both flow through Set_camera_data, which then calls:
Set_camera_positionSet_camera_directionPers_parameterCamera_controlB also calls Set_double_buffer first, copying the B-side scroll, center, and background values into a second buffer before the next camera pass runs.
That is a strong clue that the game was preserving multiple camera states at once, not only recalculating one set of Mode 7 registers.
The background register path is equally explicit.
BG_resister_A and BG_resister_B write:
Scroll_0H and Scroll_0VRotation_X and Rotation_YScroll_2H and Scroll_2Vback_2* and back_3*That means the race view was combining the Mode 7 road plane with additional scrolling background layers rather than treating the whole scene as one flat transformed bitmap.
The most revealing routine is Screen_control.
During VBlank it:
IRQ_controlcamera_flip into Screen_flipnuki_color values into the color registers when neededSet_window_biasThen the IRQ path takes over later in the same frame.
Regular_IRQ seeds the timer, and the later IRQ_2 and IRQ_6 handlers do the really interesting work:
IRQ_2 waits for the right beam position, blanks the screen, switches the screen-flip state, writes the B-side background registers, and rebuilds OAM for screen 2IRQ_6 restores the normal PPU control state, resets DMA sync, restores OAM for screen 1, and runs Color_transport_sSo the split-screen effect is not a simple static layout. It is an IRQ-timed display pipeline that changes scroll, window, color, and sprite state while the frame is being drawn.
kart-ppu.asm shows the matching sprite and transfer side.
OBJ_transport walks obj_trans_buffer in 6-byte chunks, each one describing:
That is then fed straight into DMA channel 0 for VBlank-time uploads.
The paired Set_trans_bufferA and Set_trans_bufferB routines fill those transfer queues, but they do not try to upload everything every frame.
They rotate through counters, cap the number of entries, and only call Trans_mark when there is still room.
That is a lovely low-level detail because it shows Nintendo throttling sprite and kart graphic uploads instead of treating VRAM as infinitely cheap. Animation and object-character updates were being budgeted across frames.
The same file also keeps two separate OAM staging paths:
Set_OAM_screen1Set_OAM_screen2That fits neatly with the IRQ logic in kart-bg.asm.
The game was not only maintaining two camera contexts.
It was also maintaining two sprite presentation states and swapping between them as the beam moved down the screen.
Taken together, these files make the graphics side feel much more concrete. Super Mario Kart’s display pipeline was built around:
That is one of the clearest places in the whole leak where you can see the difference between “a game uses Mode 7” and “a commercial SNES game has a carefully engineered display pipeline.”
The display side becomes even clearer once Camera.asm, Screen.asm, and Window.asm are read alongside the PPU modules.
Camera.asm is not the general race camera solver.
It is a smaller control layer for special camera states, and it is explicitly credited to H. Yajima in the file header.
Its exported routines are:
Init_cameraEnding_cameraStop_cameraWinner_cameraTV_cameraThat makes the file useful because it shows the camera system had named cinematic or state-specific overrides on top of the normal race camera path.
Winner_camera, for example, sets special camera-control bits, tracks a separate goalin_offset, and gradually rotates the camera around the winning kart instead of leaving the view locked to the standard chase direction.
Screen.asm then shows how world-space objects become on-screen sprite positions.
Its header calls it XYZ -> HV convert, and that description is accurate.
The file exports:
projectscreen_Ascreen_Bproject_Zscreen_AZscreen_BZdist_projectdist_screenThe key detail is that this path is DSP-backed.
Both project and dist_project write position data to DSP_data, then the later screen_A and screen_B routines read the resulting values back and turn them into:
min_patern handling when something is too far away or outside the visible rangeThat means the object layer was not doing all of its projection math in ad hoc per-object code. It had a dedicated world-to-screen conversion module, again credited to H. Yajima, feeding the sprite placement path.
The split-screen logic shows up here too.
screen_A and screen_B are separate routines, with mirrored handling for upper and lower views.
They even preserve special back-view behavior when the player index indicates rear-view mode, flipping the horizontal placement around H_center+80h instead of the normal center.
Window.asm fills in the last missing piece: the HUD and rank-window layer.
WINDOW_INIT does much more than clear a small buffer.
It:
deco_rankwindow_lank data in WRAMThen WINDOW_CONTROL updates that data live from gameplay state.
It reads obj_flag and rank_number for the upper and lower players, checks whether each side has reached the goal state, and switches between small and large rank-number layouts by writing different entries into window_lank.
That is a great low-level detail because it means the rank display is not only a sprite overlay. It is tied into the SNES window and color-blend hardware as a separate display layer, with its own WRAM-backed HDMA data stream.
So the display stack now reads very cleanly from top to bottom:
Camera.asm handles special camera-state overrideskart-bg.asm prepares camera A/B scroll, perspective, IRQ, and window stateScreen.asm projects world-space positions into screen-space coordinates with DSP helpkart-ppu.asm queues and uploads sprite and object graphicsWindow.asm drives the rank and goal overlay through HDMA-backed window dataThat separation is one of the strongest technical details in the archive. Nintendo did not hide camera math, projection, sprites, and HUD logic in one giant render routine. They split them into distinct layers that each map quite neatly onto specific pieces of SNES hardware.
The audio side is much richer than a simple “write song IDs to the SPC” setup.
kart-apu.asm shows three different layers working together:
| Layer | Main routines | Role |
|---|---|---|
| High-level music control | BGM_control, Set_race_BGM, Set_battle_BGM, Call_bgm, Call_fanfare |
Choose themes, handle fanfares, final-lap changes, and queue music transitions |
| Frame-to-frame sound dispatch | Sound_set, Sound_set_trigger, Check_trigger_SE |
Feed the current sound command registers and queued effects to the APU |
| Driving and event logic | Engine_sound, Count_down_sound, Goal_in_sound, Start_ultra_BGM, End_ultra_BGM |
Turn race state into engine pitch, countdown behavior, and event-driven music changes |
One of the most useful details in kart-apu.asm is that BGM requests are buffered.
Call_bgm does not immediately rewrite the active music.
It pushes a value into bgm_buffer and increments bgm_pointer.
Later, Sound_set checks bgm_pointer, trigger_pointer, and se_pointer in order, and only then writes the chosen command into the APU-facing sound registers.
That means the audio system is explicitly queue-driven. Multiple pieces of logic can request music or sound changes, and the dispatcher drains those requests in a controlled order during the sound update path.
That is a much cleaner design than every gameplay routine poking the SPC ports directly.
Set_race_BGM and Set_battle_BGM reinforce the same theme we saw in the course loader.
The game does not hardcode one song per map in scattered logic.
Instead:
Set_battle_BGM always selects the battle theme pathSet_race_BGM uses map_type to pick an entry from a BGM_data tableBGM_transport then decodes the corresponding music data into RAM and boots the APU with itThat ties neatly back into label.def, where the actual song data addresses are named explicitly:
Title_BGMBattle_BGMCircuit_BGMIce_BGMGrass_BGMObake_BGMSand_BGMCastle_BGMDart_BGMEnding_BGMSo the audio side follows the same pattern as the graphics side: gameplay code selects a theme category, and the actual data addresses live in tables and labels.
BGM_control is where the race audio gets much more interesting.
It is not only checking “are we racing?”
It is watching multiple gameplay-driven counters and using them to trigger music changes:
signal_counter controls the delayed handoff from the start countdown into the main race BGMfinallap_counter controls the switch into the final-lap musicfanfare_counter controls how long win or loss fanfares suppress the normal track themesultra_index controls a separate special-music path that can start and stop around ultra or star-style statesThat is a strong preservation detail because it shows how much music state the game was tracking in real time. The BGM was not static background decoration. It was reacting to countdowns, race milestones, placement results, and special gameplay states.
The sound-effect side is just as revealing.
Trigger_sound_1 and Trigger_sound_2 do not write one effect directly to the sound port and return.
They push effect IDs into se_buffer and matching pan values into pan_buffer, then advance se_pointer.
Later, Sound_set drains that queue and uses the pan value to decide how to write the stereo side bit into Sound2.
That means the effect system was designed around:
Even the helper list is revealing.
Named entry points such as Coin_sound, Jump_sound, Spin_jump_sound, Landing_sound, Dash_sound, Crash_sound, Falling_sound, Splash_sound, Balloon_sound, Lost_sound, Kame_sound, and Cursor_sound all exist as stable API hooks, even though the real queue work happens later.
The engine code is also much more tightly coupled to gameplay than the filename alone suggests.
Count_down_engine and Check_countdown_engine read:
game_statusrank_numberengine_powerkart_statusspecial_triggerThat lets the code detect wheelspin and super-start windows before the race even begins.
Then the normal Engine_sound path chooses different handlers depending on game_mode, so one- and two-player states can mix engine and doppler behavior differently.
That is a nice low-level reminder that the audio layer was not bolted on afterward. It was sharing real state with the kart-control model and reacting to it every frame.
kart-calc.asm sits underneath a lot of the files already discussed and acts as the shared math and decision layer for movement, targeting, ranking, and race progression.
The two most important exports are:
Calc_move_directionCalc_targetThose are the routines other modules keep leaning on whenever they need a direction or angle from one point to another.
The file also imports lookup data like Sec_data and Tan_data, which makes it clear that a lot of the steering and camera math is table-driven rather than done with slow generic arithmetic each frame.
That gives the rest of the codebase a common geometry language.
kart-enemy.asm, kart-effect.asm, and the camera-side code are all routing through the same shared calculation layer rather than reinventing their own target-angle logic.
The rest of the file reinforces that role. Alongside the geometry helpers it also carries:
Calc_timerGame_controlRace_checkerGoalin_controlAll_check_rankCheck_crash_mykartCheck_crash_enemySo kart-calc.asm is not only “the math file.”
It is also one of the main places where geometric state gets turned into race-state decisions.
Calc_target is especially revealing because it shows up all over the project.
It is used for:
kart-enemy.asmkart-calc.asm itselfkart-effect.asmIt is a good example of how shared the low-level movement model really was. The game does not have one aiming system for enemies, another for drift correction, and another for camera turns. It has one core target-angle routine, then a number of systems interpreting that result in their own way.
The same is true of Calc_move_direction.
That routine appears to convert vector or target deltas into the discrete direction format used by kart and body state, which helps explain why so many files can exchange values like mark_direction, move_direction, body_direction, and kart_direction without needing expensive conversion code everywhere.
If kart-calc.asm provides the math, kart-effect.asm provides a lot of the live state-machine glue for driving effects and moment-to-moment reactions.
Its exports tell the story immediately:
Tire_effectColor_effectCheck_engine_statusEvent_controlCamera_spinSpin_jumpSpin_crashFade_controlEngine_power_controlThis is not a single-purpose effects module. It is a broad “what should happen to the kart and camera right now?” layer.
The file is especially useful because it preserves how Nintendo grouped these behaviors together:
So while kart-drive.asm owns the top-level race flow, kart-effect.asm is where a lot of the local state transitions actually get expressed.
The drift and spin code is one of the nicest remaining low-level details in the archive.
This is not a one-bit “is drifting” flag.
kart-effect.asm carries multiple fields like:
drift_indexdrift_angledrift_posevector_velocitycamera_driftand uses them to step through different handling states over time.
That is why the file exports separate routines like Spin_jump, Spin_crash, and Spin_crash_sub rather than one general “spin” helper.
The game is keeping track of distinct movement phases and using those to decide:
The same idea shows up in the ultra and engine-status handling.
Check_engine_status and Engine_power_control tie wheelspin, braking, drift resistance, and engine power together instead of treating them as unrelated effects.
So the “feel” of Mario Kart is not hidden in one giant physics routine. A lot of it lives in these smaller state-machine transitions layered on top of the shared movement math.
One especially helpful part of kart-effect.asm is Event_control.
Its dispatch table groups several otherwise separate-looking situations into one unified event-state system:
That is important because it clarifies the relationship between collision and movement.
BGcheck-p.asm can decide that a kart has hit a wall or gone out of bounds, but kart-effect.asm is where those conditions become timed movement states with gravity, landing behavior, and control recovery.
So the overall runtime model looks more coherent after reading these two files together:
kart-enemy.asm and the drive tables choose where things want to gokart-calc.asm provides the geometry, direction, and race-state calculationskart-effect.asm turns special situations like drift, spin, jump, crash, and fall into explicit state transitionskart-drive.asm keeps the wider race flow moving around themThat makes the remaining gameplay code feel much less fragmented. The separate files are not isolated tricks. They are specialized layers built around one shared movement and state model.
This is where the source stops being mostly about tools and runtime structure and starts to show the actual rule systems underneath the game.
The most revealing files here are kart-enemy.asm, kart-drive.asm, BGcheck-p.asm, Item.asm, and Battle.asm.
Taken together, they show a surprisingly data-driven runtime: tracks are broken into area and target tables, AI karts read those tables through shared buffers, collision pushes back through a surface-status layer, and battle mode adds a separate HP and object-state system on top.
label.def already hints that the course logic hangs off Drive_data_address, but kart-enemy.asm makes that concrete.
Initial_enemy opens two kinds of per-course data:
Area_address_ROM or Area_address_SRAMTarget_address_ROM or Target_address_SRAMThat split matters. The course is not treated as one flat AI blob. It is divided into:
area_buffertarget_bufferX, target_bufferY, and target_statusThe area stream is code-based and terminated by FFh, while the target stream is read as repeating position and status entries.
After loading, the code even appends the first target to the end of the buffer so the path can wrap cleanly.
That is a very strong clue about the runtime model. The AI is not following a hand-authored spline in code. It is moving from target to target across a course map that has already been partitioned into logical areas.
The goal line is layered on top of the same system.
For race maps below 14h, Initial_enemy also opens Goal_data, finds the matching area rows, and marks those cells with a dedicated goal-box bit inside area_buffer.
Battle maps skip that path entirely.
That suggests race and battle were already diverging at the data-layout level, not only in higher-level gameplay code.
The enemy-control module is more sophisticated than a simple “rubber band” routine.
Its update path is split into three passes:
| Pass | Routine | What it appears to do |
|---|---|---|
| Pass 1 | Enemy_control_100 |
Run event logic, update direction, and set velocity |
| Pass 2 | Enemy_control_200 |
Choose steering and control behavior from the current move state |
| Pass 3 | Enemy_control_300 |
Recompute the current drive status for tactical decisions |
Check_target_direction is the key bridge between course data and steering.
In normal driving it can use the precomputed drive_direction table directly from the current area cell.
In special states such as jumps or irregular positions, it switches to the explicit target coordinates in target_bufferX and target_bufferY and recomputes the heading through Calc_target.
That is a smart hybrid design:
The tactical layer sits on top of that.
Check_drive_status does not just ask whether a kart is moving.
It looks at:
From there it returns different drive states such as:
This is one of the most interesting low-level findings in the whole archive. The AI is not only “follow the path faster or slower.” It is path-following layered with rank-aware tactical behavior.
The battle data in label.def turns out to be revealing here too.
Not every battle map has its own unique area and target tables:
MAP14, MAP15, MAP17, and MAP18 share the same area and target baseMAP16 keeps its own distinct pairThat suggests the battle layouts were not all authored with the same level of unique drive-data support as the normal race tracks. Some are clearly sharing a common base path or logic grid, even while their visual presentation differs.
That looks like the same asset-reuse mindset showing up in gameplay data, not just graphics.
The surviving source for the collision side is the PAL branch file BGcheck-p.asm, but it is enough to show the general design clearly.
The important routines are:
BGcheck_kartBGcheck_itemcheck_BGcheck_entry turns the current position into a lookup inside BGcheck_buffer, then uses the resulting tile or surface code to index BGstatus_buffer.
That means the collision layer is working from a decoded background-status map rather than from raw graphics.
The consequences are more nuanced than a simple solid or non-solid test:
area_out flagBG_mirror_flag, _hit_timer, and _unable_controlThe velocity-damping logic is especially revealing.
speed_chenge distinguishes between:
and scales the rebound differently depending on crash side and current speed.
So this is not just “did I hit a wall.” It is a compact terrain and crash-response system with out-of-bounds handling, control lockout, and different bounce strengths for different situations.
Item.asm shows the same design philosophy again.
Items are not only random pickups with a single shared probability table.
The enemy-fire path uses:
rank_numbertable_select and patern_select tablesto decide whether and what to fire.
That means enemy item behavior is partly character-tuned and partly position-aware.
The source also keeps distinct sound hooks like sound_kame, sound_banana, and sound_missile, which makes the whole item layer feel tightly integrated with both gameplay state and the audio queueing system we already saw in kart-apu.asm.
So even the item system is more data-driven than it first appears:
Battle.asm is smaller than the editor-side battle files, but it still preserves some important gameplay-specific rules.
Init_BTmode initializes battle_HP for both sides, and Check_spin watches effect flags for spin events.
When a spin is confirmed, it walks the battle-object status list, updates HP, and can trigger a balloon-hit sound path through sound_balloon.
That is a useful reminder that the battle system was not only “race mode with different maps.” It had its own win-condition layer tracking damage or balloon loss separately from lap progression.
The same file also preserves the question-block animation path:
Init_questionDMA_questionThose routines advance through Question_buffer, rewrite character data in BGcheck_buffer, and update the live background display through DMA-style screen writes.
That is a nice low-level detail because it shows battle arenas were not static tilemaps. At least some arena objects were being animated by rewriting their backing background data on the fly.
Taken together, these files show how table-driven the gameplay layer really was. Tracks were divided into areas and targets, AI steering combined path following with rank-based tactics, collision used a decoded surface-status map, items used character- and rank-aware decision tables, and battle mode layered its own HP and arena-object rules on top.
The race-end side of Super Mario Kart is almost its own subsystem.
Once you put Goal.asm, Round.asm, and Result.asm together, the finish line stops looking like a single flag flip and starts to look like a layered presentation and state machine.
Goal.asm itself is surprisingly small.
It is basically a thin In_goal entry that copies a block of data with MVN, then returns.
The real finish logic lives in Result.asm.
That file preserves separate goal handlers for:
The branching is explicit in the tables.
goalset_A and goalset_B read the active goalstate, then use rank_jump to route rank 1 into winer_demo, ranks 2 through 4 into safe_demo, and ranks 5 through 8 into lost_demo.
That is a great example of how structured the finish logic was. The game was not only checking “did the player finish?” It was immediately sorting the player into different end-of-race presentation flows based on rank and mode.
Round.asm is another nice surprise.
It is not a lap-check module at all.
It is the animated ROUND overlay system that appears around the start of a race and in demo contexts.
The key state variable is round_process, which dispatches through:
round_offround_openround_dispsprite_eraseround_closeInit_round builds the tile and OAM data in round_buffer, using data_round_a and data_round_b to lay out the ROUND text separately for the upper and lower displays.
Then Main_round opens the overlay, holds it on-screen with a timer, erases the sprite side, and finally closes it again.
That means the race lifecycle had its own miniature presentation controller before the player even got to lap timing and end-of-race results.
Result.asm is where the finish system becomes much more revealing.
It ties together:
Stop_camera, Winner_camera, and TV_cameragoal_flag and result_statusSave_laptimeThe goal text path is especially rich.
Init_goalin resets the per-screen message state, DMA_goalin pushes character data toward VRAM, and Disp_goalin builds the actual finish-time display into the moji_result buffers.
That function does not only print a message.
It calls Disp_laptime, which in turn calls Save_laptime, so the visible result screen and the SRAM update are directly connected.
That makes the race-end pipeline much clearer:
So the result screen is not just a menu layered on top of the race. It is the point where race logic, save logic, and presentation all meet.
The cup-clear ceremony is much richer than a simple victory screen.
Final-j.asm reads like a full presentation controller with its own timer, rank gate, camera state, paper effects, cloud motion, and shadow logic.
The key routines make the structure pretty clear:
Champgne_timer acts as the master award timerAWARD_SET handles the core podium and celebration setupRank_Check decides whether the player gets a true award scene or the lower-ranked “too bad” branchMessage_set and Zannen_pose handle the lower-ranked outcomePukupuku_1, Pukupuku_2, and Pukupuku_3 handle the top-three-specific animation pathsKUMO_move, Paper_fall_set, and Final_end_set keep the common background presentation moving underneath the rank-specific animationThat is a lot more than a static post-race overlay. The ceremony is being staged as its own animated scene with separate branches for podium ranks, moving background elements, and a timed handoff into the final exit path.
The NMI side confirms that the scene is doing real display work too.
Final_nmi writes Scroll_X, Scroll_Y, Center_X, Center_Y, and the live rotate_A to rotate_D matrix values into the SNES registers, while Final_end_set eventually calls Rotate_mode7.
So the award ceremony is using the same kind of active Mode 7 camera control we saw elsewhere in the project, not just a flat congratulation screen.
The Japanese ending path is not bundled into one file.
It is split between Ending1-j.asm and Ending2-j.asm, and those two files appear to handle very different parts of the ending presentation.
Ending1-j.asm is the staff-roll choreography layer.
Its two entry points, Staff_roll_A and Staff_roll_B, pick alternate layouts from ending_pattern, then feed into Staff_roll_set.
From there the sequence runs through a clear staged controller:
| Stage | Main routines | What it appears to do |
|---|---|---|
| Initial page setup | Executive_position_set, Name_set |
Lay out the current heading block and matching name rows |
| Motion phase | Executive_position_move, Name_move |
Slide the current heading and name group into place |
| Final packed page | Jump_name_set |
Reconfigure the final page layout before the ending closes |
| Cleanup | Erase_Executive_position, Erase_name_set |
Remove the current page and advance to the next pattern |
That makes the staff roll feel far more authored than a plain text crawl. The source is positioning, coloring, moving, and erasing credits as grouped OAM data with explicit counters and per-pattern layout rules.
Ending2-j.asm then takes over for the actual finale.
Its timer triggers a series of staged events:
Banzai_set_everydodySet_fall_moji enables the falling-text phaseSet_Wave_play enables the wave sequence and updates Banzai_counterWave_set, Noji_Fall_set, and Color_change_setThe_End_set, driven by Fade_set, Fade_counter, and Fade_timerThe setup path in Tensou_ending2_set is just as revealing.
It seeds wave acceleration, wave points, landing points, falling-text acceleration, BG3 data, thank-you sprite positions, and the fade controller before the main loop even starts.
So the final scene is another real timed animation layer, not just one still image after the credits finish.
Scene.asm helps make sense of all of this because it preserves the shared scene/object layer underneath the rest of the game.
At the top of the file, scene_select maps every race and battle map to:
grid_circuit0 or grid_battle4set_kart mount stepset_dokan, set_bubble, set_fish, set_poo, set_wood, set_ball, or set_rdossunIt also keeps type_data and level_data, then exposes the shared helpers the rest of the project relies on, including Execute_VRAM, Execute_TRANS, Set_VRAMextra, Buffer_pause, and Set_debugmap.
That matters because the ending and ceremony code are not building an entirely separate renderer. They are leaning on the same project-wide scene, object-mount, and VRAM transport layer that the race and battle systems already use.
The ending branch layout also reinforces the wider picture of this archive as a live multi-branch project.
The source preserves:
Final.asm with Final-e.asm, Final-j.asm, and Final-p.asmEnding1.asm with Ending1-e.asm, Ending1-j.asm, and Ending1-p.asmEnding2.asm with Ending2-e.asm, Ending2-j.asm, and Ending2-p.asmThat is useful in itself. Nintendo was not only localizing text strings at this point. It was still carrying separate ending and ceremony code by branch, just as it did for title, cup select, and pause.
The last group of files worth calling out are the ones that do not fit neatly into the course editor, race loop, or save system sections, but still reveal a lot about how Nintendo actually worked.
Debug.asm and Debug-d.asm are much more than tiny leftover cheat hooks.
They preserve a full in-game RAM inspector and editor built around debug_FLAG, debug_address, debug_cursor, and a small meter-buffer display.
The input combinations are unusually explicit. The file can toggle:
ON_goaldebugON_usemeterON_realtimeON_debugitemON_debugmodeOnce active, the code displays the current bank and address, reads back RAM through the indirect address in debug_address, and can write modified values back through paired debug_load and debug_store handlers.
That is historically useful because it shows Nintendo carrying a real-time memory editor inside the game runtime itself, not only external tooling on the dev hardware side.
The Debug-d.asm branch preserves the same structure, which suggests this was not a one-off abandoned experiment.
System.asm is not flashy, but it helps explain how the object layer stays under control.
The check_mode_A and check_mode_B routines gate object display based on:
If an object is too far away or the sprite budget is already full, these routines drop it to min_patern and route into an erase or return path instead.
That is a very practical low-level detail. The object system was not only projecting and animating hazards. It was continuously deciding whether each object was worth drawing at all in the current screen context.
The smaller object files are revealing because they show how many gameplay-specific systems Nintendo kept as separate modules rather than folding them into one giant object file.
Jugem.asm is the biggest example.
It is effectively Lakitu’s whole rescue and race-control layer, with control sequences for:
That is a strong reminder that Lakitu was not just a sprite with a few canned animations.
He was part of the actual race-state machinery, clearing official_flag, interacting with camera control, and moving players back into a valid state.
Missile.asm is much smaller, but it preserves the projectile-spawn side very cleanly.
open_shot uses per-direction Xoffset and Yoffset tables to place the missile relative to the firing kart, then links it to the current firing register.
Pole.asm is really a collection of course-object behaviors rather than only poles.
It includes logic for:
dossun behaviorBecause it routes those objects through project, project_Z, screen_AZ, screen_BZ, jumpset_A, and jumpset_B, it also acts like a worked example of how the general projection and object-display pipeline was reused for specific hazards.
Poo.asm and Net.asm round that picture out.
Poo.asm handles a dedicated poo status path and links back into the pole-object state, while Net.asm appears to manage indexed placement data for net or fence-style course objects using per-screen offsets and object-position setup.
Taken together, these smaller files make one last point clear: Super Mario Kart’s runtime was highly modular. Lakitu, projectiles, heavy stage hazards, and track-side objects all had their own compact control modules, but they still plugged back into the same shared projection, collision, and display layers described earlier in the page.
The record and backup side of the game survives unusually well here.
Record.asm and record-e.asm are not vague menu helpers.
They operate directly on a Backup_RAM area at 700000h, with Backup_time records stored at Backup_RAM+660H.
Each course gets its own fixed record block, that block is guarded by a checksum, and the game updates best-lap and best-total tables separately.
The save layout is built around one repeated record structure per course.
Check_backupRAM loops over 20 entries, and it computes the per-course address by ORing the course number with 1400h before multiplying.
The address math lines up with a fixed 20-byte course record:
2 bytes of checksum at the start5 ranked total-time entries at 3 bytes each1 best-lap entry at another 3 bytesThat is exactly 18 data bytes plus the initial checksum word, giving 20 bytes per map.
The internal comparison code makes the layout even more concrete:
| Offset | Meaning |
|---|---|
+00 to +01 |
Per-course checksum |
+02 to +04 |
Fastest total time entry |
+05 to +07 |
Second-place total time entry |
+08 to +0A |
Third-place total time entry |
+0B to +0D |
Fourth-place total time entry |
+0E to +10 |
Fifth-place total time entry |
+11 to +13 |
Best-lap entry |
So the record system is not a general-purpose save blob. It is a very tight table of per-course time-attack data.
Check_backupRAM is one of the nicest small routines in the whole archive because it shows exactly how Nintendo treated SRAM integrity.
For each course it:
Checksum_mapThe checksum itself is simple and very hardware-friendly.
Checksum_map just sums the next 18 bytes after the checksum word, then stores the resulting 16-bit value back at the start of the course block.
The default data is also preserved explicitly. When a course record is initialized, the code writes a sentinel checksum and then fills every time slot with the same placeholder triplet:
99590AThat gives the game an obviously “bad” default time and lets the later comparison code treat any real finish as an improvement.
Each saved record entry is only 3 bytes wide, so the game packs multiple meanings into the same fields.
The low-level display and compare code shows this pattern:
2 bytes store the time value used for orderingThat is why Disp_best can decode both the time and the character name from the same tiny record.
It masks the low nibble to decide whether the time is valid, then pulls the upper nibble back out and uses it as an index into the character-name table:
marioluigekuppapeachcongkamekinopioyossySo the save data is not only preserving the best times. It is also preserving who set them.
The most interesting design choice in Save_laptime is that best-lap and best-total handling are separate, even though both live in the same 20-byte course block.
The first pass scans the current race’s lap times and chooses the fastest lap from the local _round buffer.
If that lap beats the stored best-lap entry, the code writes:
+11best_flag+13That means the best-lap entry is storing more than a time. It is also encoding which lap number produced the record and which character set it.
The second pass then switches to the total race time and compares it against the five ranked total-time entries. If the new total qualifies, the code:
20-byte block into a temporary escape buffer3-byte record into the correct slotThat insertion logic is one of the nicest low-level details in the whole file. Super Mario Kart was not just overwriting a single best time. It was maintaining a real ordered top-five table in SRAM.
record-e.asm also preserves a little extra behavior that the base Record.asm file does not expose as clearly.
It exports Erase_timeRAM, which:
That is a useful regional clue. The export branch was carrying a more explicit record-erasure path, not just passive validation on boot.
The save code is not isolated in one forgotten corner of the project. It is wired into the normal game flow.
Result.asm calls Save_laptime during the lap-time display path, so the SRAM update happens right where the player sees the finished times.
kart-init.asm still has a Record_initial entry in its mode setup table, even if the routine itself is tiny here, and c-select-j.asm calls the backup checks from the menu side.
That is a good reminder that on older console projects, menu code and result code often had to know quite a lot about persistent save structures.
Taken together, these files show that Super Mario Kart’s save system was compact but carefully structured:
F-Zero is a beautifully tidy archive. Super Mario Kart is the opposite, and that is exactly why it is so useful.
This folder preserves the game code, the regional branches, the prebuilt objects, the decompression code, the editor entry points, the backup-RAM logic, and the DOS/dev-hardware support layer in one place.
For anyone trying to understand how official SNES games were actually built at a low level, that combination is the key detail. It shows Super Mario Kart not as a single frozen ROM source tree, but as a living workspace where race code, Mode 7 data handling, editor screens, save logic, and development-hardware support all still overlapped.