STOP THE PRESSES!
I guess, in this case, they're key presses. But that's besides the point. There's a hot new glitch discovered this morning, the 15th of February 2020. And we're the first on the scene.
What's the scoop? It's easy. Get a boomerang into slot 0 or 1, then get an ice rod shot into slot 3 or 4, respectively. The boomerang stops moving on its own, instead following a weird trajectory as Link moves around.
Velocity is pretty easy to understand. Add this number (speed) to that number (coord). Hooray! We moved! Calculating velocity is not always as easy to understand. Sometimes, you need to use trigonometry. In the case of A Link to the Past, you need to use something that poorly mimics trigonometry. For the boomerang, or indeed, ancillae in general, the routine of interest is
Ancilla_ProjectSpeedTowardsPlayer. It will be covered more in-depth later; all you need to know right now is that before calling this routine, some value is loaded into the accumulator. This value is the magnitude of the base speed to be manipulated into a 2 dimensional velocity.
The boomerang turns around. Otherwise it'd just be a stick. The throw speed and base speed are calculated on initialization. A magnitude of
$30 is used for cardinal throws, and a value of
$28 is used for diagonal throws, for the blue and red boomerangs, respectively. If a velocity is for moving up or left, then its value is made negative. Diagonal velocites are put on both axes; obviously, cardinal velocities only go on the relevant axis. Regardless of direction, a positive number—the absolute value of the speeds used—is used for the return speed, which is stored in
$03C5,X. This value is later used when calling
Ancilla_ProjectSpeedTowardsPlayer to create a velocity on each axis every frame.
Weird… why should diagonal throws return slower? Whatever.
The boomerang will glide along happily if it was just thrown. It's not until it's reached its maximum distance that it suddenly stops. Something must be setting the return speed to 0.
As far as the boomerang is concerned, nothing abnormal is actually happening. It's chugging along and executing all of its code perfect. What a good boy. That doesn't change when this glitch takes place. Clearly, the ice rod is the problem. More specifically, the ancilla it creates.
We've already got our finger pointed at the ice rod, so let's see what's up. The answer is a bit subtle, but it's a single operation in the ice shot initialization code:
STZ $03C2,X. In our example, the ice rod is slot 4, so that'd be
$03C6. Hmmmmm… If the boomerang is slot 1, that means… Aha! The return speed of the boomerang is the first slot of
$03C5,X, which is also
$03C6. The ice rod is using a shorter space as if it were longer, which causes it to bleed over into memory already allotted for something else. Case closed! And, later on, it uses this value for absolutely nothing.
That's right! That damaging operation had no function whatsoever. If it were removed, ice rod shots would act no differently, but the boomerang wouldn't be subject to this glitch. Or would it…?
The ice rod isn't the only thing that messes with this array. In fact, there's another array that's only got space for 2 elements right before it. And that array is responsible for somaria lamp bouncing. Somaria blocks also use
$03C5,X, but they use it to control bouncing, which you probably figured out from the preceding sentence.
So we have that much figured out. Most people would be satisfied with that. Not me. Sure, we know why it stops, but what about how it moves? Why does it sort of move with Link? Why does it seem to move away from him?
To answer these questions, we need to take a look at
Yregisters in the stack.
Xby 1. If it's not
0, go back to the previous step.
Yregisters from the stack and return.
So what does this mess actually do? Well, it's an over-the-top algorithm to calculate
b is the base speed given as an input and
θ is the angle of a right triangle whose hypotenuse is a line that connects Link and the entity, with the side adjacent to
θ being longer or as long as the side opposite to
Or to put it less formally:
I don't think I get it either. I mean, I get it. But why? That's a really weird way to go about doing this. It doesn't even partition the velocity cleanly. One of the axes (the further one) will always have the input value. The other will have something else. This results in values anywhere from
b√2 as the final gross speed, which shouldn't be desirable. The more reasonable expectation is that the gross speed is the magnitude of the sum of each axis' vector.
The worst part, though, is that all this is doing is multiplication and division. While there may not be any operations in the CPU's instruction set for these functions, there are hardware registers specifically for that purpose. Very small base speeds will outperform those registers, but values are never that small. By using this looped algorithm, the runtime becomes longer as the base speed increases. Using the built-in registers would pretty much always have the same run time.
If you want to see an example problem worked through (and you don't), check out this page.
The problem we're interested in, specifically, is how the loop is terminated. It's when the loop counter is exactly 0. This is fine in most cases. The routine is basically copy/paste from the sprite version, which does find use negative values to move sprites away from the player. But in the case of 0, it's acting like a big negative value, but only for the loop.
Oh wait, here's a funny thing: the sprite version of this routine does not accept values of 0. Someone must have realized it could be a problem there, so the routine exits immediately.
The more displaced axis will always get 0. The other axis will get a speed that locks it onto 1 of 8 lines, which are 45° (π/4 rad) apart, starting from 0. Once the boomerang has reached a point on this line, it will stay there until disturbed. If it has to move, it will favor one of the directions it was thrown in, preferring Y over X when both are equally displaced.
In some sense, the boomerang no longer cares about returning to Link. It will return if you can reach it, which is possible if you move towards it along the same line it's obsessed with. The boomerang normally seems to be moving pretty slow with this, but that's only because it doesn't need to move much to stay on the line. If you move closer to it in one direction but not the other during a diagonal throw, it will achieve high instantaneous velocities as it snaps towards a cardinal line.
Data bleed causes the ice rod shot to set the boomerang's return speed to 0 when it's in a slot 3 positions lower. Funky speed math causes the boomerang to snap to and stay on specific lines, which all intersect at Link. This funky math also involves an overflow from the value of 0, resulting in negative numbers that tend to push the boomerang away from Link.