owned this note
owned this note
Published
Linked with GitHub
# Super Mario 64 Debug code
On this page I document everything debug-related I found in the code of Super Mario 64. Gameshark codes that activate these have been known for a while now, but I think nobody documented the 'cheat codes' and button controls that the developers used. Note that while the code is present, it's not actually executed. *You still need memory hacks to access these menus.*
If you want to try these yourself, make these edits to the RAM of the **Japanese version** of SM64:
```
8029CF60: 0C0B287A -- call 'check dprint activation code'
802CA2E0: 080B28BA -- continue with 'change dprint page'
802CA3AC: 080B28EF -- continue with 'handle effectinfo/enemyinfo controls'
802CA5C8: 08091814 -- continue with 'check profiler/classic debug code'
802B6F04: 0C0ACF33 -- end Bowser's function with 'get commands from enemyinfo'
```
On the left is a RAM address pointing to a MIPS instruction, and on the right you see the instruction to replace it with. The numbers are big-endian and hexadecimal.
For the **US version**, the addresses are different:
```
8029D6E8: 0C0B2B32
802CADC0: 080B2B72
802CAE8C: 080B2BA7
802CB0A8: 08091814
802B785C: 0C0AD167
```
Note that your emulator needs to be in **pure interpreter** mode. If it's doing recompilation, chances are the code modifications won't have effect because it's actually executing cached translated code.
If you want to make it a ROM hack, subtract `0x80245000` from the RAM addresses and make the modifications there in the ROM. You need to update the ROM checksum in that case.
So now I'll try to cover everything using screenshots and code snippets. All decompilations are from the Japanese version, though the same code exists in the American, European and Shindou versions. The American version lacks some font textures though (the letters J, V, X, Z in particular).
:::warning
I have by no means found everything yet, so all things that are left to explore are in boxes like this.
:::
> [color=#99AADD] All button-combinations and controls are in these boxes.
---
> [TOC]
## Profiler and 'classic debug'
There is an unused function in the game's code that toggles two debug modes when you enter a button sequence on the D-pad:
> [color=#99AADD]
> * **Up up down down left right left right** toggles a profiler
> * **Down down up up left right left right** toggles some variable displays, dubbed 'classic debug'
The profiler shows how well the game can maintain its frame rate of 30 fps.
![](https://i.imgur.com/HG8AcgF.png)
There are four colored bars acting as a time scale on the bottom. Each bar represents 1/60th of a second. On top of it, the time of the audio thread (red), game logic thread (yellow) and video thread (orange) are plotted. When the top bars reach the orange reference bar, it means they took more than 1/30th of a second so a lag frame is introduced. A little red bar is drawn in the middle left when that occurs.
When it reaches red, two lag frames are introduced. During room transitions, tens of lag frames are introduced because loading takes several frames. The profiler will spike for a frame then.
![](https://i.imgur.com/wPGRP8X.png)
> [color=#99AADD]When you press **L**, the bars are shown in a different way.
The time bars are moved to the right, and on top of the blue bar a yellow bar is now drawn. It seems like in this view, the red, yellow and orange times are not grouped. It rather shows a timeline of which thread is active.
:::warning
Is this true? What does the cyan bar mean? What else is drawn?
:::
This profiler also [exists in Mario Kart 64](https://tcrf.net/Mario_Kart_64#CPU_and_Graphic_Display).
Now for what is dubbed 'classic debug' (I love this name):
![](https://tcrf.net/images/8/80/SM64Debug.png)
This displays some values on the screen. Negative values are indicated with an 'M'.
`ANG` is the slope of the ground in degrees (0 = flat, 90 = vertical)
`SPD` is Mario's ~~horizontal speed~~ forward velocity
`STA` is the last 9 bits of Mario's state variable.
`MEM` is something related to memory of display lists
`BUF` is how many bytes are left in the draw buffer. Display lists and transformation matrices are allocated and freed every frame.
:::warning
What are the limits of MEM and BUF? How were these values interpreted by the developers?
:::
## Stage select
With this debug flag on, you go to a stage select when you press start at the title screen or exit from a level.
![](https://tcrf.net/images/c/ca/SM64LevelSelect.png)
This reveals the internal names for the courses. See [the list on The Cutting Room Floor](https://tcrf.net/Super_Mario_64_(Nintendo_64)/Debug_Content#Level_Select).
> [color=#99AADD] You can select levels with the **D-pad**, (1 at a time **up**/**down**, 10 at a time **left**/**right**)
> **A** and **B** can also be used for scrolling through levels one at a time
> **Start** makes you enter the selected level
This is pretty straightforward, but there's also this combination:
> [color=#99AADD] If you press **start** while holding **Z** + **C-left** + **C-right** then the game restarts without level select on.
![](https://i.imgur.com/PZY6D5G.png)
With this flag on, you also won't lose health from being underwater water, in cold water, or in toxic haze. This is not a side-effect, but explicitly programmed in. I suppose the developers got tired of suffocating while testing levels.
## Free movement
This was [found by Dudaw](https://www.youtube.com/watch?v=J14MRUktEiY).
There's a movement function that makes navigating the level really easy. It can be accessed by replacing Mario's running function.
![](https://i.imgur.com/Gv4Wbzp.png)
> [color=#99AADD] The **D-pad** moves Mario up or down with 16 units per frame,
> The **control stick** moves Mario laterally with 32 units per frame.
> When you hold **B**, these speeds are multiplied by 4.
> Holding **L** multiplies the speed by 0.01, giving very slow and precise movement.
> Pressing **A** makes Mario exit to an in-air or underwater state.
There is collision checking with walls and floors, but not with ceilings. When you hold B, you move with 128 units per frame, and walls are 100 units thick so you can pass through them. No quarter frame checks are done in this mode.
:::warning
How did the developers originally access this state? Maybe there are some hints left in Mario's state logic.
:::
## Frame advance
This was found by [bad_boot](https://www.youtube.com/channel/UC280b7IZCFnzCR0RCwi6JYg). The `short` at 0x339EC8 is 0 normally and 2 when the game is paused, 4 during transitions (entering a painting, going through a door, debug level select).
When you hack it to 5 you enter a frame advance mode.
> [color=#99AADD] The game only advances a frame if you press **D-pad down** then.
## Debug print
This was [found by Yoshielectron](https://www.youtube.com/watch?v=Lu6rsl8HdaI).
While the 'classic display' shows some variable on a fixed position, there is also a whole system for automatically laying out debug text. The state is stored in a struct:
```C
typedef struct
{
short disabled, //0x00 (if not zero, it doesn't draw)
short cursor_x, //0x02
short cursor_y, //0x04
short min_y, //0x06 causes DPRINT OVERFLOW if cursor_y not
short max_y, //0x08 in this range
short line_y_offset, //0x0A equals -15 (top aligned) or +15 (bottom aligned)
} DprintState;
```
There are two of these states, one in the top left corner growing down, and one in the bottom right corner growing up. Note that (0, 0) is the bottom left corner and (320, 240) is the top right corner. Now whenever the developers wanted to show a certain variable, they would call a function that automatically ensures it doesn't overlap with other variables.
The bottom right corner is used for all kind of ad-hoc things, while in the top right there are 6 pages that you can scroll through.
Again, there are functions that check for controls:
> [color=#99AADD] Hold **L** and press **C-up**, **left**, **down**, **right** to toggle this menu on or off
> Hold **L** and press **D-pad left** to increase the page number and **D-pad right** to decrease it
> Press **Z** to toggle whether the text is collapsed
I don't know why D-pad left increases the page number, I would find it more intuitive if D-pad right increased it.
> Japanese is traditionally read from right to left, so you'd turn the lefthand page to advance.[name=GlitterBerri]
### Things that appear in the Bottom right corner
![](https://i.imgur.com/0NxbFU2.png)
Every time a floor check returns a null pointer a counter is incremented, and if it is positive it will be drawn with the label `NULLBG`. In the image, Mario's four quarter steps are Out of Bounds so the counter is 4.
In some courses, you'll see `NULLBG` in the corner whenever an object spawns. That's because these object aren't in the right position right from the start: on the first frame they are at (0, 0, 0). So if you're in a stage where this origin point is not above a floor, `NULLBG` shows up whenever an object spawns.
---
Similar to `NULLBG`, there is a `WALL` counter. It only pop ups when the wall counter is higher than 0, but that never happens. The counter gets reset to 0 every frame, but nowhere does it get incremented.
:::warning
What would have been the meaning of this `WALL` counter, would it increment on wall collision? That could help to differentiate between walls, steep floors and steep ceilings.
:::
---
![](https://i.imgur.com/K0hZHax.png)
In Snowman's Land, the `X` and `Z` coordinates of the penguin are shown. (The `NULLBG` in the image is because the snowman's windparticles spawn on the origin, which is Out of Bounds in Snowman's Land)
---
![](https://i.imgur.com/VoJ9Dmr.png)
When Mario is grabbed by King Bob-omb / Chuckya, a number appears signifying how many times you pressed A or jolted the control stick. When it reaches 11, you break free. The developers probably displayed this to gauge what a reasonable button mashing rate is for breaking free. See also: [Escaping from Chuckya's Grasp](https://www.youtube.com/watch?v=_R8c70zuJSw) on YouTube.
---
![](https://i.imgur.com/66p122N.png)
Chuckya shows his 'mode', which is a variable that many objects have to differentiate different phases in their behavior logic. It also shows his speed, and `FG`. This sometimes doesn't fit on the screen, so on the right image I made it draw more to the left. What is FG and why is it -100928964928? Well, it's actually drawing an uninitialized local variable with an incorrect number to string conversion. More on that in the bugs section.
---
![](https://i.imgur.com/Io953Wz.png)
Ukikis show their mode too, but also their 'action' which is 1 when you talk to them.
mode 0 = standing, 1 = walking, 2 = turning, 3 = jumping, 4 = opening cage.
---
![](https://i.imgur.com/CbqSYyR.png)
The metal cage platforms in Lethal Lava Land show their `NUMBER` which is like a state, it increments every time its movement changes.
:::warning
I don't get why a platform going back and forth needs a state variable shown, so maybe these platforms once had more involved movement?
:::
---
![](https://i.imgur.com/vQBKHeC.png)
Bowser prints his horizontal speed when jumping back on the platform.
---
### Page 0: AREAINFO
The castle, Big Boo's Haunt and Hazy Maze Cave are all split up into multiple areas separated by doors. The 'area' shows in which room Mario is.
![](https://i.imgur.com/KxjJrJy.png)
It also displays an object counter. It signifies the number of objects loaded, except coins spawners, scuttlebug spawners and falling bridge spawners. See the bugs section for an explanation of why.
There's a function that prints an object's movement flags on page 0. It is not attached to any object, but it could have been used for any object using the standard movement system (crazy box, penguin, goomba etc.).
```C
//2CA8B4:
void print_debug_object_state()
{
int status = current_object->move_flags; //0x00EC
//fn2C9AD8
if (status & 0x01 != 0) print_debug_page0("BOUND %x", status); // on ground
if (status & 0x02 != 0) print_debug_page0("TOUCH %x", status); // first frame on ground
if (status & 0x04 != 0) print_debug_page0("TAKEOFF %x", status); // first frame in air
if (status & 0x08 != 0) print_debug_page0("DIVE %x", status); // first frame under water
if (status & 0x10 != 0) print_debug_page0("S WATER %x", status); // at surface of water
if (status & 0x20 != 0) print_debug_page0("U WATER %x", status); // under water
if (status & 0x40 != 0) print_debug_page0("B WATER %x", status); // bottom of water
if (status & 0x80 != 0) print_debug_page0("SKY %x", status); // above floor
if (status & 0x100!= 0) print_debug_page0("OUT SCOPE %x", status); // ?
}
```
Note that there are other flags from `0x200` onwards, but those aren't drawn. 'Out scope' is the most mysterious, it's never set or read elsewhere, the name suggests it's set when an object is out of bounds, but that's not the case?
> Scope could mean "telescope". Reminds me of the view you get after jumping into a cannon.[name=GlitterBerri]
![](https://i.imgur.com/prR84nm.png)
Finally, the tornado prints 'off' on page 0 when it's leaving the center of the quicksand to chase Mario.
### Page 1: CHECKINFO
This page is concerned with collision checking.
![](https://i.imgur.com/bk8Q9D0.png)
`AREA`: which grid square Mario is in. The game world is split up into a 16x16 grid of 1024x1024 cells. To speed up collision checking, only triangles that are in the relevant area are checked.
![](https://i.imgur.com/mDAMY68.png =400x)
*I swapped the digits in the image, will fix it at some point*
The rows `DG`, `DW` and `DR` are for floor, wall and ceiling triangles. The first column shows how much of them are in Mario's current `AREA`, and the second column displays how many checks are done for them. Not only checks done by Mario, but by any part of the code.
> G = Ground, W = Wall, and R = Roof.[name=GlitterBerri]
`STATBG` and `MOVEBG` are how many static and moving triangles are currently present. Static triangles are initialized once at the start of the level and never change, but moving triangles are part of objects and they are transformed and stored in the triangle grid every frame. This takes a lot of time, which is why the submarine in Dire Dire Docks causes so much lag: since it's only there in mission 1, it can't use static triangles and is consequently updated every frame.
`LISTAL` is the sum of the amount of triangles for every area square. So if a triangle is stored in three grid cells, it counts for three. The total amount of triangles may not exceed 2300, and the total amount of list nodes may not exceed 7000 or the buffer overflow. While there is still code that checks for such overflows, it doesn't do anything if that happens.
```C
//383340:
void initialize_triangle_datastructures()
{
max_triangle_count = 2300; //38EEA0
triangle_node_list = (struct Node*) malloc(sizeof(Node)*7000, 0); //38EE98 = fn277B70, 7000*8
triangle_list = (struct Triangle*) malloc(sizeof(Triangle)*max_triangle_count, 0); //38EE9C = fn277B70
short0x35FEEC = 0; //?
fn2DA4DC(); //?
}
//382490:
struct Node* get_free_list_node()
{
struct Node* node = triangle_node_list[triangle_node_index];
triangle_node_index += 1;
node->next = 0;
if (triangle_node_index >= 7000)
{
// Well, tough luck
// There probably used to be code here, but there's none
// in the final build. The dead if-statement is not optimized away
}
return node;
}
```
### Page 2: MAPINFO
![The mapinfo page](https://i.imgur.com/Ozg7ToM.png)
`AREA` shows, again, which area of the triangle grid Mario is in.
`WX`, `WY`, `WZ` is Mario's position in the world.
`ANGY` is Mario's Y angle in degrees.
The ones with `BG` are related to Mario's floor triangle.
`BGY` is the height of the ground directly under Mario.
`BGCODE` is the ground type. 0 is default, but there are different types for grass, sand, water flow etc.
`BGSTATUS` is the status byte of the floor triangle. It contains miscellaneous bit flags. Bit 0 is set when the triangle belongs to an object, bit 1 means the camera can pass through it. Bit 3 means the wall's hitbox extends in the x-direction instead of the z-direction, but you won't see that here since that's a property of walls, not floors. Bit 2 seems unused.
`BGAREA` is which level area the floor triangle belongs, just like in page 0 with 'Areainfo'. (not to be confused with the `AREA` in the collision triangle grid)
`DPRINT OVER` means debug print overflow. When the cursor isn't within a certain y range, newly drawn lines will change into "DPRINT OVER".
So what's hidden under there? Decreasing the minimum Y shows us that there's two things: the same broken object counter and a water level indicator which only shows up when Mario's y-value is less than or equal to the water level.
### Page 3: STAGEINFO
![](https://i.imgur.com/6QizGo1.png)
This page only shows the stage parameter, and it is only used for one thing: The speed setting on Tick Tock Clock. The angle of the big hand of the entrance clock determines how the platforms move inside the stage, and this variable keeps track of that.
![](https://i.imgur.com/PmNjHw9.png =300x)
It's weird that this one variable got its own page. Wet Dry World's water setting (which depends on how high you jump into the painting) does not use this stage parameter variable. Also, paintings actually have three different triangle types layed out horizontally. While they are all identical in the final game, it suggests that there used to be course variations based on whether you entered the painting on the left, middle or right.
### Page 4 and 5: EFFECTINFO and ENEMYINFO
The last two pages, 4 and 5, effectinfo and enemyinfo, are perhaps the most mysterious of all: They always stay zero, and the names don't give much away either. Looking at the code, it turns out that these were not meant to monitor values, but rather to modify them.
> [color=#99AADD] With **D-pad** **up** and **down**, you can change the row.
> With **left** and **right**, you can modify the value between -32768 and 32767 inclusive.
> You can hold the D-pad in a direction to repeat the presses so you don't have to button mash.
> You can hold **B** to modify the selected row 100 at a time instead of 1.
> Holding **A** and pressing **D-pad** **left** returns it to a default value stored in memory, which is always zero.
It's curious that they aren't hard-coded to be set to 0, perhaps they were once set to a non-trivial default value?
## Things affected by effectinfo and enemyinfo
For a video demonstration of these effects, see [Effectinfo and EnemyInfo - SM64 Debug code](https://youtu.be/M8cSGD6l9n0) on YouTube.
### Particles
`EFFECTINFO` was likely meant to tweak particle effects, since guessing the right parameters to make them look good is hard and recompiling every time to see them in action is time consuming. I could only find one particle affected by this, though:
![Yellow triangles from punching a wall](https://i.imgur.com/EN1mzee.png)
The yellow triangles that appear when your punch hits a wall exist for 7 frames, but the value at A0 can make their lifetime shorter or longer.
### Jolly Roger Bay ship
![The ship of Jolly Roger Bay translated and scaled via enemyinfo](https://i.imgur.com/vMle1uS.png)
The floating ship in Jolly Roger Bay is offset in X, Y and Z by the values at B0, B1 and B2. It's also scaled by 1+B3/100.
### Rotation script
There is a function that can be used to set the rotation and rotation speed of an object. It is assigned to an unused checkerboard block, likely used to test rotational displacement.
```C
//2C5360:
void rotate_from_enemyinfo()
{
if (enemyinfo[0] == 1) // reset rotation
{
//fn29E1F0
set_moving_and_actual_rotation(current_object, 0, 0, 0);
//0x0114
current_object -> rot_speed.x = 0;
current_object -> rot_speed.y = 0;
current_object -> rot_speed.z = 0;
}
if (enemyinfo[0] == 2) // set rotation
{
//offset 0x0D
current_object -> real_rotation.x = enemyinfo[1] * 4096;
current_object -> real_rotation.y = enemyinfo[2] * 4096;
current_object -> real_rotation.z = enemyinfo[3] * 4096;
}
current_object -> rot_speed.x = enemyinfo[4];
current_object -> rot_speed.y = enemyinfo[5];
current_object -> rot_speed.z = enemyinfo[6];
if (enemyinfo[0] == 3) // integrate rotation speed
{
//offset 0x0D, 0x114
current_object -> real_rotation.x += current_object -> rotation_speed.x;
current_object -> real_rotation.y += current_object -> rotation_speed.y;
current_object -> real_rotation.z += current_object -> rotation_speed.z;
}
}
```
### Transparency
There is a GFX node that preceeds models that are drawn with transparency. There are two types of transparency: First there is dithering, which is used on Mario and Bowser. This means that a percentage of the pixels is drawn creating a noise pattern. Other objects, like Toad and Peach, use alpha blending where pixels are blended in with the background. By setting `B3` to 1, they get the dithering transparency too.
![Peach with alpha blended transparency and dithered transparency](https://i.imgur.com/EdB0Rds.png)
Note that many emulators (including Wii Virtual Console, but excluding Wii U Virtual Console) emulate dithering just like alpha blending.
### Spawn debug
![A crazy box, water shell and ground shell](https://i.imgur.com/mGFzE05.png)
If the value at B7 is 1, and you go back to page 3 with the `STAGE PARAM`, you can spawn three different items using the D-pad: D-pad left spawns a crazy box, D-pad down an underwater shell, and D-pad right a normal shell. This debug spawn was already known ([found by yoshielectron](https://www.youtube.com/watch?v=i7UxdrkMFmg)), and is often listed as a separate debug mode. But it turns out to be one of the enemyinfo functionalities, which also explains why it requires the debug page to be 3: otherwise using the D-pad for any other debug controls would constantly cause shells and boxes to be spawned.
### Bowser's attacks
There is a function that sets the mode of an object. It's not attached to any object and could have been used for any object, but it's most likely used for Bowser. I don't know of any other object that has 20 modes, and controlling which attacks he does makes testing a lot easier than waiting for the right conditions and using RNG trigger it. These are the modes you can set:
```C
//32F4FC
char[] commandActions =
{0x07, 0x08, 0x09, 0x0C,
0x0D, 0x0E, 0x0F, 0x04,
0x03, 0x10, 0x11, 0x13,
0x03, 0x03, 0x03, 0x03};
```
In the case of Bowser, this maps to the following actions:
```
00 running towards mario
01 breathing fire rain
02 quick fire breath
03 explosion knockback (not fatal)
04 high jump, tilts platform (fire sea) or creates shocks
05 walking
06 breathing fire left and right
07 explosion knockback (fatal blow)
08 raging
09 teleporting
10 jumping towards mario
11 (fire sea) standing on tilted platform
12 raging
13 raging
14 raging
15 raging
```
You set B2 to one of the 16 values (it only considers the last 4 bits, so if you go outside that range it wraps around) and then change B1 to something non-zero. B1 will then immediately be set back to 0 and the command is given.
![Bowser gets explosion knockback out of nowhere](https://i.imgur.com/dXinu83.png)
### Bowser's unused hitbox
There's an unused behavior script for an object that stays a fixed distance in front of Mario.
The size of its cylinder hitbox are determined by effectinfo: `radius = effectinfo[0]+100` and `height = effectinfo[1]+300`. It's in the same object group as Bob-ombs, explosions and grabbable cork boxes. Whenever it collides with an object, it sets the flags at `0x134` in the object struct of that object to `0x0080C001`. This causes a Bob-omb to explode, a Chain Chomp to shoot up, a Goomba to die, etc.
Looking at the position and size of the hitbox, it seems to have been used for Bowser when Mario grabbed him by the tail. You see, Bowser actually consists of three objects: the main object, a hitbox and a tail anchor. When you grab Bowser, the main object becomes invisible and Mario enters a 'grabbed Bowser' state. Bowser's graphic is now part of Mario's object, while the actual Bowser object remains stationary. That means his normal hitbox isn't positioned correctly.
With this object spawned, there is a hitbox following grabbed Bowser's shell with radius 300 and height 100, the same as normal Bowser. Though the effectinfo and enemyinfo values
>...? Something's missing here. [name=GlitterBerri]
Notably, though, it doesn't cause mines to explode. That requires the flag `0x00200000` to be set. So does this mean that, originally, enemies were present during Bowser fights?
### Bowser platforms
In the final Bowser fight, the platforms that are not part of the center star depend on effectinfo.
After the second hit, Bowser rages and the remaining triangular parts of the platform crumble, carving out the final star shape. The time between these drops is 20 frames, but it can be increased or decreased by `enemyinfo[1]`.
Before a triangle piece of the platform falls, two lines of smoke spawn along the sides of the triangle, starting at one of the two outer vertices and moving to the vertex closest to the center.
With `enemyinfo[6]` these lines can be rotated. The value is multipled by 256 so setting `A6` to 128 makes the smoke lines rotate 180 degrees. It seems the developers had trouble aligning the smoke spawners with the edges of the platform.
### Wing cap wings
```C
//2A3E84:
void transform_next_node(int enable, struct GfxNode* node, struct Struct80278464 *pool)
{
if (enable != 1) return 0;
// next is offset 0x08, effectinfo at 0x33BF10 + 0x40
(struct GfxType015) (node -> next) -> pos.x = effectinfo[0]; //0x18
(struct GfxType015) (node -> next) -> pos.y = effectinfo[1];
(struct GfxType015) (node -> next) -> pos.z = effectinfo[2];
(struct GfxType015) (node -> next) -> rot.x = effectinfo[3];
(struct GfxType015) (node -> next) -> rot.y = effectinfo[4];
(struct GfxType015) (node -> next) -> rot.z = effectinfo[5];
return 0;
}
```
This code is not used anywhere, but the signature suggests that it's used on a GFX node with type `0x12A`: it would be placed just before a GFX node that needs to be modified via effect info. So we're looking for a GFX node type that has six shorts starting at 0x18. Type 0x15 seems to match since it has two short vectors: position at 0x18 and rotation at 0x1E. Gfx type 0x15 is not used often, but it is the type for the wings on Mario's cap.
I suspect that this functionality was used to position and rotate the wings on the wing cap. The final values are:
```
A0: 142
A1: -51
A2: -126 (126 for left one)
A3: 4004 (-4004 for left one)
A4: -7281 (7281 for left one)
A5: -24576
```
### Transition position
```C
struct Transition
{
/* 0x00 */ unsigned char red;
/* 0x01 */ unsigned char green;
/* 0x02 */ unsigned char blue;
/* 0x04 */ short start_radius;
/* 0x06 */ short end_radius;
/* 0x08 */ short start_x;
/* 0x0A */ short start_y;
/* 0x0C */ short end_x;
/* 0x0E */ short end_y;
}
//2CAA84:
void setTransitionPositionFromEnemyinfo(struct Transition* transition)
{
transition->start_x = enemyinfo[1];
transition->start_y = enemyinfo[2];
transition->end_x = enemyinfo[3];
transition->end_y = enemyinfo[4];
}
```
This code is called nowhere. The struct it affects seems to be the screen transition struct.
The struct it affects is located at `0x33A744`.
## 'Official' variable names
While all the symbol names of the source code are lost after compilation to a ROM, this debug mode shows us what the variables are likely called.
Given that:
* The floor triangle variables are prefixed with "BG" in the `MAPINFO`
* The movement flag for being on the ground is called "BOUND"
* There is a `WALL` counter that's probably related to collision checking
* `DG`, `DW` and `DR` correspond to floors, walls and ceilings
I believe that they were internally called "**G**round, **W**all and **R**oof Boundaries" instead of "Floor, Wall and Ceiling triangles". Level triangles are called 'static' and object triangles are called 'moving' (`STATBG` and `MOVEBG`). I wonder what `LISTAL` stands for, 'List all'? 'List Area Length'?
`SPD` and `ANGY` imply they used 'Speed' and 'Angle Y' instead of 'Velocity' and 'Rotation' or 'Yaw'.
## Bugs
There are several bugs or oddities regarding the debug functions.
### Wrong object counter
The object counter in the DPRINT menu is supposed to show the number of loaded objects, but it misses some.
There are 10 object groups that get processed every frame in a certain order.
![The 10 object groups](https://i.imgur.com/p2WBfWl.png =600x)
Group 12 contains some (not all) particles that are created in bulk. Whenever all 240 object slots are full, these are unloaded to make room for objects in other groups. Group 9 contains objects with collision triangles, though it also includes the signs on the castle walls, which don't have collision. Group 10 contains poles, but also the tornado?
The bug is in this function:
```C
//29CB9C:
void process_first_two_groups()
{
//obj count at 0x33BF00, object group array pointer at 0x35FD78,
//offsets 11*0x68 = 0x0478 and 9*0x68 = 0x03A8
obj_count = process_object_group(&object_group[11]);//fn29C5A8
obj_count = process_object_group(&object_group[9]);
}
```
The second `=` should be a `+=`, likely a copy-paste error. This function processes the rest of the groups. Note that group and 11 and 9 are in the group order array, but the for-loop starts at index 2:
```C
//29CBEC:
void process_rest_of_groups()
{
int index;
for (index = 2; group_order[index] != -1; index += 1)
{
obj_count += process_group(&object_group[group_order[index]]);
}
}
```
*Hypothesis*: I suspect that at first, all object groups were processed in one go. Then they found that some logic needed to be executed *before* Mario and enemies were processed but *after* objects with collision are processed. So they put the processing of group 9 in a separate function. Then they found that some spawners should be executed before anything else, so they created group 11 and made it process first by copy-pasting the line in `process_first_two_groups` of group 9 without realizing / caring that the obj_count is now incorrect.
```C
process_first_two_groups(); //fn29CB9C
move_player_with_moving_platforms(); //fn2C83F0
check_inter_group_collisions(); //fn2C8C44
process_other_groups(); //fn29CBEC
```
### Dprint state not reset during pause
```C
struct DprintState dprint_top_left_corner; //0x35ff20;
struct DprintState dprint_bottom_right_corner; //0x35ff30;
//2CA140:
void reset_debug_counters() {
null_bg_count = 0; //u32 33BEF4
wall_count = 0; //u32 33BEFC
obj_count = 0; //u32 33BF00
byte33ff3c = 0; //this is actually a pointer?
short35fee2 = 0; //they stay zero
short35fee4 = 0; //changes when going through doors
//fn2C9938,
initialize_dprint_state(&dprint_top_left_corner, 20, 185, 40, 200, -15);
//-15 located at 2CA180 + 2
initialize_dprint_state(&dprint_bottom_right_corner, 180, 30, 0, 150, 15);
//15 located at 2CA1A8 + 2
handle_dpad_rapid_press(); //fn2CA004
}
```
The function that resets all global debug variables (cursor position, collision check counters) is called in the function that also processes objects. When the game is paused, this won't get executed, so:
* When you pause, the dprint text will move downwards out of the screen
* When you unpause, the collision counters 'spike' with all accumulated checks since the pause menu was opened
### Uninitialized variable
Chuckya draws a 'FG' variable which contains garbage. It reads a certain variable from the stack (Stack Pointer + 0x3C) but that variable is never set. This is most likely the result of an uninitialized variable:
```C
int fg; // no value given
...
dprint_bottom_right("fg %d", fg);
dprint_bottom_right("sp %d", (int) current_object->hspeed);
```
The code that actually sets fg is most likely removed, commented out or inside an `#ifdef DEBUG`.
In any case, the number it prints is whatever was put there on the stack before. In RR, it's 0. In Tall Tall Mountain, it happens to be Mario's x position reinterpreted from a float to an int, and then incorrectly converted to a large number that clips the right side of the screen. This brings us to:
### Wrong number to string conversion
```C
//2D5374:
void write_number_to_string(int number, int base, char* string, int* cursor,
unsigned char min_length, char pad_with_zeroes)
{
int digit_amount = 0;
...
//2D53E8:
while (1)
{
unsigned int ret = pow(base, digit_amount); //fn2D5320
if (number < ret) break; //unsigned
digit_amount += 1;
}
```
For converting the number to a string, first the amount of digits is counted. This is done by calling `pow(base, digit_amount)` with an increasing digit_amount until it finds a number that is higher than the number to print. In the case of base 10, this goes wrong when the number nears the limit of what fits in a 32-bit integer. One billion still fits, but `pow(10, 10)` evaluates to about 1.4 billion instead of 10 billion because of overflow. For any number below `1410065408` this is still okay. But once it exceeds that, it tries `pow(10, 11)` which overflows to `1215752192`. This is still lower. Then `pow(10, 12)` is tried: 3567587328. That's higher, so a number with 10 digits is assumed to have 12 digits. So when it tries to convert digits to a string:
```C
for (int i = digit_amount-1; i >= 0; i-=1)
{
int ret = pow(base, digit_amount-1); //fn2D5320
char digit = number / ret;
...
```
It goes completely wrong.
In the case of base 16 it's even worse: `pow(16, 8)` evaluates to 0. So does`pow(16, 9)`, and `pow(16, 10)`... It ends up in an endless loop and it will appear as if the game crashed (though no exception was thrown by the processor, the game logic thread is just in an endless loop).
### Null dereference when Out of Bounds
Unlike `SPD` and `STA`, the `ANG` value displayed in 'classic debug' display is not read from Mario's data. It is calculated on the fly based on Mario's floor triangle. Because there is no check for whether the triangle isn't null, the game will crash when trying to dereference this null pointer.
```C
char debug1Flag; //32C658
//2534F4:
void print_classic_debug(struct Player* player)
{
if (debug1Flag == 0) return;
struct Triangle* tri = player->floorTriangle;
//tri->normal.x can crash!
float normal_hdis = sqrt(tri->normal.x * tri->normal.x
+ tri->normal.z * tri->normal.z); //fn322B20 SP2C
int angle = (int) arctan(tri -> normal_y, normal_hdis); //fn37A9A8
int angleDegrees = (int) (((float) angle * 180f) / 32768f);
draw_string_format(210, 88, "ANG %d", angleDegrees);
draw_string_format(210, 72, "SPD %d", (int) player->hspeed) //(offset 0x0054)
draw_string_format(210, 56, "STA %x", player->state & 0x01FF) //(offset 0x0C)
return;
}
```
The programmers tried really hard to make the presence of a floor triangle an invariant. Mario can't move OoB, get pushed OoB by objects, release things OoB (without HOLP manipulation) etc. But still there are situations where it's possible, like climbing on a ledge near an OoB zone or being above only a moving triangle that then moves out under Mario.
### Dead assignments of rotation speed
Going back to the rotating checkerboard block:
```C
//2C5360:
void rotate_from_enemyinfo()
{
if (enemyinfo[0] == 1) // reset rotation
{
set_moving_and_actual_rotation(current_object, 0, 0, 0);
//Dead assignments here!
current_object -> rot_speed.x = 0;
current_object -> rot_speed.y = 0;
current_object -> rot_speed.z = 0;
}
if (enemyinfo[0] == 2) { /* ... */ }
// This overrides it anyways!
current_object -> rot_speed.x = enemyinfo[4];
current_object -> rot_speed.y = enemyinfo[5];
current_object -> rot_speed.z = enemyinfo[6];
```
When `b0` equals 1, the rotation speed is supposed to be reset. It doesn't return in the if-statement, however, so later it gets set to `b4`-`b6` anyways.