diff --git a/changelog.d/1444-replay-speed-countdown.md b/changelog.d/1444-replay-speed-countdown.md new file mode 100644 index 000000000..fb35d008b --- /dev/null +++ b/changelog.d/1444-replay-speed-countdown.md @@ -0,0 +1,3 @@ +- Keep Space Missions' replay countdown in real time when the replay speed + changes before liftoff; 0.25× had stretched T−8 to T−32 (Aditya, + @Rohilalala, #1444). diff --git a/src/layers/launches/replay.js b/src/layers/launches/replay.js index 364399a5a..86b6a08f0 100644 --- a/src/layers/launches/replay.js +++ b/src/layers/launches/replay.js @@ -249,7 +249,8 @@ export function createReplay({ state: layerState, services, parts, source }) { ? layerState._replayPausedAtMs : Date.now(); for (const [launchId, startedAt] of layerState._animationStarts) { - if (!Number.isFinite(startedAt)) continue; + // A countdown runs in real time; rescaling it turned T-8 into T-32 at 0.25x. + if (!Number.isFinite(startedAt) || startedAt > now) continue; const elapsedMissionMs = (now - startedAt) * previousSpeed; layerState._animationStarts.set( launchId, diff --git a/src/layers/launches/replay.test.mjs b/src/layers/launches/replay.test.mjs new file mode 100644 index 000000000..b0700d0f3 --- /dev/null +++ b/src/layers/launches/replay.test.mjs @@ -0,0 +1,47 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { createState } from './state.js'; +import { createReplay } from './replay.js'; +import { createPaths } from './paths.js'; + +const noop = () => {}; + +function replayRig() { + const state = createState({ + services: { + overlays: { + setOverlayEntries: noop, + setOverlaySourceVisible: noop, + clearOverlaySource: noop, + }, + }, + }); + const parts = { panel: { publishPanel: noop } }; + parts.paths = createPaths({ state, parts }); + return { state, replay: createReplay({ state, parts }) }; +} + +test('changing replay speed during the countdown keeps its real-time liftoff', (t) => { + t.mock.timers.enable({ apis: ['Date'], now: 1_000_000 }); + const { state, replay } = replayRig(); + const launch = { launchTime: '2026-07-20T10:00:00Z', timeline: [] }; + state._animationStarts.set('countdown', 1_008_000); // T-8 + state._animationStarts.set('flying', 990_000); // 10 s into the mission + + replay.setReplaySpeed(0.25); + + const startedAt = state._animationStarts.get('countdown'); + assert.equal(startedAt, 1_008_000); + const countdown = replay.replayState( + launch, + startedAt, + 12, + 28, + 5400, + state._replaySpeed, + Date.now(), + ); + assert.equal(countdown.countdownSeconds, 8); + // Mission time already flown is still preserved across the change. + assert.equal(state._animationStarts.get('flying'), 960_000); +});