Files
gods-eye-view/src/layers/awareness/history.js
T
Sameh Khamis cbbf55cacd feat(vessels): key vessels by their source's stable id, and share a selected vessel
AIS Vessels are keyed by the id their live source gives, with the MMSI as an
attribute: an MMSI can be shared, spoofed or reused, so two vessels with one
MMSI are now two vessels. Looking a vessel up or selecting it by MMSI still
finds it.

A share link made while a vessel is selected opens that vessel. A UUID vessel
id travels as 16 bytes in a new thing field (13). A shared thing that is not
reporting now says so in a sentence per kind, with its id (ICAO, NORAD, MMSI
or vessel UUID) on a second line.
2026-11-10 18:12:53 -08:00

240 lines
7.2 KiB
JavaScript

export function createHistory({ state: layerState, services, parts, source }) {
const militaryFlightsLayer = services.military;
const flightsLayer = services.flights;
/**
* Walk navigation history for the next entry that can still be focused.
*
* A history entry only holds a SNAPSHOT of a contact; the live contact behind
* it can be evicted from its layer at any time (the cull that drives
* CONTACT LOST). Focusing an evicted entry fails, so stepping over it is the
* only way PREVIOUS/NEXT survives a lost subject — stopping at the first dead
* entry strands the operator there for the rest of the session, because the
* index never advances past it.
*
* `state.navigationIndex` moves only on a successful focus: a caller that
* falls through to the live cohort must still splice forward history from the
* position the operator actually occupies.
* @param {number} direction Walk direction (negative = previous).
* @param {{targetLayer: string|null, aircraftClass: string|null}} filters Navigation filters.
* @returns {boolean} Whether a history entry was focused.
*/
function focusCompatibleHistory(
direction,
{ targetLayer, aircraftClass, aircraftOnly, origin = 'programmatic' },
) {
let cursor = layerState.navigationIndex;
for (;;) {
const historyIndex = findCompatibleHistoryIndex(
layerState.navigationHistory,
cursor,
direction,
{
targetLayer,
aircraftClass,
aircraftOnly,
resolveItem: historySubjectItem,
},
);
if (historyIndex === -1) return false;
if (
parts.focus.focusSubject(
layerState.navigationHistory[historyIndex],
true,
{ origin },
)
) {
layerState.navigationIndex = historyIndex;
return true;
}
// Contact evicted since it entered history — step over it. The scan is
// strictly monotonic, so this terminates at the end of history.
cursor = historyIndex;
}
}
function navigateHistory(
direction,
{
targetLayer = null,
aircraftClass = null,
aircraftOnly = false,
origin = 'programmatic',
} = {},
) {
if (
focusCompatibleHistory(direction, {
targetLayer,
aircraftClass,
aircraftOnly,
origin,
})
)
return true;
if (direction < 0) return false;
let targets = parts.navigation.selectNavigationTargets(
layerState.results?.cohorts,
layerState.subject,
[...layerState.navigationVisited],
{ targetLayer, aircraftClass, aircraftOnly },
);
const targetHistoryFullyVisited =
targets.length > 0 && targets.every((target) => target.visited);
const subjectCohortUnknown = parts.subject.subjectCohortFeedUnknown();
if (
targetHistoryFullyVisited &&
parts.navigation.isFlightLayer(layerState.subject?.layerId) &&
!subjectCohortUnknown
) {
const expanded = parts.navigation.findExpandedFlightTarget({
targetLayer,
aircraftClass,
});
if (expanded) {
return parts.focus.requestFocus(
expanded.candidate.layerId,
expanded.candidate.id,
false,
{ origin },
);
}
}
if (targetHistoryFullyVisited) {
parts.navigation.resetNavigationVisitedCycle();
targets = parts.navigation.selectNavigationTargets(
layerState.results?.cohorts,
layerState.subject,
[...layerState.navigationVisited],
{ targetLayer, aircraftClass, aircraftOnly },
);
}
const hasNearbyFlight = targets.some((target) =>
parts.navigation.isFlightLayer(target.layerId),
);
if (
parts.navigation.isFlightLayer(layerState.subject?.layerId) &&
!hasNearbyFlight &&
!subjectCohortUnknown
) {
const expanded = parts.navigation.findExpandedFlightTarget({
targetLayer,
aircraftClass,
});
if (expanded) {
return parts.focus.requestFocus(
expanded.candidate.layerId,
expanded.candidate.id,
false,
{ origin },
);
}
}
if (parts.subject.subjectCohortFeedUnknown()) return false;
const candidate = targets[0];
if (!candidate) return false;
return parts.focus.requestFocus(candidate.layerId, candidate.id, false, {
origin,
});
}
function historySubjectItem(subject) {
if (!subject) return null;
const cohort = layerState.results?.cohorts?.find(
(item) => item.id === subject.layerId,
);
const items =
cohort?.summary?.navigationNearest || cohort?.summary?.nearest || [];
return (
items.find(
(item) =>
String(item?.icao24 || item?.id || item?.mmsi) === String(subject.id),
) || subject
);
}
/** Retain filter metadata when a production subject enters navigation history. */
function historySubjectSnapshot(subject, sourceItem = null) {
const aircraftClass =
sourceItem?.aircraftClass ||
sourceItem?.klass ||
sourceItem?.type ||
subject?.aircraftClass ||
null;
return aircraftClass ? { ...subject, aircraftClass } : { ...subject };
}
function historySourceItem(subject) {
if (!parts.navigation.isFlightLayer(subject?.layerId) || !subject?.position)
return null;
// The current sweep already holds this contact's record. Reusing it keeps
// selection O(1) against the cohort instead of paying a fresh full-layer
// proximity scan per selection — the burst cost when NEXT walks a cohort.
const cohort = layerState.results?.cohorts?.find(
(item) => item.id === subject.layerId,
);
const items =
cohort?.summary?.navigationNearest || cohort?.summary?.nearest || [];
const fromSweep = items.find(
(item) => String(item?.icao24 || item?.id) === String(subject.id),
);
if (fromSweep) return fromSweep;
const layer =
subject.layerId === 'military' ? militaryFlightsLayer : flightsLayer;
return (
layer
.getNearby(subject.position, 1000, 25, { includeHidden: true })
.find(
(item) => String(item?.icao24 || item?.id) === String(subject.id),
) || null
);
}
/** Find the next history entry compatible with requested navigation filters. */
function findCompatibleHistoryIndex(
history,
startIndex,
direction,
{
targetLayer = null,
aircraftClass = null,
aircraftOnly = false,
resolveItem = (subject) => subject,
} = {},
) {
const step = direction < 0 ? -1 : 1;
for (
let index = startIndex + step;
index >= 0 && index < history.length;
index += step
) {
const subject = history[index];
if (aircraftOnly && !parts.navigation.isFlightLayer(subject?.layerId))
continue;
if (targetLayer && subject?.layerId !== targetLayer) continue;
if (
aircraftClass &&
!parts.navigation.aircraftClassMatchesFilter(
resolveItem(subject),
aircraftClass,
)
)
continue;
return index;
}
return -1;
}
return {
focusCompatibleHistory,
navigateHistory,
historySubjectItem,
historySubjectSnapshot,
historySourceItem,
findCompatibleHistoryIndex,
};
}