The fireball algorithm in the code begins at 0x30ED if anyone is interested in reading it. I read through it a while back and understood most of it. I remember at a high level all the important parts of how the algorithm works, but I don't remember very well how stuff is organized in the code. I know that the fireball algorithm was very confusing at first because there was a ton of jumping around across different blocks of code. Some of the comments in that routine are also misleading and may add to the confusion. I was going to go through that routine adding more comments and replace some of the existing comments to make it easier to read, but I haven't gotten around to it yet.
Every frame, each fireball is processed, where it decides if it should be updating it's location and where it should be updated to and whether or not to change its animation. First, an RNG check is made based on the internal difficulty. So, for example, on Level 1 (internal difficulty 1) a fireball may only have a 4/8 chance to continue to the next block of code but on Level 5 it has a 7/8 chance to continue to the next block of code (if it does NOT continue to the next block of code, the fireball does NOT move or update its animation during this frame).
This is almost right, except for one important detail: this decision is NOT based on RNG, instead it just uses the frame counter, which counts down from FF to 00 over and over again. So, for example, L1 fireballs will always alternate: update, don't update, update, don't update, update, don't update, etc. with no randomness.
Next, there is another RNG check related to whether or not it's time to change animations which results in a number between 0 and 2 -- but the wrong block of memory is used (a bug in the code), and every time the value is 0 a totally seperate block of code that is also run during this frame is looking at that same spot in memory where a value of 0 has some other meaning (which I think results in the fireball NOT updating when it actually should be updating).
Hmm, I'm not sure I've heard of this. Are you talking about the bug that causes you to always be awarded 300 points from a hammer smash on a certain animation frame of the fireball? I didn't think that bug had anything to do with fireball speed. What I do remember is that when a fireball is moving left, and it falls into the "update" case of the first check described above, then there is a 25% chance (based on RNG) that the fireball will not move on that frame regardless, but this only happens when it's moving left and not when it's moving right.
Overall, this means that, as far as I know, fireball speed to the right is 100% deterministic and fireball speed to the left is random but averages 75% of the speed to the right. A quick check in mame using frame advance confirms that fireball speed right is constant (i.e., on L1 you will always see a fireball move right one pixel every two frames).
Now, about the "decision points" for reversing direction, the way it works is each fireball has an internal "direction reverse counter". When this counter reaches 0, and only when it reaches 0, will the fireball make a decision about whether or not to reverse direction (and when a fireball does make such a decision the counter gets reset back to some predefined value and required to count down again before the next decision point). The first important property about this counter is that it does NOT get updated when a fireball fails the first check (the deterministic one described above based on the frame counter). This means that internal difficulty does not affect the distance a fireball travels between decision points, because failing the first check causes neither the direction reverse counter or the fireball's position to be updated. Internal difficulty does, however, affect the time between decision points since it will take longer on lower internal difficulties for the direction reverse counter to reach 0.
Next, when a left-moving fireball fails the second check (the RNG check described above with 25% probability) the direction reverse counter does still get updated. This means that on a given internal difficulty the time between decision points will be the same (and purely deterministic) regardless of whether the fireball is moving left or right, but the distance the fireball travels will be smaller if it is moving left (and the distance while moving left will also be random).
So in summary, the distance between decision points of a right-moving fireball is the same across all internal difficulties and is totally deterministic. For a left-moving fireball, the average distance will be the same across all internal difficulties, but will vary based on how fast the fireball was moving.
When a fireball is forced to reverse direction due to hitting the edge of a girder, the direction reverse counter does not change. So this makes it possible, as pointed out by Mitch, for a fireball to go back and forth more quickly when near the edge of a girder.
Finally, when a fireball is on a ladder the direction reverse counter is frozen until the fireball reaches the other end of the ladder. So if a fireball reversed direction then immediately climbed down a ladder it might be possible to sneak past it and up the ladder because when it reaches the bottom it will go left and won't be able to reverse back to the right for a while.
As far as jumping fireballs, on L1 it is often possible to jump them immediately after they reverse direction and before the next decision point, but on L5 they will usually be moving too fast and you won't be close enough to them to safely jump them before the next decision point, however, it can still be safe to jump them after that decision point but before the following one. This requires either very accurately knowing the distance between decision points if the fireball is moving right, or if it's moving left, then distance is an unreliable measure and you have to have a good sense of the time between decision points.