The current URL is datacrystal.tcrf.net.
Super Mario Bros. 3/Notes
The following article is a Notes Page for Super Mario Bros. 3.
Common Nintendo Elements
Super Mario Bros. 3 uses a number of common Nintendo data structures and algorithms:
- Display List Nintendo_Entertainment_System#Data_Formats
- RNG Nintendo_Entertainment_System#Nintendo_Linear-Feedback_Shift_Register_RNG
- Sound Engine
Disassembly/reverse engineering notes
- NN: all values are hexadecimal and at least two digits (e.g. 0F, not F) unless noted otherwise.
- $NN or $NNNN: a memory address.
- Countdown timer: a byte in memory set to some value, decremented every frame and when it reaches 0 something is usually set up to happen. Many timers are cyclic, meaning that they are being re-set and reused immediately after reaching 0.
Player physics/movement
Horizontal movement
- If the player holds L/R the horizontal velocity $BD will start to increase (to-do: what's the exact increment?) at around 1 per frame. The same acceleration is always used when walking/running/dashing.
- abs($BD) can at most reach a limit that varies depending on things below.
- If WALKING, the limit is 18.
- If player holds B,
- The limit is raised to 28 and $BD is allowed to increase again. This is RUNNING.
- When abs($BD) is >= 28, you are DASHING and the P-timer $515 cycles from 7. Every time it elapses an arrow is added to the P-meter.
- When the P starts blinking, the player is in "flight mode":
- 1) The limit is raised to 38 and $BD is allowed to increase again.
- 2) The P-timer is LOCKED to the value 0F (don't know if the exact value has significance).
- 3) Pressing A at this point launches the player into flight/super-jump, indicated by $57B being set to 1.
- 4) The flight duration timer $56E is set to 80 and decrements every other frame.
- Player exits "flight-mode" ($57B = 0) when one of the following happens: (to-do: incomplete)
- 1) Player touches ground.
- 2) Flight-limit timer $56E elapses.
- 3) Player lets go of the A button.
- Once no longer in "flight-mode", the P-timer $515 cycles from 23. Every time it elapses an arrow is removed from the P-meter.
- You can only refill the P-meter again by accelerating to abs($BD) >= 28 as above. (If $515 hasn't had time to elapse, i.e. the P-meter hasn't decreased, you can stay "eligible" for $57B flight-mode by doing short hops of flight and staying at >= 28.)
$AC5A Jump power
$AC5A:A5 BD LDA $00BD ; Load player horz velocity $AC5C:10 03 BPL $AC61 ; If negative, $AC5E:20 0F DD JSR $DD0F ; take absolute value $AC61:4A LSR ; Divide by 16 $AC62:4A LSR $AC63:4A LSR $AC64:4A LSR $AC65:AA TAX ; X = A, going to use it as an index $AC66:AD 47 A6 LDA $A647 ; Load default jump velocity $AC69:38 SEC $AC6A:FD 48 A6 SBC $A648,X ; Subtract from the jump velocity (remember lower means more power) using the table 00,02,04,08. ; Thus a higher horizontal speed means a more powerful jump. $AC6D:85 CF STA $00CF ; Store as new vertical velocity.
$ACA1 Application of gravity
$ACA1:A0 05 LDY #$05 ; Y = default falling gravity $ACA3:A5 CF LDA $00CF ; Load current vertical velocity $ACA5:C9 E0 CMP #$E0 ; $ACA7:10 0D BPL $ACB6 ; If currently rising and v vel is still faster than E0, $ACA9:AD 79 05 LDA $0579 ; Don't know what 0579 is... unused? Debugger never sees a nonzero value. $ACAC:D0 0D BNE $ACBB $ACAE:A5 17 LDA $0017 ; Read gamepad. 80 is jump key, so value will appear negative! $ACB0:10 04 BPL $ACB6 ; If jump pressed, $ACB2:A0 01 LDY #$01 ; Y = jump gravity (lower than normal) $ACB4:D0 05 BNE $ACBB ; $ACB6:A9 00 LDA #$00 ; This is run if jump key is NOT pressed $ACB8:8D 79 05 STA $0579 ; So what does it do? $ACBB:98 TYA ; A=Y $ACBC:18 CLC ; $ACBD:65 CF ADC $00CF ; Add gravity to current vertical velocity $ACBF:85 CF STA $00CF ; And store back
So, jumps are aborted either by the user releasing the button (bit 7 in 0017 is zero) or the 00CF is more positive than E0. Since SOME gravity is always applied, this ensures jumps are aborted.
$BFCC (Subroutine) Clamp Y velocity to the maximum
$BFCC:A5 CF LDA $00CF ; Load current vertical velocity $BFCE:30 08 BMI $BFD8 ; Negative? Then skip clamping. $BFD0:C9 40 CMP #$40 ; #$40 is the maximum fall vel (note: gravity is added afterwards so the effective value is 45) $BFD2:30 04 BMI $BFD8 ; Less than this? Then skip clamping. $BFD4:A9 40 LDA #$40 ; Replace $00CF with the maximum fall vel. $BFD6:85 CF STA $00CF $BFD8:A2 12 LDX #$12 ; UNKNOWN $BFDA:20 93 BF JSR $BF93 ; UNKNOWN $BFDD:60 RTS
Metatiles
In Super Mario Bros. 3, like many NES titles, background (nametable) entities are generally rendered in 2x2 tile blocks (16x16 pixels). These are frequently referred to as metatiles. For SMB3, there are 11 metatile banks, each containing 256 metatile slots. Each bank is made up of four tables of tile IDs representing the northwest, southwest, northeast, and southeast tiles of a 2x2 CHR metatile. Additional banks utilize smaller, specialized metatile tables to render 2x2 tile blocks.
Bank | Style | BG Bank 0 | BG Bank 2 | OBJ Bank 0 | OBJ Bank 1 | OBJ Bank 2 | OBJ Bank 3 |
---|---|---|---|---|---|---|---|
0C | Map | 14 (Ani: 70, 72, 74) | 16 | 20 | 21 | 22 | 23 |
0D | Underground | ||||||
0E | Battle | ||||||
0F | Plains | ||||||
10 | Hills | ||||||
11 | Mountains and Ice | ||||||
12 | Water, Toad Houses, and Pipe Mazes | ||||||
13 | Pipe World, Giant World, and Clouds | ||||||
14 | Desert | ||||||
15 | Fortress | ||||||
16 | Bonus | ||||||
17 | Airship | ||||||
1A | HUD | 5C | 5E | 48 | 49 | 05 | 4B |
Each bank except the HUD begins at 0xA000 in the CPU memory map and indeed the engine expects to find a precisely 1024-byte map of metatiles at the start of each of these banks. The metatile is then simply a 2x2 block composed of the respective member from each table. Larger scenery graphics are then composed from these metatiles. The identity of metatiles cannot be determined alone from their bytes. Rather, each map corresponds with tiles in specific CHR banks placed into PPU address space by the MMC3 mapper. Source code for the various consumers of metatiles must be consulted to determine the correct CHR banks for each set. When these have been better documented, they will be added to the above table. Note that some scenery types include animations that are performed by swapping between several banks with the animation frames in the correct places.
The HUD is a special case in that much smaller metatile tables are used for the population of powerup and bonus card images, but otherwise the assembly of the borders of the HUD dialogs is achieved via display lists. The CHR banks themselves are loaded in by the IRQ handler. Those given in the above table are the most typical case, although different banks may be used for different specialized IRQ handlers. In the case of the default banking, only OBJ graphics from OBJ bank 2 bear any significance to the rendering of the HUD, the other banks contain graphics for boss battles. The only reference in HUD rendering to any of these tiles are to the first tile of the first bank for the null item entry. This graphic would be used if the null item could be selected in the menu, but it cannot be as it is a passthrough object. The other OBJ tiles from bank 2 are used for highlighting items in the menu when selected.
Bumped Blocks
In addition to table-based metatile rendering, a mechanism is offered to draw an individual metatile stored at 0x36E, in nw-ne-sw-se order, to the nametable address stored at 0x36C on the next vertical interrupt, with a 0 in 0x36C skipping this action. This mechanism makes no adjustments for scroll boundaries and simply adds 32 to the address register to write to the next row. In practice, this mechanism is used for the bouncing of blocks to rapidly write either blank or block graphic data to the screen where a bounced block sprite needs to be moved. This mechanism reappears from Super Mario Bros. as a means to preserve the sprite count and only render a block as a sprite when it needs to smoothly bump, otherwise rapidly masking and restoring the nametable bytes to render it in the background.
Palettes
A general palette loading framework is provided with a table of palettes assigned to each course type as well as a few extra bits of explicit logic for three variable palettes.
The determining factor in loading a palette is either a byte at $1A, which goes unused, but allows selecting the first palette of any non-map group of palettes, overriding any conventional palette selection. This mechanism forces an overarching course-based palette but does not allow selection of the specific entry in that type's palette selections. The BG and OBJ palettes are loaded from the first and second pages in the table respectively.
When this mechanism goes unused, which is always, the palette is instead selected in chunks, BG and OBJ, based on the IDs placed at $73A and $73B. Each value is multiplied by 16 to index one of the palette pages provided. This allows for up to 16 palette entries to be provided, but the limiting factor becomes the amount of space in the corresponding ID fields in course types to store a palette index.
General Palettes
In the most general case, the background palette of a course is assigned based on a byte in the course header, with the bitfield supplied only allowing 8 background palette selections. The object palette can then be selected from 4 additional palette selections encoded in the next 2 bits. These 4 additional selections are stored immediately following the 8 possible background entries, leading to a total of 12 entries per table. Much of this logic is done with bitwise binary logic, so adjustment to non-power-of-two numbers of selections would be tricky. Additionally, the number of selections for basic courses is a function of how much space is provided in the course headers to encode the selections, so changes in this format would be required to accommodate changes in the palette table sizes.
Map Palettes
Map palette loading is much simpler and is achieved via a couple of look-up tables, each containing one entry per world. While not required, the BG and OBJ lists are still separated into 8- and 4-entry sequential groups. Two object palette sets are filled but go unused. This table could be expanded to 16 entries and arbitrarily indexed if so desired.
Bonus Palettes
One palette table is shared amongst the various bonus games, so the type is first checked, and depending on the result, the 1st or 2nd entries of the BG and OBJ sections respectively are explicitly loaded rather than consulting a course header. Otherwise presumably the typical logic is used. This mechanism could exist outside the normal table completely by assigning these short-circuits to entries $12, $13, $14, and $15.
Exceptions
There is some extra logic beyond simply consulting the per-course-type palette tables. The following are selected by runtime player properties:
- The 2 BG palette rows used to draw the bonus game intro, based on the current player ID.
- The 1 OBJ color ID used to draw the normal suit bits of the player by ID (e.g. red for Mario, green for Luigi).
- The 1 OBJ palette row used to draw the player suit based on their current power-up. An additional bit of code steps over this palette and inserts the correct "base" color for the player ID in the necessary places.
Non-Course Palettes
Other areas of the game such as cutscenes (title, ending, etc.) instead utilize display lists to draw in palette tables as well as explicit logic to edit palette entries and apply cycles where necessary. For instance, the 3 on the titlescreen is cycled from an explicit table provided with the subroutine. Particular attention will be given to documenting non-course palettes in the future.
Maps
The map system is a special case of the course engine in which most graphics loading is handled explicitly by custom logic rather than the usual scenery generator. The map metatiles, however, are loaded in similarly to other metatile rendering and the screen is cleared via standard routines jumped to via the same jump tables as other course types. The map system itself consists of several components including:
- Stripe-based rendering of the map metatiles.
- Sprite-based occlusion of the border.
- Item/inventory manipulation.
- Warp and hand trap "grabbing".
- Tracking of completed stages and removed obstacles.
- RNG for things like airship escape destinations and population of bonus tiles.
- Graphical effects such is irising and the world 08 darkness.
- Game over processing.
Course Completion
The completion of stages in the current map is tracked in a buffer located at 0x7D00 in the MMC3 SRAM. This buffer is 128 bytes long and is split into two 64-byte chunks, one for the first player and one for the second. Each entry represents a single 16x16 metatile column of a screen, with enough space allocated for four screens-worth of map data.
This collection is indexed by first finding the page and column of interest, this is used to find the entry in the buffer, then any bits marked 1 in that byte represent a completed course tile in that column. These positions then correspond with tiles such as courses, fortresses, Toad houses, and so on.
To put this in other terms, the identity of a tile to mark cleared consists of the address of a bit in the buffer. The offset of the byte containing this bit is of the form
0abbccc
where:
- a = player a/b
- b = page 0/1/2/3
- c = column 0-15
so counting from 0 an offset of 0x6C would be the 12th column of the 2nd page of the map for Luigi. The bit would then indicate the row of this column, with bits 7-1 being rows 0-6, then bit 0 indicating row 8, not row 7.
As the buffer is only sized for three screens of a single map, the completion information is not persistent between maps. It is possible using warp whistles to loop back on completed worlds, namely world 5, 6, and 8, and cause a reset of the cleared courses.
This also means that clear data stored in the ROM is meaningless without context, as it could apply to any world. Positions must be applied based on the world that they're associated with.
One caveat to the row indexing is that the first seven rows are contiguous from the top of the map, whereas the last row is spaced one down from the last. In other words, the next-to-last row of the map cannot contain a complete-able tile, it will simply trigger completion of the tile below it.
While most course completions result in populating the M or L tag over the entry tile, some instead result in changing for instance a fortress to a pile of rubble or clearing a gate after said fortress clear. For the primary action, the fortress going to rubble, this is achieved with a series of tables indicating special pairs of pre-and-post-clear tiles. As for gates, bridges, and so on, a secondary routine contains clear targets for the completion of specific levels such as fortresses. The end result is clearing the fortress crumbles its tile *and* opens the gate.
Course Headers
Each course type includes a 9-byte header indicating a number of properties:
Offset | Size | Mask | Description |
---|---|---|---|
0x00 | word | aaaaaaaaaaaaaaaa | Scenery table address, this is the large background objects and obstacles. |
0x02 | word | aaaaaaaaaaaaaaaa | Actor table address, this is the sprite-based actors and other objects. |
0x04 | byte | aaa0bbbb |
|
0x05 | byte | abbccddd |
|
0x06 | byte | abbcdddd |
|
0x07 | byte | aaabbbbb |
|
0x08 | byte | aa00bbbb |
|
Internal Data for Super Mario Bros. 3
| |
---|---|