Super Mario Bros. introduced many of us to Warp Zones and their ability
to skip us ahead to later Worlds. A few years after the game’s release,
the 3rd issue of Nintendo Power revealed Minus World – a secret world that could
be reached by exploiting a glitch. A precise jump allowed Mario to clip
through the block, pipe, and wall at the end of World 1-2 and land in the Warp Zone in a
state incompatible with the game’s design – allowing him to travel to Minus World.
In the years that followed, we learned that the graphic used for the World number is simply a
blank tile graphic with an ID of 36. So - welcome to World 36.
But why does this happen? How is it possible? I’ll dive into the circumstances that lead to
the famous Minus World glitch and also reveal a new discovery that suggests
that the underground Warp Zones were intended to behave in a slightly different way.
There is an invisible enemy on this screen that isn’t working as designed.
Here – I’ll edit RAM and turn it into a different enemy that you can see.
That hidden enemy is or… was responsible for unlocking our route to the warp zone.
If I try to go to the warp zone now… I can’t. Hidden enemies? Broken scrolling?
Confused yet? I have some explaining to do…
Let’s start with some rather crude basics of how a Mario Level is built.
The Program ROM for Super Mario Bros. is only 32K – it fits inside the addressable
space for the NES without the need for bank switching. That isn’t a lot of room for
storage – especially if you were to build a level using block patterns stored as consecutive bytes
such as Mega Man 2 would do years later. So you need a way to compress level
information so that a very small amount of data can be used to render a level.
Rather than store an entire level layout, Super Mario Bros provides a block count for
the ceiling and the floor and will continue to draw that pattern until it is told otherwise.
It also adds various objects or enemies to the screen when and where appropriate
and does so by providing a position and an ID for each.
Here is the beginning of World 1-4. I’ll walk you through the
objects that help craft the first screen. Our starting block count is three for the
ceiling and five for the floor. There are two block configuration changes that occur on this
first screen. Here the count is changed to three ceiling and two floor. Two columns later, it is
changed back to three ceiling and five floor. Four objects are also added to the playfield.
Three Horizontal block objects of lengths 3, 4, and 5 are used to build the stairs.
A lava object of length 2 is placed at the bottom of the screen and covers the two floor blocks in
this section that I mentioned earlier. So – six objects comprise level render
instructions for this first screen – also called a “Page.”
This is SMB Utility – a level editor. We are in map editing mode right now, and we can see
our object locations in this top Object View window. They are represented as shaded boxes.
Four Pages are shown – 0, 1, 2, 3. Each Page has tile columns ranging from 0 to 15 across the top,
and row numbers spanning negative 1 to positive 15 along the left side.
Each object is stored in the ROM as a tile location with an object ID.
The Object List helps illustrate this. The first entry for Page 0 is an object at Column 0, Row 5,
and is a Horizontal Block object of length 3. Notice that the ceiling and floor block
configuration changes are positioned on Row 14 near the bottom. If I drag the lava object out
of the way, you’ll see that the floor behind it is indeed two blocks tall as mentioned earlier.
Some object positions are relevant for placing an object in the playfield. Other
object positions are used to indicate special cases and inform the game of something like… an
upcoming ceiling/floor configuration change. If we switch the editor to enemies mode,
we see enemy objects and their positions in the level inside the Object List window.
Some “enemies” are things like this platform that moves left and right.
One example of a unique enemy type is here in World 1-1. A “Room Change” enemy
is located here on Page 1, column 1, Row 14. This indicates the destination room for Mario
should he descend into any pipes or climb any vines after this enemy has been processed.
This room number is at the ready if Mario uses this fourth pipe here in the first level.
If we change the ID for this pipe to be a pipe that Mario can enter,
that “room change” enemy will still send him to the same place we expect. I could also change
the room for that “room change” enemy and send Mario to… World 1-4 when he enters the pipe.
While this level building simplification is not comprehensive, we can summarize Mario level
rendering as “continue to draw the level using this configuration unless I say otherwise.”
As Mario moves to the right, the game looks ahead to the upcoming columns for alterations to ceiling
and floor layout, additional objects, enemies, destination rooms, and more.
This is all relevant when it comes to understanding the bugs that lead to Minus World.
And on that note, one last thing worth mentioning is a scroll stop object.
When Mario reaches the center of the screen, the game is designed to scroll right.
That is its natural state. Again – “do this unless I say otherwise,” right?
To get it to stop, a scroll stop object is used. The entry to World 1-2 uses a scroll stop. Mario
moves past the center of the screen and enters a pipe. The screen does not scroll.
That stop object is here. Since the game will always read and render half a screen’s worth
of information ahead of the current screen on the TV, the stop is active immediately.
If we remove this object and start World 1-2… the screen scrolls this time!
With this crash course in Mario level layout and rendering behavior complete,
it’s time to return to the first warp zone. With the editor in map mode, we see a lot of
objects defined in rows 13-15 under the playfield as well as a few in the playfield itself.
Our focus is how the warp zone works, so let’s pare down
these objects in the Object List window. We are left with the three warp pipes and
two objects on row 13. Two scroll stop objects. One is simply a “scroll stop” here on Page 11,
column 2. The second is a Scroll stop (Warp Zone) object on Page 12, column 6.
The second scroll stop works. It stops the screen from scrolling after the Warp Zone
room has scrolled into view and also triggers logic to setup the zone. Let’s look at the
code executed when the object is encountered. The first task is to determine which Warp Zone
we have reached – either the 4-3-2 Zone, the single pipe World 5 zone, or the 8-7-6 Zone.
The result – a value of 4, 5, or 6 – is stored in RAM. We’ll call it “WarpZoneControl.”
The number is used as part of an offset calculation later in code. One place it is used
is in the very next subroutine – WriteGameText. The subroutine prints welcome to warp zone,
and uses the WarpZoneControl value to know which numbers to place over the pipes.
Finally, the piranha plants in the pipes are removed, and the scroll lock bit is toggled.
Welcome to Warp Zone. The WarpZoneControl value
in RAM is later referenced when Mario enters a pipe. It is used to determine where he should
go. A bad value at this location in RAM under certain conditions is what is responsible for
allowing Mario to travel to Minus World. We’ll get to the details of that in a bit.
What of the earlier scroll stop object? The invisible enemy that I turned into a
Koopa Troopa earlier resides here. It serves as a key to unlock scrolling. Once present, it
continues to check for scroll lock on every frame. Once it sees that scrolling has been locked,
it continues to check unlock criteria and will unlock scrolling when that criteria has been met.
The first scroll stop encountered should lock scrolling as soon as this column is loaded.
So that would mean that we would stop scrolling once the L-pipe and two
blocks of wall behind it are on the screen. Once the scroll stop has occurred, the hidden
enemy should unlock scrolling once its unlock criteria is met… but… does scrolling ever stop
here in the first place? You can… keep scooting the screen to the right as much as you want.
Why are this enemy and this scroll stop in the level’s design? What’s happening?
Let’s get back to the emulator. I wrote a script to examine
information while the game is running. At the top we have Mario’s X and Y locations. His
Page location for each followed by coordinates. Jumping shows the change in Y-Position. You may
assume the Y Page will always be 1 because we don’t scroll vertically in this game;
however watch the Y locations when I take the elevator to the top of the screen.
The Page rolls down to 0, and the coordinate rolls under and begins counting down from the
maximum value. We’ve crossed a page boundary! The status bar’s space here is considered on
a different “Page” in terms of screen location, and we still animate upon it.
Recall that when we looked at our level editor, the row numbers started at negative
1. And this now makes a bit more sense as we know that this row is on the “previous Page.”
Warp Zone Control is next. Zero is here when no Warp Zone is present.
Scroll Lock is 0 when scrolling is allowed and 1 when it is not. We are not locked at the moment.
Finally, the box below shows the current enemies and their positions. The Scroll Unlocker enemy is
here, and I have highlighted the entire column for that enemy. The enemy’s exact position on
this screen is irrelevant – it only needs to spawn prior to the scroll stop and be present
in the area. The highlight illustrates it is here and attempting to unlock scrolling.
Let’s move Mario forward to reach that scroll stop object – which.. again - is
several columns past the right side of the screen. Scrolling briefly stops. Immediately after this,
the Scroll Unlocker enemy unlocks scrolling and destroys itself. You can see that
the column I created has disappeared. The scroll lock barely lasted a frame.
You can also see that … Warp Zone Control incremented to 1. Why?
You just witnessed two bugs. The unlocker object unlocking scrolling when it probably
should not have done so, and… Warp Zone control changing to an invalid value.
Let’s check the unlocker enemy code and see what is up.
Mario’s Y Position and Y Page are ANDed together to determine whether or not scrolling should
be unlocked. If the result of the operation is zero, then the scroll stop is unlocked.
Why would you AND these values together as your criteria for unlocking a scroll stop?
Perhaps the intended design was to lock the scrolling here unless Mario reaches Y Page
0 – until he reaches the top of the screen. That would mean that so long as Mario is moving around
inside this area, scrolling is stopped. The warp zone room remains hidden. As soon as he reaches
an area above the playfield, the scroll stop is removed, and he can proceed to the warp zone.
The reason this AND statement doesn’t work is because anytime the lowest bit of Mario’s Y
position is zero – meaning his Y Position is an even number - the result of the AND
statement is zero… and the scrolling unlocks. So you... could keep the scroll lock in place
if you manage to jump from the elevator, and Mario’s vertical velocity continues to
keep his Y Position at an odd number. Have you ever jumped to the ground here
and had a momentary “hitch” in the scrolling? That’s why. Mario just
rolled a handful of odd-numbered Y Positions. This scroll stop thanks to an odd-numbered Y
position is short lived, however, as landing puts Mario at an even-numbered Y Position of 128.
So first up, let’s “fix” this weird AND statement.
If we change it to a Compare and the Mario Y Page RAM address to a value of zero,
the scroll unlocker will unlock the screen as soon as Mario’s Y position is zero as it will
write that zero to the scroll lock RAM value. The result is that Mario can jump around and
attempt to scroll the screen in this area, but the scroll stop stays in place. It does its job.
As soon as he reaches the top of the screen, however, the Scroll Unlocker enemy does its job.
This was likely the intended design for this area.
If you attempt the Minus World block clip, the screen still
won’t scroll until Mario gets to the top. But wait – with the scroll lock in place,
Mario passed the center area and is now on the far right side. That means he can walk right,
drop into the warp zone room without reaching the second scroll stop which
sets the warp zone control to the proper value, and…. Still go to minus world.
Bug… not quite fixed. Just before the Scroll Unlocker
is erased, it increments the Warp Zone Control value by 1. This is where that invalid value
originates. Again - this is what is responsible for allowing Mario to travel to minus world.
Let’s take a look at the logic used for Warp Zone pipe destinations.
If the Warp Zone Control value is something other than zero, the logic concludes the
pipe Mario used is part of a Warp zone. The value kicks off an offset location in the
ROM to know which warp zone “pipe trio” to use. Mario’s X Position is then examined to know
which of the three pipes he entered. The offset is incremented where appropriate,
and the pipe destination is finally referenced. Under normal circumstances, the hex values of 24
(36 in decimal) that flank the World 5 pipe aren’t relevant for pipe destinations as there
is only one pipe for the World 5 Warp. The values are only referenced so that the draw code puts a
blank space above non-existent pipes. In fact if I change those values in
the ROM to something else, you can see them in the World 5 Warp Zone!
When that invalid value of 1 is placed in Warp Zone Control and Mario enters a pipe,
this code knows Mario is in a Warp Zone, uses the value as an offset, selects the second pipe trio,
sees that he went down either a left side pipe or right side pipe, and…
sends him to the World it sees. This is also why when you enter the center
pipe after clipping through the block, pipe, and wall in World 1-2 that you are sent to World 5.
SO! If we wanted to “fix minus world” – which isn’t fun,
but let’s do it anyway - we need to eliminate this erroneous increment statement here.
The easiest way to do that is to change the INC to LDA – To basically just
let it load Warp Zone Control into the Accumulator. This statement is harmless as
the first thing performed in EraseEnemyObject is to place a zero in the Accumulator.
Regardless of if the scroll lock fix is in place, entering the Warp Zone room without reaching
the last scroll stop will bypass Warp Zone destination logic for all three pipes and simply…
Place Mario in the room destination that was assigned for the L-pipe.
The minus world bug has been fixed, and we pulled it off using only three Game Genie codes.
Most gamers aren’t likely to want to fix minus world, but those of you interested
in game logic may find this intriguing. I’d like to thank Mike Schiraldi for calling the
AND statement bug to my attention, providing an explanation and a fix! This saved so much research
time on this topic and moved up my interest in doing a Minus World video to… right now.
If you’d like to see more videos like this one, please like, subscribe, and leave a comment.
I also have a Patreon available for those of you interested, and I look forward to producing more
videos like this in the future. Thanks for watching.