In 1984, Teenage Mutant Ninja Turtles hit comic book stands.
The franchise blossomed into a cartoon series alongside action figures. And naturally,
video games would follow – including Konami’s 1989 entry on the NES.
The game is fun to play but could have been better.
It runs at 30 frames per second instead of 60, has a lot of flicker, and also has
questionable hitboxes among other things. The area for focus of this video is:
What is wrong with the water stage? Sure, it is passable – maybe even
easy if you know where the bombs are and remember that you can switch turtles,
but swimming is more difficult than it should be, and collision detection seems awful.
This is a collision. This is not.
There are two types of collision detection used in the water level, and neither is perfect.
Let’s examine them both. The dissection of code is at hand!
First, an overview. The level is very short – eight bombs must be disarmed
within the specified time, and there are many obstacles to avoid along the way.
Some areas of the water have Current that influences your direction. You may have
noticed that there are also bubbles in the game. They would be a useful
indicator as to whether or not Current is present, but they weren’t implemented well.
Let’s start with the easiest item – collision detection with the obvious hazards.
There is logic in the game that takes each object’s location
and dimensions and creates a hitbox relative to screen position.
Collision logic uses the appropriate extremes to see if there is overlap and therefore collision.
For these electrical barriers, collision detection is pretty good. You can get right
up next to it and not take damage. Intersect with it when it is active, and you take damage.
Water would appear to be an insulator in this game.
With the size of the hitbox applied, the hazard’s (X,Y) location is a center point.
Not all obstacles or enemies follow this logic.
These guys at the beginning of the game have their coordinates here. Their box is still built
using the position and sizes but is intentionally calculated so that it is above their coordinates.
Regardless of this orientation, the sprite would seem to “match” the hitbox.
When it comes to this obstacle in the water, the relationship between position,
sizes, and hitbox calculation is not very good. As you can see, the boxes favor the area above the
objects themselves. It is a mismatch between location and the hitbox calculation method.
To verify the boxes are accurate, let’s freeze all objects that are straight across.
From the bottom my hitbox can intersect the sprites, and I don’t take damage.
Approach from the top, and I take damage when my hitbox connects with the areas above the sprites.
So it is perhaps safer to swim below the obstacle than it is above it.
Next up – let’s talk about bubbles and current. When you encounter current, it affects your
velocity in the water. Different areas have different currents.
The first area with current is this glowing area of kelp. It pulls you downward. If you
get too close, you are captured. The area reduces the ascent
speed of both you and the bubbles. However, notice that the bubble’s
upward velocity changes depending on where the turtle is – not the bubble itself.
They should probably follow the physics of their location and not that of the turtles’.
In addition, they should probably have an exaggerated effect from
the currents so water movement is obvious to the player immediately.
It would also be better to consider having more than one bubble at a time.
Let’s get into some of the logic. When the player enters certain areas of the water,
the game checks for any current that should affect the velocities of the player and bubble.
The value at $7E is an indicator of current being present and is also part of an offset
used to look-up the Current’s velocities. If $7E is zero, there is no current.
Let’s check bubble logic first. It is easier to follow.
The Bubble velocity routine calls the generic Apply X and Y Velocity Subroutine.
This applies velocity for turtles, bubbles, enemies, etc. to their current location.
$7E is then referenced to obtain an offset so we can look-up the Current’s
X and Y displacement velocities and apply them to the bubble.
Bubbles normally rise at 1.5 pixels per frame. The Current here reduces it to half a pixel per frame.
Personally, I feel like the bubbles should be pulled downward and not simply slowed.
If we use a Game Genie to increase Current for bubbles, you can see the
effect it has on Bubble direction. I think seeing this would cause
the player to frantically mash the A button – which is a desired effect.
Of course with the turtle’s location being the sole influencer of current,
you can alter bubble physics in a rather comical way by moving in and out of the Current.
For a Current’s effect on the turtle, things are a bit more
complex as controller input is considered. The logic that handles velocity based
on controller input and velocity based on Current looks like this.
That’s a lot of code. Pause and read it if you would like.
Now some of you may ask if there is anything wrong with this code, and my response is:
“a great… many… things.” Let’s break it down into
chunks. Not exactly a code walk, but you may want to grab a taco anyway.
First off – Here is the code that influences turtle Y Velocity,
and here is the code that influences turtle X Velocity. Pretty significant difference.
This first area checks to see if you are pressing Left or Right. If so,
it applies horizontal velocity. Maximum values are 2 pixels per frame in either direction.
This next area handles the natural sinking acceleration.
This is hardcoded to about .06 of a pixel per frame and is capped at 2 pixels.
Next up is a controller check for A button presses that make you swim upward.
Swimming code adds about .12 pixels per frame. Upward velocity is capped at 2 pixels.
The math here is also a little weird – we’ll come back to it.
Next up is the application of Current. $7E is used to see if a current is present. If
no current is present, we skip ahead to here. Otherwise, $7E is used to lookup four water
current values for velocity – integer and fractional values for both the X and Y axes.
Following this is one additional controller check for a down press on the d-pad. It adds 0.1875
pixels per frame. No cap logic exists here. Finally, all controller checks and velocity
application are complete. A subroutine is called that caps Y velocity at either 2.0
or -2.0 and then we continue on to Environmental collision detection.
OK. Here are a few oddities for this sequence... and the first one is huge.
Controller checks 1, 2, and 3 are in these locations.
Current is checked and performed here. If no Current is present, the logic skips down to here.
That means pressing down on the d-pad is ONLY processed when you are in a current.
Let me say that again – the code that lets you swim downward is broken.
The sprite sequence changes to a turtle swimming down,
but the physics remain the same. The only code that moves you downward outside a Current
is the natural sinking code. This and this are the same.
While we are at it, pressing up does nothing. It makes your turtle look like he is swimming up,
but it is only the mashing of the A button that moves you upward.
Both the sinking code and the swimming code have their own maximum checks for
Y velocity here and here. The cap is for plus 2 and minus 2 respectively.
Note that it doesn’t bother to do anything for the fractional values. This cap logic considers 2.0,
2.1, 2.17, etc. to all be valid maximum values. However, your turtle never exceeds 2 pixels per
frame in an upward or downward direction because… at the end of the sequence,
there is an entire subroutine that is executed for capping Y velocity.
Not only does it cap the integer values at +2 or -2, it also zeroes out the fractional Y velocity.
The logic is the only “proper” cap for Y velocity.
So that begs the question of “why does the code bother with a rather sloppy cap
implementation here and here if it is going to do a proper cap with a subroutine call here?”
Something else about this sequence – both sinking down and swimming up add to the same fraction.
If the moment of rollover occurs here, you get an extra plus one. If it occurs here,
you can an extra minus one. That’s… weird. On one hand, whatever, it “works.”
On the other hand, how about we make the logic consistent and add to both values to go down and
subtract from both values to go up? OK. Now for Current logic.
It has a pretty bad bug in it. As mentioned, here is all the
code that has to do with X Velocity. There is no code to limit X Velocity for the fraction.
The integer does have a limit of sorts because the first thing we do is set X Velocity to either -2,
+2, or 0 depending on if the player is pressing Left, Right, or Nothing.
Previous X Velocity integer doesn’t matter – we start from scratch each time through this code.
Current logic adds to the X fraction and X integer every frame when there is current no matter what.
This means two things – Rollover code lets your turtle now exceed an X velocity of 3 pixels
until it is pulled back to 2 (and some change) at the start of the next frame.
Secondly, the X velocity fraction continues to overflow with no cap at all.
So long as you are present in the Current, it will continue to overflow
over and over and over again. Once you exit an area with Current,
your X Velocity will still have the most recent fractional X velocity from the Current logic.
One of the worst places for this is in this area as you now have to proceed
up to the seaweed screen with an inherited Current pushing you to the right.
Not only that, but the velocity’s value is… random! It is whatever it was…
at the time you left the Current. This means your seaweed navigation has an added
“difficulty dice roll” when you enter this screen.
Now you can collide with the environment to reset all your velocities to zero,
however it is likely that you will pick up the current again in this area.
Here is the big trick – the current appears to kick-in based on where
you are on the screen relative to the environment;
however that is not quite correct. The camera and therefore scrolling also play a roll.
For example, notice how I can move in and out of the Current when I cross over into this
area. If I backtrack a bit and get this second electrical hazard off the screen,
I can now perform the same back and forth maneuver at the same area of the environment
and… there is no current on either side. So if you feel up to it, deactivate the bomb,
scroll the second electrical barrier off the screen, get out of the current, and then try to
swim up to the seaweed screen without scrolling to the right if you don’t want inherited X Velocity.
Goodness. Good luck with that. OR… We could make a code change.
Because that is what we do.
A Game Genie approach is a bad idea for my proposal. Hear me out. This change is big.
The initial X Velocity here will at least keep pulling the integer value back to 2,
but what about that fractional value that keeps overflowing?
That’s one bug to address. Next up – It would be nice if you
could swim down in the water at all times. So we need to alter the “no current present” skip code.
Then of course there is the weird math and extraneous cap logic for sinking and swimming.
So oh my word… here are the edits I propose. Eliminate the maximum value checks for
sinking and swimming. The Y cap subroutine will take care of it.
Update the math so that swimming up is 100% subtraction.
You'll have to increase the strength of swimming velocity in order to compensate.
Move the down controller press logic to above the Current Check so players can
actually swim downward whenever they want. Steer the “no Current present” branch to an
area just above “Water Velocity Complete” instead of directly to it, and add code
that zeroes out the fractional X velocity. This way no current means no fractional X velocity.
You aren’t going to make this change with a Game Genie. This is a ROM patch in waiting.
That said – ONE simple change would be to redirect the no Current present
branch statement to the down controller press handler rather than water velocity complete.
We can do that with a single game genie code. Now you can swim downward.
While there is still a lot of upward swimming to do, the ability to reach
full downward velocity so quickly thanks to a downward press on the d-pad
actually doing something helps the turtles move through several areas with ease.
But that feeling of ease will fade when we reach this screen…
To this point, most collision detection for hazards has been object-based.
These electrical hazards are objects. This thing is a set of multiple objects.
When it comes to the seaweed area, there is a lot of it,
and it is an environmental hazard rather than a massive set of objects.
The majority of the upper 256 bytes of RAM is used to hold a crude table of “block traits.”
Each nibble or “half byte” contains collision information for a 16x16 block of pixels.
By reading this table of data, we can use colored boxes as an overlay on the water level
to illustrate which areas are passable (no box), impassable (green box), or hazardous (red box).
You’ll immediately notice that not all seaweed blocks have a red box.
There appear to be some areas that are safe.
If I enter the Seaweed Screen of Doom(TM), the path of safety is much more obvious.
We are going to look at this screen quite a bit. Let me hide the green boxes as red is our focus.
It is obvious just how tile-based the NES is most of the time. While the graphics appear to
create this nice, rounded passageway through the seaweed, in reality is it a staircase.
You’ll also notice that the boxes seem slightly
higher than they should be relative to the graphics.
Sadly, there is a four pixel offset that raises the boxes four pixels higher than they should be.
This is how the environment is defined. What about the turtle’s hitbox?
Environmental collision is done using targeted pixels – similar in concept to
Mega Man 2 but slightly more complex. Following the Velocity code we just
covered, we make a call to the Env Collision Check Driver subroutine.
This subroutine uses our turtle’s position and velocities to anticipate environmental collision.
I say anticipate because we have our new velocities ready but have yet to apply
them to the turtle’s position in our code. The targets checked depend on the velocities.
The code is rather large and basically selects a target by adding look-up vectors to a turtle’s
position, adding your velocity and factoring in the camera’s position.
It then checks the target for collision against the type of environmental box in which it lands.
We could trace the code, but it is a LOT easier to understand if we just use graphics.
I wrote a script to pull target positions from
RAM during calls to environmental collision detection and used them
to draw a line from the turtle’s location (at the center of the sprite) to the target.
As you can see, there are many targets that are checked – as many as five at a time.
If collision occurs for the current frame, any remaining checks are skipped.
So even if I take damage to get to this rock, no damage occurs once I reach it because this
first target sees collision with an impassable object and skips the remaining checks.
To get a good idea of where the targets land based on player input and current velocity,
I have frozen the turtle’s coordinates in RAM. This lets me use the controller to assign
velocities and get an idea of target locations without actually moving.
Remember the targets factor in velocity for a place you haven’t been moved to yet,
however it shouldn’t be a difference of more than 2 or 3 pixels depending on the axis.
This is for sinking. The order of the checks are center, then right, then left.
The center and right targets seem a bit too low due to the turtle’s angle,
however know that the code doesn’t care which direction you are facing.
So the Y distance is always the same for all three.
Pressing down on the d-pad uses a sprite that perhaps seems more compatible with the targets,
however remember that pressing down does nothing most of the time.
Pressing up does nothing but changes your sprite… the bottom targets are still checked.
Rapidly tapping A lets you swim upward. These targets don’t look too bad.
Pressing right on the d-pad alone will add two front targets both high and low to the turtle’s
right. When combined with this angled turtle sprite, the lower front target looks WAY off
even if the turtle’s velocities are maxed out. It is a similar story for pressing left on the
d-pad but is flipped in the other direction. Tapping A while pressing right moves the
vertical targets to the top as usual. That lower front target looks very out of place.
Once again – similar story when you press Left and keep tapping A.
So that gives you an idea of the environmental collision targets with the seaweed.
Best to avoid pressing left or right whenever you can.
Collision code does not cover all combinations of sprite animations
and controller presses for targets. We are limited to “up or down” and “left or right.”
However, perhaps we could tighten up some of the vectors
just to provide more wiggle room when swimming. I raised the bottom checks two pixels – basically
negating any addition of a downward velocity. I also raised the right bottom and left bottom
checks by SIX pixels. This puts the lower of the two front targets in each direction
nearly straight out in front of a turtle swimming right or left.
Feel free to give these a try and see if it makes swimming through the seaweed a little bit easier.
I think a tighter environmental hitbox + the ability to swim downward help make seaweed
navigation manageable for more people than just those that are well-practiced.
I elected not to alter the spinning thing’s hitboxes here,
but the ability to swim downward now helps with that.
Hope this answers any questions you have had to a long-standing
frustration with the water level in TMNT. Please like and subscribe for more videos
like this one. I also have a Patreon available if you are interested, and thanks for watching.