diff --git a/CHANGELOG.md b/CHANGELOG.md
index 87315f61ba..0ceaea6675 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@
- #1389 - Prevent unfair penalty at KDCA on IRONS7 arrivals by deactivating R6601B/C
- #1218 - Ensure proper removal of aircraft who collide with terrain/traffic
- #1513 - Add missing fleets to AFR/LDM airlines that were crashing EDDM/EIDW
+- #1398 - Fix go-arounds from aircraft with low descent rates
### Enhancements & Refactors
- #1486 - Update AFL airline
diff --git a/src/assets/scripts/client/aircraft/AircraftModel.js b/src/assets/scripts/client/aircraft/AircraftModel.js
index 31bf3579f9..a0cb2be1a9 100644
--- a/src/assets/scripts/client/aircraft/AircraftModel.js
+++ b/src/assets/scripts/client/aircraft/AircraftModel.js
@@ -472,7 +472,6 @@ export default class AircraftModel {
// not given a heading directly, but has a fix or is following an ILS path
this.target = {
altitude: 0,
- expedite: false,
heading: null,
turn: null,
speed: 0
@@ -1040,10 +1039,14 @@ export default class AircraftModel {
* @return {boolean}
*/
isOnFinal() {
+ if (!this.isEstablishedOnCourse()) {
+ return false;
+ }
+
const approachDistanceNm = this.positionModel.distanceToPosition(this.mcp.nav1Datum);
const maxDistanceConsideredOnFinalNm = AIRPORT_CONSTANTS.FINAL_APPROACH_FIX_DISTANCE_NM;
- return this.isEstablishedOnCourse() && approachDistanceNm <= maxDistanceConsideredOnFinalNm;
+ return approachDistanceNm <= maxDistanceConsideredOnFinalNm;
}
/**
@@ -1402,7 +1405,6 @@ export default class AircraftModel {
* @method updateTarget
*/
updateTarget() {
- this.target.expedite = _defaultTo(this.fms.currentWaypoint.expedite, false);
this.target.altitude = _defaultTo(this._calculateTargetedAltitude(), this.target.altitude);
this._updateTargetedDirectionality();
@@ -1422,7 +1424,6 @@ export default class AircraftModel {
case FLIGHT_PHASE.APRON:
// TODO: Is this needed?
// this.target.altitude = this.altitude;
- // this.target.expedite = false;
// this.targetHeading = this.heading;
// this.target.speed = 0;
@@ -1431,7 +1432,6 @@ export default class AircraftModel {
case FLIGHT_PHASE.TAXI:
// TODO: Is this needed?
// this.target.altitude = this.altitude;
- // this.target.expedite = false;
// this.targetHeading = this.heading;
// this.target.speed = 0;
@@ -1440,7 +1440,6 @@ export default class AircraftModel {
case FLIGHT_PHASE.WAITING:
// TODO: Is this needed?
// this.target.altitude = this.altitude;
- // this.target.expedite = false;
// this.targetHeading = this.heading;
// this.target.speed = 0;
@@ -1453,7 +1452,6 @@ export default class AircraftModel {
this.target.altitude = this.model.ceiling;
}
- this.target.expedite = false;
this.targetHeading = this.heading;
this.target.speed = this.model.speed.min;
@@ -2227,6 +2225,8 @@ export default class AircraftModel {
const runwayModel = this.fms.arrivalRunwayModel;
const offset = getOffset(this, runwayModel.relativePosition, runwayModel.angle);
const distanceOnFinal_nm = nm(offset[1]);
+ const stableApproachTimeHours = PERFORMANCE.STABLE_APPROACH_TIME_SECONDS * TIME.ONE_SECOND_IN_HOURS;
+ const stableApproachDistance = this.model.speed.landing * stableApproachTimeHours;
if (distanceOnFinal_nm <= 0 && this.isOnGround()) {
return 0;
@@ -2237,9 +2237,9 @@ export default class AircraftModel {
}
const nextSpeed = extrapolate_range_clamp(
- AIRPORT_CONSTANTS.LANDING_FINAL_APPROACH_SPEED_DISTANCE_NM,
+ stableApproachDistance,
distanceOnFinal_nm,
- AIRPORT_CONSTANTS.LANDING_ASSIGNED_SPEED_DISTANCE_NM,
+ AIRPORT_CONSTANTS.FINAL_APPROACH_FIX_DISTANCE_NM,
this.model.speed.landing,
startSpeed
);
@@ -2419,12 +2419,6 @@ export default class AircraftModel {
updateAltitudePhysics() {
this.trend = 0;
- // TODO: Is this needed?
- // // TODO: abstract to class method
- // if (this.speed <= this.model.speed.min && this.mcp.speedMode === MCP_MODE.SPEED.N1) {
- // return;
- // }
-
if (this.target.altitude < this.altitude) {
this.decreaseAircraftAltitude();
} else if (this.target.altitude > this.altitude) {
@@ -2440,10 +2434,9 @@ export default class AircraftModel {
*/
decreaseAircraftAltitude() {
const altitude_diff = this.altitude - this.target.altitude;
- // TODO: this should be an available property on the `AircraftTypeDefinitionModel`
let descentRate = this.model.rate.descent * PERFORMANCE.TYPICAL_DESCENT_FACTOR;
- if (this.target.expedite) {
+ if (this.mcp.shouldExpediteAltitudeChange || this.isEstablishedOnCourse()) {
descentRate = this.model.rate.descent;
}
@@ -2452,6 +2445,7 @@ export default class AircraftModel {
if (abs(altitude_diff) < feetDescended) {
this.altitude = this.target.altitude;
+ this.mcp.shouldExpediteAltitudeChange = false;
} else {
this.altitude -= feetDescended;
}
@@ -2469,7 +2463,8 @@ export default class AircraftModel {
const altitude_diff = this.altitude - this.target.altitude;
let climbRate = this.getClimbRate() * PERFORMANCE.TYPICAL_CLIMB_FACTOR;
- if (this.target.expedite) {
+ // TODO: Ensure expediting is STOPPED when the altitude is reached
+ if (this.mcp.shouldExpediteAltitudeChange || this.isTakeoff()) {
climbRate = this.model.rate.climb;
}
@@ -2478,6 +2473,7 @@ export default class AircraftModel {
if (abs(altitude_diff) < abs(feetClimbed)) {
this.altitude = this.target.altitude;
+ this.mcp.shouldExpediteAltitudeChange = false;
} else {
this.altitude += feetClimbed;
}
@@ -2486,7 +2482,8 @@ export default class AircraftModel {
}
/**
- * This updates the speed for the instance of the aircraft by checking the difference between current speed and requested speed
+ * This updates the speed for the instance of the aircraft by checking the
+ * difference between current speed and requested speed
*
* @for AircraftModel
* @method updateWarning
diff --git a/src/assets/scripts/client/aircraft/Pilot/Pilot.js b/src/assets/scripts/client/aircraft/Pilot/Pilot.js
index ebc9e179f4..4c7edde1e0 100644
--- a/src/assets/scripts/client/aircraft/Pilot/Pilot.js
+++ b/src/assets/scripts/client/aircraft/Pilot/Pilot.js
@@ -145,6 +145,7 @@ export default class Pilot {
this.cancelApproachClearance(aircraftModel);
this._mcp.setAltitudeFieldValue(clampedAltitude);
this._mcp.setAltitudeHold();
+ this._mcp.shouldExpediteAltitudeChange = false;
// Build readback
const readbackAltitude = _floor(clampedAltitude, -2);
diff --git a/src/assets/scripts/client/constants/aircraftConstants.js b/src/assets/scripts/client/constants/aircraftConstants.js
index d7f7c66308..899d7083f2 100644
--- a/src/assets/scripts/client/constants/aircraftConstants.js
+++ b/src/assets/scripts/client/constants/aircraftConstants.js
@@ -133,24 +133,6 @@ export const PERFORMANCE = {
*/
DECELERATION_FACTOR_DUE_TO_GROUND_BRAKING: 3.5,
- /*
- * Distance from landing threshold at which to establish on final approach speed
- *
- * @property LANDING_FINAL_APPROACH_SPEED_DISTANCE_NM
- * @type {number}
- * @final
- */
- LANDING_FINAL_APPROACH_SPEED_DISTANCE_NM: 1,
-
- /**
- * Distance from landing threshold outside of which you must maintain assigned speed
- *
- * @property LANDING_ASSIGNED_SPEED_DISTANCE_NM
- * @type {number}
- * @final
- */
- LANDING_ASSIGNED_SPEED_DISTANCE_NM: 5,
-
/**
* Maximum vertical distance between the aircraft and the glidepath to
* consider the aircraft to be "established on the glidepath"
@@ -223,6 +205,18 @@ export const PERFORMANCE = {
*/
INSTRUMENT_APPROACH_MINIMUM_DESCENT_ALTITUDE: 200,
+ /**
+ * Length of time individual aircraft will require themselves to be established at Vref
+ * (their landing speed) before landing. If they cannot reach that speed by that time, they
+ * will not consider themselves on a "stable approach", and will likely go around.
+ *
+ * @memberof PERFORMANCE
+ * @property STABLE_APPROACH_TIME_SECONDS
+ * @type {number}
+ * @final
+ */
+ STABLE_APPROACH_TIME_SECONDS: 60,
+
/**
* Altitude above the runway at which aircraft begin their on-course turn, in feet
*
diff --git a/src/assets/scripts/client/constants/airportConstants.js b/src/assets/scripts/client/constants/airportConstants.js
index 383f679b24..40f62faf5d 100644
--- a/src/assets/scripts/client/constants/airportConstants.js
+++ b/src/assets/scripts/client/constants/airportConstants.js
@@ -36,7 +36,7 @@ export const AIRPORT_CONSTANTS = {
* @type {number}
* @final
*/
- FINAL_APPROACH_FIX_DISTANCE_NM: 4,
+ FINAL_APPROACH_FIX_DISTANCE_NM: 5,
/**
* Maximum allowable indicated airspeed for aircraft below 10,000 feet MSL
diff --git a/test/aircraft/Pilot/Pilot.spec.js b/test/aircraft/Pilot/Pilot.spec.js
index 517c764c3e..7cc732b906 100644
--- a/test/aircraft/Pilot/Pilot.spec.js
+++ b/test/aircraft/Pilot/Pilot.spec.js
@@ -562,10 +562,10 @@ ava('.conductInstrumentApproach() returns failure message when no runway is prov
ava('.conductInstrumentApproach() returns failure message when assigned altitude is lower than minimum glideslope intercept altitude', (t) => {
const expectedResult = [false, {
- log: 'unable ILS 19L, our assigned altitude is below the minimum glideslope '
- + 'intercept altitude, request climb to 3400',
- say: 'unable ILS one niner left, our assigned altitude is below the minimum '
- + 'glideslope intercept altitude, request climb to three thousand four hundred'
+ log: 'unable ILS 19L, our assigned altitude is below the minimum glideslope ' +
+ 'intercept altitude, request climb to 3700',
+ say: 'unable ILS one niner left, our assigned altitude is below the minimum ' +
+ 'glideslope intercept altitude, request climb to three thousand seven hundred'
}];
const aircraftModel = new AircraftModel(ARRIVAL_AIRCRAFT_INIT_PROPS_MOCK, createNavigationLibraryFixture());
diff --git a/test/airport/runway/RunwayModel.spec.js b/test/airport/runway/RunwayModel.spec.js
index 63edfe188a..95b1292f3f 100644
--- a/test/airport/runway/RunwayModel.spec.js
+++ b/test/airport/runway/RunwayModel.spec.js
@@ -83,7 +83,7 @@ ava('.getGlideslopeAltitude() returns glideslope altitude at the specified dista
ava('.getGlideslopeAltitudeAtFinalApproachFix() returns glideslope altitude at the final approach fix', (t) => {
const model = new RunwayModel(runway07L25R, 0, airportPositionFixtureKLAS);
- const expectedResult = 3452.7428770628912;
+ const expectedResult = 3771.178596328614;
const result = model.getGlideslopeAltitudeAtFinalApproachFix();
t.true(result === expectedResult);
@@ -91,7 +91,7 @@ ava('.getGlideslopeAltitudeAtFinalApproachFix() returns glideslope altitude at t
ava('.getMinimumGlideslopeInterceptAltitude() returns glideslope altitude at the final approach fix', (t) => {
const model = new RunwayModel(runway07L25R, 0, airportPositionFixtureKLAS);
- const expectedResult = 3500;
+ const expectedResult = 3800;
const result = model.getMinimumGlideslopeInterceptAltitude();
t.true(result === expectedResult);