fix(cockpit): radar contacts stay where the vessel layer draws them

The vessel layer draws other ships at their last AIS fix and does not
advance them, so dead-reckoning radar contacts moved the mismatch from own
ship to everyone else: a 15 kn ferry reported 90 s ago showed ~700 m ahead
of the ship out of the window. Own ship stays centred where the camera
rides; contacts go back to their last fixes, and the query drops the extra
reach and candidates (which could also cut the nearest ship in a crowd).
This commit is contained in:
Sameh Khamis committed 2026-11-08 14:09:17 -08:00
1 parent 0ba7ba6512
commit f96a89a9ef
7 files changed
+27 -114

No files matched your search

+14 -26
View File
@@ -360,12 +360,8 @@ export function createQueries({
* @param {number} [maxCount=60] Maximum entries to return.
* @returns {Array<{ id: string, name: string, latitude: number,
* longitude: number, course: number|null, heading: number|null,
* type: string, color: string, stale: boolean,
* motionCourse: number|null, velocityMps: number|null,
* fixEpochMs: number|null, receivedAtMs: number|null,
* feedEpochMs: number|null }>} Empty while the layer is off. The
* motion fields are `vesselCockpitInfo`'s, for dead reckoning; a
* ship with no MMSI has no motion fields and an `unkeyed:` id.
* type: string, color: string, stale: boolean }>} Empty while the
* layer is off. A ship with no MMSI has an `unkeyed:` id.
*/
getRadarContacts(lat, lon, rangeM, maxCount = 60) {
const records = state.records.all;
@@ -393,26 +389,18 @@ export function createQueries({
entries.push({ record, index, distanceM });
});
entries.sort((a, b) => a.distanceM - b.distanceM);
return entries.slice(0, cap).map(({ record, index }) => {
const motion = vesselCockpitInfo(record);
return {
// A ship with no MMSI still needs a key of its own on the scope.
id: record.mmsi ? String(record.mmsi) : `unkeyed:${index}`,
name: String(record.name || record.mmsi || ''),
latitude: record.lat,
longitude: record.lon,
course: finite(record.course),
heading: finite(record.heading),
type: String(record.type || ''),
color: vesselTypeCss(record.type),
stale: (record.missedRefreshes || 0) > 0,
motionCourse: motion?.motionCourse ?? null,
velocityMps: motion?.velocityMps ?? null,
fixEpochMs: motion?.fixEpochMs ?? null,
receivedAtMs: motion?.receivedAtMs ?? null,
feedEpochMs: motion?.feedEpochMs ?? null,
};
});
return entries.slice(0, cap).map(({ record, index }) => ({
// A ship with no MMSI still needs a key of its own on the scope.
id: record.mmsi ? String(record.mmsi) : `unkeyed:${index}`,
name: String(record.name || record.mmsi || ''),
latitude: record.lat,
longitude: record.lon,
course: finite(record.course),
heading: finite(record.heading),
type: String(record.type || ''),
color: vesselTypeCss(record.type),
stale: (record.missedRefreshes || 0) > 0,
}));
},
/**