How Aviator simulates a stalling plane and the recovery process
As a plane flies through the air, its wings provide lift. This lift is caused by the special shape of the wings, and specifically by air flowing over this shape from front to back, creating a lower pressure above the wing than below it. This pulls the wing up, thereby lifting the plane.
If this front-to-back airflow is disrupted, then the amount of lift will drop. The most obvious way to disrupt the lift is to slow the plane down so that air is no longer passing over the wing at a fast enough rate; a less obvious way is to increase the angle of the wing too much, typically by pulling back on the stick and pulling the nose up too far. In the first case, the lift disappears because the air isn't flowing over the wing fast enough; in the second case, the air no longer flows cleanly over the wing due to the steeper angle of attack, which is disruptive enough to reduce the amount of lift.
In both cases, the plane is said to be stalling. Let's look at how Aviator's flight model implements stalling, and what you can do about it.
The stalling checks
-------------------
The flight model checks for stalling in part 2 of the ApplyAerodynamics routine, which is itself called by part 2 of the ApplyFlightModel routine on every iteration around the main loop. The main check is as follows:
zVelocityP <= |yVelocityP| * 4
When this is true, the plane is stalling. To break this down, the plane stalls when the forward velocity in zVelocityP is less than four times the vertical velocity in yVelocityP, with both velocities being relative to the plane's frame of reference. Both of these velocities are shown on the dashboard, with zVelocityP on the airspeed indicator and yVelocityP on the vertical speed indicator, so in theory you can use these indicators to predict when the plane will stall.
In summary, we stall when we aren't going forward fast enough compared to the vertical velocity, specifically when we fall more than four times faster than we are travelling forwards. Note that zVelocityP has to be positive for us not to be stalling; a fast but negative airspeed is still a stall, as the air has to travel over the wing from front to back for normal flight to occur.
If the plane starts to stall, then this status isn't stored in a flag - it's actually stored in the force factor entry for yLiftDrag, in byte #4 of the forceFactor table. This value is set to 156 in normal flight, so yLiftDrag is scaled by 156 * 16 when calculating yLiftDragSc. When we are stalling, however, the force factor is changed to 39, so yLiftDrag is only scaled by 39 * 16. If we want to know if we are stalling, then we can just check the value in forceFactor+4, which will be 156 in normal flight and 39 if we are stalling. This also affects the flight model, so let's take a closer look in the next section.
See the deep dive on the flight model for more about the force factor scaling system.
The effect of stalling on flight
--------------------------------
As mentioned above, forceFactor+4 not only acts as a status flag for stalling, it also reduces the force factor for yLiftDrag to a quarter of its normal value when we are stalling (as 39 / 156 = 0.25). This means that as soon as we start stalling, the vertical lift due to the wings instantly drops to a quarter of its value in normal flight.
Not only does this, but the flight model also simulates the effect of one wing stalling before the other (again, this code is in part 2 of the ApplyAerodynamics routine). The stalling wing will be the one that loses lift first, meaning the plane will roll to that side once the stalling effect kicks in. This effect is only applied if the plane is not too close to the ground (specifically, the code checks that yPlaneLo >= 20, or that we are more than 5 feet above the ground), so stalling very close to the ground, which will typically happen as part of a controlled landing, won't spin the plane into what would almost certainly be an instant crash.
Assuming we aren't just above the ground, the flight model applies the following roll to the plane to simulate one of the wings stalling before the other:
zTurn = zTurn +/- (A 0) >> 5
where:
- The +/- sign is the sign of yTurn
- A = xTurnTop EOR #%00111111
The +/- part means that the wing to stall first will be the one that we are already yawing towards. In other words, if we are yawing to the left when we stall, then the left wing will have slightly slower airflow over the wing, because it's yawing backwards while the other wing is yawing forwards, so it will the first wing to stall. This translates into a change in the z-axis turn rate (i.e. the roll rate) in the same direction.
The amount of roll in (A 0) >> 5 is inversely proportional to xTurnTop, which is our current the pitch rate, so pitching hard when stalling, in either direction, will decrease the roll effect.
This roll effect is only applied at the very start of the stall, as a one-off, so it has the effect of tipping the affected wing into the start of the roll, but once applied, the tipping nudge isn't applied again.
Recovering from a stall
-----------------------
Once we are stalling, we will continue to stall until we address the cause of the stall - i.e. the bad airflow over the wing. The best way, then, to get out of a stall is to orientate the plane so that we get fast enough front-to-back airflow over the wings. Typically this involves pitching forward to let gravity help pull us out of the stall.
This is modelled by the following check in part 2 of the ApplyAerodynamics routine:
zVelocityPHi >= 11
This test is true if the high byte of the forward airspeed in zVelocityPHi is greater or equal to 11, so it is true when the plane's forward airspeed is greater or equal to (11 0), or 119 mph.
The code works like this. If we are already stalling and our forward airspeed is lower than 119 mph, then we are not going fast enough to pull out of the stall, so we keep stalling (and the game makes a beeping noise to warn us that things are not going well). However, once our speed rises to 119 mph or above, then we are potentially fast enough to pull out of the stall, so we apply the stalling test above. If we find that we are still stalling, then things continue as they were, but if the stalling condition is no longer true, then we pull out of the stall, the force factor for yLiftDrag gets set back to its normal value, the game stops yelling at us... and we can heave a big sigh of relief.