iss-spoofing: with real-time correction, cities hearing a signal more than 5% off the recorded speed turn red

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Toby
2026-10-31 11:54:07 -04:00
parent 2743af1e81
commit 9b597a312d
3 changed files with 20 additions and 7 deletions
+6 -3
View File
@@ -127,7 +127,7 @@ footer{padding-block:18px 30px;color:var(--faint);font-size:13px}
<section class="wrap intro"> <section class="wrap intro">
<p><b>Hypothesis.</b> 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.</p> <p><b>Hypothesis.</b> 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.</p>
<p><b>Test.</b> The <span class="k-st">station</span> is placed where the published orbit puts it, which is where it is seen to pass at the predicted times (<a href="https://spotthestation.nasa.gov/">Spot the Station</a>); its speed across each map follows from that map's distances. The <span class="k-sig">spoofed signal</span> 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 <i>Line up signal now</i>. A city turns <span class="k-bad">red</span> when the station is above its horizon and the signal is not, or the reverse.</p> <p><b>Test.</b> The <span class="k-st">station</span> is placed where the published orbit puts it, which is where it is seen to pass at the predicted times (<a href="https://spotthestation.nasa.gov/">Spot the Station</a>); its speed across each map follows from that map's distances. The <span class="k-sig">spoofed signal</span> 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 <i>Line up signal now</i>. A city turns <span class="k-bad">red</span> when the station is above its horizon and the signal is not, or the reverse. With <i>Real-time correction</i> 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.</p>
</section> </section>
<div class="bar"> <div class="bar">
@@ -163,7 +163,7 @@ footer{padding-block:18px 30px;color:var(--faint);font-size:13px}
<li><svg viewBox="0 0 22 14" aria-hidden="true"><path d="M1 7h20" stroke="#ff4fd8" stroke-opacity=".6" stroke-width="3"/></svg>Gap along the track</li> <li><svg viewBox="0 0 22 14" aria-hidden="true"><path d="M1 7h20" stroke="#ff4fd8" stroke-opacity=".6" stroke-width="3"/></svg>Gap along the track</li>
<li><svg viewBox="0 0 22 14" aria-hidden="true"><circle cx="11" cy="7" r="6" fill="none" stroke="#fff" stroke-opacity=".6"/></svg>Above the horizon</li> <li><svg viewBox="0 0 22 14" aria-hidden="true"><circle cx="11" cy="7" r="6" fill="none" stroke="#fff" stroke-opacity=".6"/></svg>Above the horizon</li>
<li><svg viewBox="0 0 22 14" aria-hidden="true"><circle cx="11" cy="7" r="3" fill="#3dffa0"/></svg>City: station and signal in view</li> <li><svg viewBox="0 0 22 14" aria-hidden="true"><circle cx="11" cy="7" r="3" fill="#3dffa0"/></svg>City: station and signal in view</li>
<li><svg viewBox="0 0 22 14" aria-hidden="true"><circle cx="11" cy="7" r="3" fill="#ff4d5e"/></svg>City: one without the other</li> <li><svg viewBox="0 0 22 14" aria-hidden="true"><circle cx="11" cy="7" r="3" fill="#ff4d5e"/></svg>City: one without the other, or a signal at the wrong speed</li>
</ul> </ul>
<main class="wrap grid"> <main class="wrap grid">
@@ -306,6 +306,8 @@ const SM = {};
for (const key of ["ae", "dualpole", "mercator"]) SM[key] = Float64Array.from(D.maps[key].S); for (const key of ["ae", "dualpole", "mercator"]) SM[key] = Float64Array.from(D.maps[key].S);
let syncT = null, corr = false, latency = 10; let syncT = null, corr = false, latency = 10;
const LATENCY = [0, 1, 2, 5, 10, 20, 30, 60, 120, 300]; 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) { function timeAtDistance(arr, km) {
let lo = 0, hi = arr.length - 1; let lo = 0, hi = arr.length - 1;
if (km <= arr[lo]) return 0; if (km <= arr[lo]) return 0;
@@ -447,7 +449,7 @@ function drawPanel(p, S, pulse) {
} }
const okP = new Path2D(), badP = new Path2D(); const okP = new Path2D(), badP = new Path2D();
for (let j = 0; j < NC; j++) { 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; p.state[j] = st;
const q = cxy[j]; const q = cxy[j];
if (!st || !q) continue; if (!st || !q) continue;
@@ -590,6 +592,7 @@ const tip = document.getElementById("tip");
const STATE = (p, j) => { const STATE = (p, j) => {
if (p.key === "globe") return p.state[j] ? "Station and signal in view" : "Neither in view"; 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); 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"; 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; let lastS = null;
+6 -3
View File
@@ -127,7 +127,7 @@ footer{padding-block:18px 30px;color:var(--faint);font-size:13px}
<section class="wrap intro"> <section class="wrap intro">
<p><b>Hypothesis.</b> 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.</p> <p><b>Hypothesis.</b> 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.</p>
<p><b>Test.</b> The <span class="k-st">station</span> is placed where the published orbit puts it, which is where it is seen to pass at the predicted times (<a href="https://spotthestation.nasa.gov/">Spot the Station</a>); its speed across each map follows from that map's distances. The <span class="k-sig">spoofed signal</span> 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 <i>Line up signal now</i>. A city turns <span class="k-bad">red</span> when the station is above its horizon and the signal is not, or the reverse.</p> <p><b>Test.</b> The <span class="k-st">station</span> is placed where the published orbit puts it, which is where it is seen to pass at the predicted times (<a href="https://spotthestation.nasa.gov/">Spot the Station</a>); its speed across each map follows from that map's distances. The <span class="k-sig">spoofed signal</span> 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 <i>Line up signal now</i>. A city turns <span class="k-bad">red</span> when the station is above its horizon and the signal is not, or the reverse. With <i>Real-time correction</i> 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.</p>
</section> </section>
<div class="bar"> <div class="bar">
@@ -163,7 +163,7 @@ footer{padding-block:18px 30px;color:var(--faint);font-size:13px}
<li><svg viewBox="0 0 22 14" aria-hidden="true"><path d="M1 7h20" stroke="#ff4fd8" stroke-opacity=".6" stroke-width="3"/></svg>Gap along the track</li> <li><svg viewBox="0 0 22 14" aria-hidden="true"><path d="M1 7h20" stroke="#ff4fd8" stroke-opacity=".6" stroke-width="3"/></svg>Gap along the track</li>
<li><svg viewBox="0 0 22 14" aria-hidden="true"><circle cx="11" cy="7" r="6" fill="none" stroke="#fff" stroke-opacity=".6"/></svg>Above the horizon</li> <li><svg viewBox="0 0 22 14" aria-hidden="true"><circle cx="11" cy="7" r="6" fill="none" stroke="#fff" stroke-opacity=".6"/></svg>Above the horizon</li>
<li><svg viewBox="0 0 22 14" aria-hidden="true"><circle cx="11" cy="7" r="3" fill="#3dffa0"/></svg>City: station and signal in view</li> <li><svg viewBox="0 0 22 14" aria-hidden="true"><circle cx="11" cy="7" r="3" fill="#3dffa0"/></svg>City: station and signal in view</li>
<li><svg viewBox="0 0 22 14" aria-hidden="true"><circle cx="11" cy="7" r="3" fill="#ff4d5e"/></svg>City: one without the other</li> <li><svg viewBox="0 0 22 14" aria-hidden="true"><circle cx="11" cy="7" r="3" fill="#ff4d5e"/></svg>City: one without the other, or a signal at the wrong speed</li>
</ul> </ul>
<main class="wrap grid"> <main class="wrap grid">
@@ -304,6 +304,8 @@ const SM = {};
for (const key of ["ae", "dualpole", "mercator"]) SM[key] = Float64Array.from(D.maps[key].S); for (const key of ["ae", "dualpole", "mercator"]) SM[key] = Float64Array.from(D.maps[key].S);
let syncT = null, corr = false, latency = 10; let syncT = null, corr = false, latency = 10;
const LATENCY = [0, 1, 2, 5, 10, 20, 30, 60, 120, 300]; 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) { function timeAtDistance(arr, km) {
let lo = 0, hi = arr.length - 1; let lo = 0, hi = arr.length - 1;
if (km <= arr[lo]) return 0; if (km <= arr[lo]) return 0;
@@ -445,7 +447,7 @@ function drawPanel(p, S, pulse) {
} }
const okP = new Path2D(), badP = new Path2D(); const okP = new Path2D(), badP = new Path2D();
for (let j = 0; j < NC; j++) { 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; p.state[j] = st;
const q = cxy[j]; const q = cxy[j];
if (!st || !q) continue; if (!st || !q) continue;
@@ -588,6 +590,7 @@ const tip = document.getElementById("tip");
const STATE = (p, j) => { const STATE = (p, j) => {
if (p.key === "globe") return p.state[j] ? "Station and signal in view" : "Neither in view"; 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); 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"; 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; let lastS = null;
+8 -1
View File
@@ -85,13 +85,20 @@ const rc = await p.evaluate(async () => {
const l10 = q('ae', 's'), vs10 = q('ae', 'vs'), syncDisabled = document.getElementById('sync').disabled; const l10 = q('ae', 's'), vs10 = q('ae', 'vs'), syncDisabled = document.getElementById('sync').disabled;
lat.value = '0'; lat.dispatchEvent(new Event('input')); await wait(); 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'); 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(); 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)); console.log(JSON.stringify(rc));
if (!/^10 s behind/.test(rc.l10)) fail('correction latency 10 s not shown', rc.l10); 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 (!/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 (/×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 (!/×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); if (!rc.syncDisabled || /Corrected/.test(rc.info)) fail('line-up controls not disabled / restored', rc.syncDisabled, rc.info);
await b.close(); await b.close();