fix(vessels): show a 359.7° course as 0°, not 360°

Vessel cards and the AIS HUD rounded the direction without wrapping, so
a vessel reporting no heading and a course over ground of 359.5° or more
read "360°" on its cards and "HDG: 360DEG" in the HUD. The rounded
direction now wraps to 0°.
This commit is contained in:
Aditya committed 2026-10-27 15:54:28 +05:30
1 parent 15de9d738c
commit d58199a214
3 files changed
+30 -4

No files matched your search

+3 -1
View File
@@ -503,7 +503,9 @@ those reports arrive as 102.3 knots and 360 degrees. Both are stored as unknown,
and vessel cards, the HUD and analyst queries show a missing measurement rather
than a reading. Heading keeps its existing separate check for its own sentinel.
Genuine readings are unchanged, including a stopped vessel's zero and the
highest speed and course the fields can encode.
highest speed and course the fields can encode. Vessel cards and the HUD wrap
the direction after rounding to whole degrees, so a course of 359.5° or more
reads `0°` (`0DEG` in the HUD) rather than `360°`.
For an already tracked vessel, AISStream static messages update name, type,
destination and IMO without changing its position, motion, fix timestamps,
+21
View File
@@ -1322,6 +1322,27 @@ test('buildVesselCard: heading falls back to course; missing parts are omitted',
assert.deepEqual(bare.details, []);
});
test('vessel cards read a course that rounds to north as 0°, not 360°', () => {
const card = (course) => buildVesselCard(makeRecord({ heading: null, course })).details[0];
assert.equal(card(359.4), 'CONTAINER SHIP · 14.5KT · 359°');
assert.equal(card(359.7), 'CONTAINER SHIP · 14.5KT · 0°');
assert.equal(
buildSelectedVesselCard(makeRecord({ heading: null, course: 359.7 })).details[0],
'CONTAINER SHIP · 14.5KT · 0°',
);
});
test('the vessel HUD reads a course that rounds to north as HDG 0DEG', () => {
const harness = installWireHarness(undefined, { selectedRecord: null });
try {
Object.assign(harness.record, { heading: null, course: 359.7 });
assert.equal(aisLiveVesselsLayer.selectById(harness.record.mmsi), true);
assert.match(harness.hud.textContent, /HDG: 0DEG/);
} finally {
harness.cleanup();
}
});
test('buildVesselCard: unnamed vessels title as MMSI; long names truncate', () => {
const unnamed = buildVesselCard(makeRecord({ name: 'VESSEL' }));
assert.equal(unnamed.title, 'MMSI 353136000');
+6 -3
View File
@@ -60,7 +60,10 @@ export function createCards({
if (record.speed !== null && record.speed !== undefined)
parts.push(formatSpeed(record.speed));
const direction = record.heading ?? record.course;
if (Number.isFinite(direction)) parts.push(`${Math.round(direction)}°`);
// A course of 359.5° and above rounds to north; modulo keeps it from
// printing 360°. The selected card and the HUD wrap the same way.
if (Number.isFinite(direction))
parts.push(`${Math.round(direction) % 360}°`);
return {
id: vesselOverlayEntryId(record),
actionable: Boolean(record?.mmsi),
@@ -91,7 +94,7 @@ export function createCards({
[
vesselTypeShort(record) || t('VESSEL'),
formatSpeed(record.speed),
Number.isFinite(direction) ? `${Math.round(direction)}°` : '--°',
Number.isFinite(direction) ? `${Math.round(direction) % 360}°` : '--°',
].join(' · '),
];
const destination = String(record.destination || '').trim();
@@ -181,7 +184,7 @@ export function createCards({
function formatHeading(heading) {
return t('{heading}DEG', {
heading: Number.isFinite(heading) ? Math.round(heading) : '--',
heading: Number.isFinite(heading) ? Math.round(heading) % 360 : '--',
});
}