Summary
Label_4T_AGFSR.decode builds latitude and longitude as sign * degrees + minutes/60. Operator precedence makes the hemisphere sign multiply the degrees only; the minutes are then added unsigned. For northern/eastern hemispheres the sign is +1 and the bug is invisible, but for any S latitude or W longitude the result lands 2 * minutes/60 degrees east/north of the correct point — up to ~2 nm for tiny minute values and as much as ~120 nm for minutes near 60.
This is a distinct bug from #460 (Label_16_TOD / Label_80 use a different wrong formula — division by 100 instead of decimal-minutes math). Same plugin file as #452 in concept (formatting vs parsing), but here the parsing itself is broken.
The existing tests assert the buggy output, so they will fail and need updating once the fix lands.
Affected code
lib/plugins/Label_4T_AGFSR.ts:52-59
const lat = data[6].substring(0, 7); // e.g. '4435.1N'
const lon = data[6].substring(7, 15); // e.g. '07143.4W'
ResultFormatter.position(decodeResult, {
latitude:
CoordinateUtils.getDirection(lat[6]) * Number(lat.substring(0, 2)) +
Number(lat.substring(2, 6)) / 60,
longitude:
CoordinateUtils.getDirection(lon[7]) * Number(lon.substring(0, 3)) +
Number(lon.substring(3, 7)) / 60,
});
Compare the sibling helper CoordinateUtils.decodeStringCoordinatesDecimalMinutes at lib/utils/coordinate_utils.ts:68-73, which correctly applies the sign to the whole sum:
return {
latitude: (latDeg + latMin / 60) * CoordinateUtils.getDirection(firstChar),
longitude: (lonDeg + lonMin / 60) * CoordinateUtils.getDirection(middleChar),
};
Reproduction
The existing test 'decodes msg 1' (lib/plugins/Label_4T_AGFSR.test.ts:23-55) feeds in 4435.1N07143.4W:
message.text =
'AGFSR AC0620/07/08/YYZYHZ/0340Z/453/4435.1N07143.4W/350/ /0063/0035/ /281065/----/ /512/0240/0253/----/----';
// Plugin returns:
// latitude: 44.585 (correct — N hemisphere hides the bug)
// longitude: -70.277 (the test asserts '70.277 W')
//
// Correct value for 071°43.4'W = -(71 + 43.4/60) = -71.7233
// The plugin gets -1*71 + 43.4/60 = -70.277, i.e. ~1.5° east of where the aircraft actually was.
The same flight's FlightAware track (AC620 YYZ→YHZ on 2024-11-08) goes through 71–72° W around that latitude, which matches the corrected value.
A southern-hemisphere reproduction:
message.text = 'AGFSR XX1234/05/06/SSAGRU/0354Z/833/3447.5S05832.4W/350/ / / / / / / / / / / / /';
// Plugin returns: lat -33.208, lon -57.460
// Correct values: lat -34.792 (-(34 + 47.5/60)), lon -58.540 (-(58 + 32.4/60))
Why it matters
Position reports drive every downstream consumer — map overlays, ETA/altitude correlation, separation alerts, archival storage. Putting aircraft tens of nm off the correct position is the most visible kind of bug a decoder can ship, and the impact is asymmetric: N/E flights look fine while S/W flights are silently misplaced.
Suggested fix
Apply the sign to the whole sum, or route through the existing helper:
latitude:
CoordinateUtils.getDirection(lat[6]) *
(Number(lat.substring(0, 2)) + Number(lat.substring(2, 6)) / 60),
longitude:
CoordinateUtils.getDirection(lon[7]) *
(Number(lon.substring(0, 3)) + Number(lon.substring(3, 7)) / 60),
After the fix, update the two existing test assertions in Label_4T_AGFSR.test.ts:49 and :82 — both bake in the wrong longitude. Adding a southern-hemisphere regression test (latitude with S, longitude with large minutes) would prevent silent re-regressions.
Test coverage
Both existing tests use N latitudes, which masks the latitude side of the bug entirely. The longitude assertions encode the wrong value (70.277 W vs. correct 71.723 W; 71.457 W vs. correct 72.543 W). A small S/W test case would lock the fix in.
Summary
Label_4T_AGFSR.decodebuilds latitude and longitude assign * degrees + minutes/60. Operator precedence makes the hemisphere sign multiply the degrees only; the minutes are then added unsigned. For northern/eastern hemispheres the sign is+1and the bug is invisible, but for any S latitude or W longitude the result lands2 * minutes/60degrees east/north of the correct point — up to ~2 nm for tiny minute values and as much as ~120 nm for minutes near 60.This is a distinct bug from #460 (Label_16_TOD / Label_80 use a different wrong formula — division by 100 instead of decimal-minutes math). Same plugin file as #452 in concept (formatting vs parsing), but here the parsing itself is broken.
The existing tests assert the buggy output, so they will fail and need updating once the fix lands.
Affected code
lib/plugins/Label_4T_AGFSR.ts:52-59Compare the sibling helper
CoordinateUtils.decodeStringCoordinatesDecimalMinutesatlib/utils/coordinate_utils.ts:68-73, which correctly applies the sign to the whole sum:Reproduction
The existing test
'decodes msg 1'(lib/plugins/Label_4T_AGFSR.test.ts:23-55) feeds in4435.1N07143.4W:The same flight's FlightAware track (AC620 YYZ→YHZ on 2024-11-08) goes through 71–72° W around that latitude, which matches the corrected value.
A southern-hemisphere reproduction:
Why it matters
Position reports drive every downstream consumer — map overlays, ETA/altitude correlation, separation alerts, archival storage. Putting aircraft tens of nm off the correct position is the most visible kind of bug a decoder can ship, and the impact is asymmetric: N/E flights look fine while S/W flights are silently misplaced.
Suggested fix
Apply the sign to the whole sum, or route through the existing helper:
After the fix, update the two existing test assertions in
Label_4T_AGFSR.test.ts:49and:82— both bake in the wrong longitude. Adding a southern-hemisphere regression test (latitude withS, longitude with large minutes) would prevent silent re-regressions.Test coverage
Both existing tests use N latitudes, which masks the latitude side of the bug entirely. The longitude assertions encode the wrong value (
70.277 Wvs. correct71.723 W;71.457 Wvs. correct72.543 W). A small S/W test case would lock the fix in.