Hypothesis. The radio signals attributed to the International Space Station come from ground transmitters along its track, imitating a source that moves at the measured speed, 7.07 km/s.
-
Test. The station is placed where the published orbit puts it, which is where it is seen to pass at the predicted times (Spot the Station); its speed across each map follows from that map's distances. The spoofed signal is the source those transmitters imitate, moving along the same track at 7.07 km/s, lined up with the station at each northbound equator crossing, or at any moment with Line up signal now. A city turns red when the station is above its horizon and the signal is not, or the reverse.
+
Test. The station is placed where the published orbit puts it, which is where it is seen to pass at the predicted times (Spot the Station); its speed across each map follows from that map's distances. The spoofed signal is the source those transmitters imitate, moving along the same track at 7.07 km/s, lined up with the station at each northbound equator crossing, or at any moment with Line up signal now. A city turns red when the station is above its horizon and the signal is not, or the reverse. With Real-time correction it also turns red when the signal it hears moves more than 5% faster or slower than the 7.07 km/s the recordings show, since its Doppler shift would then differ from theirs.
City: one without the other, or a signal at the wrong speed
@@ -306,6 +306,8 @@ const SM = {};
for (const key of ["ae", "dualpole", "mercator"]) SM[key] = Float64Array.from(D.maps[key].S);
let syncT = null, corr = false, latency = 10;
const LATENCY = [0, 1, 2, 5, 10, 20, 30, 60, 120, 300];
+// with real-time correction the signal moves at the map speed; a receiver hears that speed in its Doppler shift
+const wrongSpeed = (p, M) => corr && p.key !== "globe" && Math.abs(M.vs / VM - 1) > 0.05;
function timeAtDistance(arr, km) {
let lo = 0, hi = arr.length - 1;
if (km <= arr[lo]) return 0;
@@ -447,7 +449,7 @@ function drawPanel(p, S, pulse) {
}
const okP = new Path2D(), badP = new Path2D();
for (let j = 0; j < NC; j++) {
- const s = inView(S.us, j), h = inView(M.ug, j), st = s && h ? 1 : s || h ? 2 : 0;
+ const s = inView(S.us, j), h = inView(M.ug, j), st = s && h ? (wrongSpeed(p, M) ? 2 : 1) : s || h ? 2 : 0;
p.state[j] = st;
const q = cxy[j];
if (!st || !q) continue;
@@ -590,6 +592,7 @@ const tip = document.getElementById("tip");
const STATE = (p, j) => {
if (p.key === "globe") return p.state[j] ? "Station and signal in view" : "Neither in view";
const S = lastS, s = inView(S.us, j), h = inView(S.m[p.key].ug, j);
+ if (s && h && wrongSpeed(p, S.m[p.key])) return `Station and signal in view; the signal moves at ×${(S.m[p.key].vs / VM).toFixed(2)} the recorded speed, so its Doppler shift is wrong`;
return s && h ? "Station and signal in view" : s ? "Station in view, no signal" : h ? "Signal, no station in view" : "Neither in view";
};
let lastS = null;
diff --git a/satellites/template.html b/satellites/template.html
index c44665e..6dfa1a0 100644
--- a/satellites/template.html
+++ b/satellites/template.html
@@ -127,7 +127,7 @@ footer{padding-block:18px 30px;color:var(--faint);font-size:13px}
Hypothesis. The radio signals attributed to the International Space Station come from ground transmitters along its track, imitating a source that moves at the measured speed, {{V_SIG}} km/s.
-
Test. The station is placed where the published orbit puts it, which is where it is seen to pass at the predicted times (Spot the Station); its speed across each map follows from that map's distances. The spoofed signal is the source those transmitters imitate, moving along the same track at {{V_SIG}} km/s, lined up with the station at each northbound equator crossing, or at any moment with Line up signal now. A city turns red when the station is above its horizon and the signal is not, or the reverse.
+
Test. The station is placed where the published orbit puts it, which is where it is seen to pass at the predicted times (Spot the Station); its speed across each map follows from that map's distances. The spoofed signal is the source those transmitters imitate, moving along the same track at {{V_SIG}} km/s, lined up with the station at each northbound equator crossing, or at any moment with Line up signal now. A city turns red when the station is above its horizon and the signal is not, or the reverse. With Real-time correction it also turns red when the signal it hears moves more than 5% faster or slower than the {{V_SIG}} km/s the recordings show, since its Doppler shift would then differ from theirs.
City: one without the other, or a signal at the wrong speed
@@ -304,6 +304,8 @@ const SM = {};
for (const key of ["ae", "dualpole", "mercator"]) SM[key] = Float64Array.from(D.maps[key].S);
let syncT = null, corr = false, latency = 10;
const LATENCY = [0, 1, 2, 5, 10, 20, 30, 60, 120, 300];
+// with real-time correction the signal moves at the map speed; a receiver hears that speed in its Doppler shift
+const wrongSpeed = (p, M) => corr && p.key !== "globe" && Math.abs(M.vs / VM - 1) > 0.05;
function timeAtDistance(arr, km) {
let lo = 0, hi = arr.length - 1;
if (km <= arr[lo]) return 0;
@@ -445,7 +447,7 @@ function drawPanel(p, S, pulse) {
}
const okP = new Path2D(), badP = new Path2D();
for (let j = 0; j < NC; j++) {
- const s = inView(S.us, j), h = inView(M.ug, j), st = s && h ? 1 : s || h ? 2 : 0;
+ const s = inView(S.us, j), h = inView(M.ug, j), st = s && h ? (wrongSpeed(p, M) ? 2 : 1) : s || h ? 2 : 0;
p.state[j] = st;
const q = cxy[j];
if (!st || !q) continue;
@@ -588,6 +590,7 @@ const tip = document.getElementById("tip");
const STATE = (p, j) => {
if (p.key === "globe") return p.state[j] ? "Station and signal in view" : "Neither in view";
const S = lastS, s = inView(S.us, j), h = inView(S.m[p.key].ug, j);
+ if (s && h && wrongSpeed(p, S.m[p.key])) return `Station and signal in view; the signal moves at ×${(S.m[p.key].vs / VM).toFixed(2)} the recorded speed, so its Doppler shift is wrong`;
return s && h ? "Station and signal in view" : s ? "Station in view, no signal" : h ? "Signal, no station in view" : "Neither in view";
};
let lastS = null;
diff --git a/tests/sync.mjs b/tests/sync.mjs
index be3cf0a..cb154ef 100644
--- a/tests/sync.mjs
+++ b/tests/sync.mjs
@@ -85,13 +85,20 @@ const rc = await p.evaluate(async () => {
const l10 = q('ae', 's'), vs10 = q('ae', 'vs'), syncDisabled = document.getElementById('sync').disabled;
lat.value = '0'; lat.dispatchEvent(new Event('input')); await wait();
const l0 = q('ae', 's'), vs0 = q('ae', 'vs'), v0 = q('ae', 'v'), g0 = q('globe', 'vs');
+ // level at 0 ms, yet cities under the AE signal hear the wrong speed; none on the globe
+ let aeRed = 0, globeRed = 0;
+ for (const t of [3000, 4000, 5000, 6000, 7000, 8000]) {
+ r.value = String(t); r.dispatchEvent(new Event('input')); await wait();
+ aeRed = Math.max(aeRed, +q('ae', 'c')); globeRed = Math.max(globeRed, +q('globe', 'c'));
+ }
document.getElementById('corr').click(); await wait();
- return { l10, vs10, syncDisabled, l0, vs0, v0, g0, off: q('ae', 's'), offvs: q('ae', 'vs'), info: document.getElementById('syncinfo').textContent };
+ return { l10, vs10, syncDisabled, l0, vs0, v0, g0, aeRed, globeRed, off: q('ae', 's'), offvs: q('ae', 'vs'), info: document.getElementById('syncinfo').textContent };
});
console.log(JSON.stringify(rc));
if (!/^10 s behind/.test(rc.l10)) fail('correction latency 10 s not shown', rc.l10);
if (!/level/.test(rc.l0) || rc.vs0 !== rc.v0) fail('zero latency: signal should be level and move at the map speed', rc.l0, rc.vs0, rc.v0);
if (/×1\.00/.test(rc.vs0)) fail('corrected signal speed should differ from its Doppler on the AE map', rc.vs0);
+if (!(rc.aeRed > 0) || rc.globeRed !== 0) fail('zero latency: AE cities should be red for the wrong signal speed, none on the globe', rc.aeRed, rc.globeRed);
if (!/×1\.00/.test(rc.offvs) || !/×1\.00/.test(rc.g0)) fail('uncorrected / globe signal speed should equal its Doppler', rc.offvs, rc.g0);
if (!rc.syncDisabled || /Corrected/.test(rc.info)) fail('line-up controls not disabled / restored', rc.syncDisabled, rc.info);
await b.close();