Mario Maker 2 Ninjis

Created Monday, May 6, 2024

In another offshoot of my Mario Maker 2 Datasets project, which was itself an offshoot of the Mario Maker 2 API, I have been using the ninji data I scraped to render visualizations of each speedrun and the performance of the players. This blogpost is about the first part of that exploration: rendering speedruns as a trajectory visualization.

link Ninjis From The Server

As an initial start I counted the number of replays for each ninji event, each one obtained from the dataset and by using a call to https://tgrcode.com/mm2/ninji_info. The numbers are as follows:

  • "Rolling Snowballs": 365366
  • "The Speedventure of Link": 204849
  • "The 10-Coin of Deep Woods": 209097
  • "Cat Mario Dash": 202793
  • "Banzai Bill Cliff Climb": 168703
  • "Swinging Claw Flyway": 189040
  • "Headgear Hustle": 218865
  • "Balloon Race": 220576
  • "Yoshi’s Piranha Plant Picnic": 150860
  • "Player’s Choice: Power-Up Party": 140888
  • "Big Shoes Gustin' in the Desert": 108323
  • "Squirrely Airship Escapades": 121491
  • "At the Croak of Midnight": 69813
  • "35th Anniversary Auto-Mario": 133055
  • "Cannon Box Blast!": 78050
  • "Goombud Bust-Up": 100664
  • "SMB2 Mario: Can You Dig It?": 93664
  • "Dry Bones Shellscape": 84862
  • "Cape Mario Master": 52811
  • "Bowser’s Castle: The Last Dash": 101019
  • "Link’s Lightweight Longshots": 73792

Number of players in each Ninji event

link Parsing the File Format

In order to render the ninji replays as they were shown in game I needed to reverse engineer the binary sent to the client. One initial question I had was whether this binary contained inputs, which would be played back by the game with accurate physics for every replay rendered, or positions, which would simply render the replay in a specific position on each frame.

As a base I started with Kinnay’s reverse engineering work. This made it clear that ninji replays contained positions. (notably, there is a place in SMM2 where input replays are recorded, I’ll be making a blogpost about that in the future). With some further research and testing I modified the format to the following:

OffsetSizeDescription
0x00x3CFile header
0x3C0x249FAReplay frames

link File Header

Big endian.

OffsetSizeDescription
0x04Version number (always 2)
0x44Unknown
0x84Unknown (usually 64)
0xC4Time in milliseconds
0x104Number of frames
0x141Character
0x151Unknown (usually 1)
0x162Unknown (usually 4)
0x184Unknown
0x1C4Unknown (maybe points?)
0x204Unknown (usually 0)
0x244Unknown (usually 0)
0x284Unknown (usually 0)
0x2C4Unknown (usually 0)
0x304Unknown (usually 0)
0x344Unknown (usually 0)
0x384Magic number (always SPGD)

link Replay Frame

Corresponds to every 4 frames. Little endian with no padding.

OffsetSizeDescription
0x010xAB
A: Flags
B: Player state
0x12X position (tiles are 16x16, centered on tile)
0x32Y position (tiles are 16x16, centered on tile)

If flags & 6:

OffsetSizeDescription
0x51Unk1

If Unk1 & 24:

OffsetSizeDescription
0x62Unk2

link Flags

A bitmask.

ValueDescription
x & 0100In pipe transition
x & 1000In subworld

link Character

ValueDescription
0Mario
1Luigi
2Toad
3Toadette

link Player state

link All

ValueDescription
0Run
1Jump
2Swim
3Ground pound
5Slide down slope
7Dry bones shell
8Clown car
9Cloud
10Boot
11Run

link SMB1

ValueDescription
4Link suit downward sword
5Crouch

link SMW

ValueDescription
10Yoshi
12Cape feather glide
13P balloon moving
14P balloon stationary

link NSMBU

ValueDescription
4Slide down slope
6Slide down wall
10Yoshi
12Acorn suit glide
13Propeller suit fly
14Propeller suit glide

link SM3DW

ValueDescription
4Slide down slope
6Slide down wall
7In pipe
8Cat suit dive
12Bullet bill mask
13Propeller box fly
14Propeller box glide

link Researching Visualizations

Next I started writing code to parse the replays and render them. I used C++ and Skia, as the number of replays, each with possibly 300 or more replay frames, would strain any other rendering library. Skia was designed to render webpages in Chrome by Google, so it’s extremely fast.

A good visualization is one that can encompass the knowledge of the entire event in one picture. That is how I came across a trajectory visualization, which is often used in movement data.

I applied a exponential decay curve to the colors of each replay, as it is guaranteed to be 0 at 0 and 1 at 1. This allowed me to use a color for the fastest time, which is lime green, and the slowest, which is red. The top 10 runs were colored aqua blue to set them apart.

The equation is as follows:

\(HSV = \left(15 + (1 - p)^{\frac{1}{d} - 1} \times 0.95, 1, 0.75\right)\)

Where p is linearly 0 for the fastest time and 1 for the slowest. d is a chosen value, tuned to show the differences in times best.

  • "Rolling Snowballs": 0.001
  • "The Speedventure of Link": 0.03
  • "The 10-Coin of Deep Woods": 0.03
  • "Cat Mario Dash": 0.04
  • "Banzai Bill Cliff Climb": 0.03
  • "Swinging Claw Flyway": 0.01
  • "Headgear Hustle": 0.03
  • "Balloon Race": 0.02
  • "Yoshi’s Piranha Plant Picnic": 0.01
  • "Player’s Choice: Power-Up Party": 0.04
  • "Big Shoes Gustin' in the Desert": 0.05
  • "Squirrely Airship Escapades": 0.02
  • "At the Croak of Midnight": 0.05
  • "35th Anniversary Auto-Mario": 0.03
  • "Cannon Box Blast!": 0.07
  • "Goombud Bust-Up": 0.05
  • "SMB2 Mario: Can You Dig It?": 0.04
  • "Dry Bones Shellscape": 0.03
  • "Cape Mario Master": 0.03
  • "Bowser’s Castle: The Last Dash": 0.04
  • "Link’s Lightweight Longshots": 0.09

For example, for "The Speedventure of Link" the color curve looks like this:

Desmos color curve

This curve highly punishes even remotely slow players. This is because in practice there will always be players who are as slow as possible, thus the players that represent those attempting to improve their runs significantly usually constitute only the top 10% of runs.

Next, I started rendering lines. Because ninji replay frames happen only ever 4 render frames, or around every 66 milliseconds, the resulting replays look rather polygonal when rendered point to point. Because of the lack of information present in the replay this is an unavoidable fact. I chose not to render the replays with splines and instead just rendered pixel-perfect lines between the positions.

link Trajectory Visualizations

Click to zoom into each visualization!

Rolling SnowballsThe Speedventure of Link
The 10-Coin of Deep WoodsCat Mario Dash
Banzai Bill Cliff ClimbSwinging Claw Flyway
Headgear HustleBalloon Race
Yoshi’s Piranha Plant PicnicPlayer’s Choice: Power-Up Party
Big Shoes Gustin' in the DesertSquirrely Airship Escapades
At the Croak of Midnight35th Anniversary Auto-Mario
Cannon Box Blast!Goombud Bust-Up
SMB2 Mario: Can You Dig It?Dry Bones Shellscape
Cape Mario MasterBowser’s Castle: The Last Dash
Link’s Lightweight Longshots

link Questions?

Use the Contact button to the side or join my Discord.