fix(satellites): a hidden class takes the ISS ring, label and docked names

Class toggles only hid points: with Stations off the ISS's orbit ring and
its label still drew, and each catalog refresh showed the ring again. The
ring and label now follow the ISS's class, and the tracked card no longer
names a docked companion whose class is hidden.

A failed Starlink load reverted to the core catalog without applying class
visibility again, so the core catalog's own Starlink points stayed on while
the chip, legend and lists said Starlink was off, and classes.starlink kept
reading true. Both are put right on failure.
This commit is contained in:
Sameh Khamis committed 2026-11-08 15:16:01 -08:00
1 parent 8bd16b0be9
commit b59eb8b18c
5 files changed
+105 -7

No files matched your search

+8
View File
@@ -209,6 +209,14 @@ export function createCatalog({ state: layerState, services, parts, source }) {
if (token !== layerState._denseLoadToken) return;
layerState._params.catalog = 'core';
_removeDenseCatalog();
// Back on core, Starlink is off: hide the core catalog's own Starlink
// points again, and say so in the class choice.
if (layerState._params.classes)
layerState._params.classes = {
...layerState._params.classes,
starlink: false,
};
parts.controls._applyClassVisibility?.();
layerState._denseStatus = 'failed';
layerState._denseError = reason;
parts.controls._notifyRowControls();
+66
View File
@@ -258,3 +258,69 @@ test('SATCAT CSV parses, quoted commas included, keyed by NORAD id', () => {
assert.equal(parseSatcatCsv('<html>busy</html>').size, 0);
assert.equal(parseSatcatCsv('').size, 0);
});
test('turning Stations off takes the ISS ring and label with its point', async () => {
const { createControls } = await import('./controls.js');
let overlaySyncs = 0;
const ring = { primitive: { show: true } };
const state = {
_enabled: true,
_params: {
catalog: 'core',
showOrbits: true,
classes: { ...DEFAULT_SATELLITE_CLASSES, stations: false },
},
_catalog: new Map([[25544, { satelliteClass: 'stations' }]]),
_points: new Map([[25544, { show: true }]]),
_orbitPaths: new Map([[25544, ring]]),
_detectionObjects: new Map(),
_trackedNorad: null,
};
const controls = createControls({
state,
services: { layerState: { isExplicitLayerStateOrigin: () => false } },
parts: { labels: { _syncIssOverlay: () => (overlaySyncs += 1) } },
source: {},
});
controls._applyClassVisibility();
assert.equal(state._points.get(25544).show, false);
assert.equal(ring.primitive.show, false, 'the ring goes with the class');
assert.ok(overlaySyncs >= 1, 'the label is re-synced');
state._params.classes = { ...DEFAULT_SATELLITE_CLASSES };
controls._applyClassVisibility();
assert.equal(ring.primitive.show, true, 'and comes back with it');
});
test('a failed Starlink load hides the core catalog Starlink points again', async () => {
const { createCatalog } = await import('./catalog.js');
let applied = 0;
const state = {
_params: {
catalog: 'dense',
classes: { ...DEFAULT_SATELLITE_CLASSES, starlink: true },
},
_denseLoadToken: 3,
_denseIds: [],
_catalog: new Map(),
_points: new Map(),
_detectionObjects: new Map(),
_trackedNorad: null,
_activeUpdateControllers: new Set(),
};
const catalog = createCatalog({
state,
services: {},
parts: {
controls: {
_applyClassVisibility: () => (applied += 1),
_notifyRowControls() {},
},
labels: { _syncIssOverlay() {} },
},
source: {},
});
catalog._denseLoadFailed(state._denseLoadToken, 'feed unreachable');
assert.equal(state._params.catalog, 'core');
assert.equal(state._params.classes.starlink, false, 'the choice says so');
assert.ok(applied >= 1, 'class visibility is applied again');
});
+24 -6
View File
@@ -48,16 +48,30 @@ export function createControls({ state: layerState, services, parts, source }) {
/**
* Show or hide every point by its class. The tracked satellite's point
* stays hidden (its entity draws the dot).
* stays hidden (its entity draws the dot). The ISS's ring and label go
* with its class.
*/
function _applyClassVisibility() {
for (const [noradId, point] of layerState._points) {
if (noradId === layerState._trackedNorad) continue;
point.show = _classVisible(noradId);
}
const issPath = layerState._orbitPaths.get(ISS_NORAD);
if (issPath) issPath.primitive.show = _issOrbitVisible();
parts.labels?._syncIssOverlay?.();
layerState._detectionObjects.clear();
}
/** Whether the ISS's ring shows: orbits on, the layer drawing, its class on. */
function _issOrbitVisible() {
return (
satelliteVisualsVisible(
layerState._enabled,
layerState._params.showOrbits,
) && _classVisible(ISS_NORAD)
);
}
/**
* The row's info line while the classes rest on old or missing SATCAT
* evidence (`_satcatState`), and an empty one otherwise.
@@ -530,11 +544,14 @@ export function createControls({ state: layerState, services, parts, source }) {
}
if (params.showOrbits !== undefined) {
layerState._params.showOrbits = params.showOrbits !== false;
for (const path of layerState._orbitPaths.values())
path.primitive.show = satelliteVisualsVisible(
layerState._enabled,
layerState._params.showOrbits,
);
for (const [noradId, path] of layerState._orbitPaths)
path.primitive.show =
noradId === ISS_NORAD
? _issOrbitVisible()
: satelliteVisualsVisible(
layerState._enabled,
layerState._params.showOrbits,
);
parts.labels._syncIssOverlay();
}
if (catalogChanged && catalog === 'dense') {
@@ -690,6 +707,7 @@ export function createControls({ state: layerState, services, parts, source }) {
_classTally,
_classVisible,
_applyClassVisibility,
_issOrbitVisible,
satelliteVisualsVisible,
satelliteCatalogModeChanged,
methods,
+4 -1
View File
@@ -356,7 +356,10 @@ export function createIngestion({
if (layerState._catalog.has(ISS_NORAD)) {
parts.rendering._showOrbitPath(ISS_NORAD, POINT_STYLES.iss.color);
const issPath = layerState._orbitPaths.get(ISS_NORAD);
if (issPath) issPath.primitive.show = layerState._params.showOrbits;
if (issPath)
issPath.primitive.show =
layerState._params.showOrbits &&
parts.controls._classVisible(ISS_NORAD);
parts.labels._syncIssOverlay();
}
+3
View File
@@ -83,6 +83,8 @@ export function createLabels({ state: layerState, services, parts, source }) {
function _dockedCompanionNames() {
const names = [];
for (const noradId of layerState._dockedCompanions) {
// A hidden class is not named on the tracked card either.
if (parts.controls?._classVisible?.(noradId) === false) continue;
names.push(
layerState._catalog.get(noradId)?.name?.trim() || `SAT-${noradId}`,
);
@@ -102,6 +104,7 @@ export function createLabels({ state: layerState, services, parts, source }) {
layerState._trackedNorad !== ISS_NORAD &&
!layerState._dockedCompanions.has(ISS_NORAD) &&
layerState._catalog.has(ISS_NORAD) &&
parts.controls?._classVisible?.(ISS_NORAD) !== false &&
_issDisplayCached();
if (!visible) {
layerState._overlayHost.clearSource(ISS_OVERLAY_SOURCE_ID);