diff --git a/templates/satellite_dashboard.html b/templates/satellite_dashboard.html
index f4344ca..df9c559 100644
--- a/templates/satellite_dashboard.html
+++ b/templates/satellite_dashboard.html
@@ -1024,6 +1024,23 @@
return Number.isFinite(value);
}
+ // Compute slant range (km) from observer on Earth's surface to satellite.
+ // Uses spherical Earth approximation; accurate enough for display.
+ function _computeSlantRange(obsLat, obsLon, satLat, satLon, satAltKm) {
+ const R = 6371;
+ const toRad = Math.PI / 180;
+ const lat1 = obsLat * toRad, lon1 = obsLon * toRad;
+ const lat2 = satLat * toRad, lon2 = satLon * toRad;
+ const rSat = R + satAltKm;
+ const ox = R * Math.cos(lat1) * Math.cos(lon1);
+ const oy = R * Math.cos(lat1) * Math.sin(lon1);
+ const oz = R * Math.sin(lat1);
+ const sx = rSat * Math.cos(lat2) * Math.cos(lon2);
+ const sy = rSat * Math.cos(lat2) * Math.sin(lon2);
+ const sz = rSat * Math.sin(lat2);
+ return Math.sqrt((sx-ox)**2 + (sy-oy)**2 + (sz-oz)**2);
+ }
+
function normalizeLivePosition(pos, previous = latestLivePosition) {
if (!pos) return null;
const merged = {
@@ -1063,7 +1080,17 @@
if (telAlt && _isFiniteNumber(normalized.altitude ?? normalized.alt)) telAlt.textContent = (normalized.altitude ?? normalized.alt).toFixed(0) + ' km';
if (telEl && _isFiniteNumber(normalized.elevation ?? normalized.el)) telEl.textContent = (normalized.elevation ?? normalized.el).toFixed(1) + '°';
if (telAz && _isFiniteNumber(normalized.azimuth ?? normalized.az)) telAz.textContent = (normalized.azimuth ?? normalized.az).toFixed(1) + '°';
- if (telDist && _isFiniteNumber(normalized.distance ?? normalized.dist)) telDist.textContent = (normalized.distance ?? normalized.dist).toFixed(0) + ' km';
+ // Compute slant range from the current lat/lon/altitude so the
+ // distance display updates at the same rate as position (1 Hz via
+ // SSE). Falls back to the Skyfield-computed value from the HTTP
+ // poll if geometry data is not available.
+ let displayDist = normalized.distance ?? normalized.dist;
+ const { lat: obsLat, lon: obsLon, valid: obsValid } = getObserverCoords();
+ const satAlt = normalized.altitude ?? normalized.alt;
+ if (obsValid && _isFiniteNumber(normalized.lat) && _isFiniteNumber(normalized.lon) && _isFiniteNumber(satAlt)) {
+ displayDist = _computeSlantRange(obsLat, obsLon, normalized.lat, normalized.lon, satAlt);
+ }
+ if (telDist && _isFiniteNumber(displayDist)) telDist.textContent = displayDist.toFixed(0) + ' km';
if (selectedPass == null && _isFiniteNumber(normalized.azimuth ?? normalized.az) && _isFiniteNumber(normalized.elevation ?? normalized.el)) {
drawPolarPlotWithPosition(
@@ -2068,9 +2095,10 @@
clearTimeout(_passTimeoutId);
_passTimeoutId = null;
}
- if (_passAbortController === controller) {
- _passAbortController = null;
- }
+ // Keep _passAbortController set until AFTER response.json() so
+ // the retry scheduler doesn't incorrectly see the request as
+ // finished (passes=0 + no controller) and increment _passRequestId
+ // mid-parse, which would cause this response to be discarded.
if (_activePassRequestKey === requestKey) {
_activePassRequestKey = null;
}
@@ -2080,6 +2108,10 @@
throw new Error('Unexpected response while calculating passes');
}
const data = await response.json();
+ // Now safe to release the controller slot
+ if (_passAbortController === controller) {
+ _passAbortController = null;
+ }
if (requestId !== _passRequestId) return;
if (data.status === 'success') {
const resolvedPasses = Array.isArray(data.passes) ? data.passes : [];