Let the reader set the clock to a date, and run it backwards
The clock from #33 could only run forwards from now. The Display panel's clock now has a Date (UTC) field, a native datetime-local in a form, so Enter submits it and the browser holds it to its min and max. It jumps the clock to that date, and the clock carries on from there at the rate it was running at. A Backwards toggle (aria-pressed) runs the same four rates the other way. The radios still pick the rate's size and keep the direction when it changes. The window is AD 1 to AD 3000. The end is where Standish's Table 2 stops being fitted (3000 BC to AD 3000; every planet within 0.29 degrees of Horizons at each date measured out to 3000). The start is the date input's own floor. TimeStore.setDate refuses anything outside it, and NaN, and leaves the clock where it was. The field is read as UTC. Its dates are proleptic Gregorian, as a Date is, so before 1582 they run up to ten days ahead of the Julian-calendar dates history gives. The window is written beside CLOCK_WINDOW, with the moons' shorter reach (Phobos 11 degrees out by 2100). The system note now names the date it is drawn for, to the minute: "... to 2020-12-21 18:00 UTC.", or "to now, <date> UTC." at the present. Measured in the app (port 4311, keyboard only: fill, Enter): - Set to 2020-12-21 18:00 UTC, Jupiter and Saturn seen from Earth's drawn position are 0.113 degrees apart. Horizons gives 0.102 geocentric (geometric 0.1017, astrometric 0.1018). Distances: 5.9267 and 10.8296 AU against Horizons' 5.9258 and 10.8270. - 3001-06-01 is refused by the form (validity false) and the clock does not move. - Backwards at 1 d/s: -2.011 days in about 2 s. - The field's accessible name is "Date (UTC)" and its description is the window. Its colour-scheme is dark, so the picker icon shows on the HUD. - Back to now puts the field back on the present too. Unit suite 799 -> 805: two tests for the store, three for the dock, one for the scene note. Eleven guarded mutants; each changed its file and made its named test fail. Among them: the window check dropped, the wall clock not re-anchored on a jump, the radio dropping the direction, the field read as local time, and the note not naming the date. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -71,4 +71,32 @@ describe('TimeStore', () => {
|
||||
// Now, not the moment the store was built: five seconds of wall clock have passed.
|
||||
expect(time.date().toISOString()).toBe(new Date(START.getTime() + 5000).toISOString());
|
||||
});
|
||||
|
||||
it('jumps to a date and carries on from it at the rate it was running at', () => {
|
||||
vi.advanceTimersByTime(5000); // five seconds before the jump, which it must not add on
|
||||
expect(time.setDate(new Date('2020-12-21T18:00Z'))).toBe(true);
|
||||
// Away from now although still at real time: the rate never changed, the date did.
|
||||
expect(time.atNow()).toBe(false);
|
||||
vi.advanceTimersByTime(1000);
|
||||
expect(time.date().toISOString()).toBe('2020-12-21T18:00:01.000Z');
|
||||
|
||||
time.setRate(3600);
|
||||
vi.advanceTimersByTime(1000);
|
||||
expect(time.date().toISOString()).toBe('2020-12-21T19:00:01.000Z');
|
||||
});
|
||||
|
||||
it('refuses a date the planets’ elements were never fitted for, and stays where it was', () => {
|
||||
const before = time.date().toISOString();
|
||||
|
||||
expect(time.setDate(new Date('3000-01-01T00:01Z'))).toBe(false);
|
||||
expect(time.setDate(new Date(Date.UTC(-100, 0, 1)))).toBe(false); // 101 BC
|
||||
expect(time.setDate(new Date('not a date'))).toBe(false);
|
||||
expect(time.date().toISOString()).toBe(before);
|
||||
expect(time.atNow()).toBe(true);
|
||||
|
||||
// Both ends are in: the first day a date input can hold, and the end of Standish's fit.
|
||||
expect(time.setDate(new Date('0001-01-01T00:00Z'))).toBe(true);
|
||||
expect(time.date().toISOString()).toBe('0001-01-01T00:00:00.000Z');
|
||||
expect(time.setDate(new Date('3000-01-01T00:00Z'))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -25,6 +25,22 @@ export const TIME_RATES = [
|
||||
const MS_PER_DAY = 86_400_000;
|
||||
const JULIAN_DATE_AT_EPOCH = 2440587.5;
|
||||
|
||||
/**
|
||||
* The dates the clock can be set to, as `datetime-local` values read as UTC.
|
||||
*
|
||||
* The end is where Standish's Table 2, the mean elements that carry the planets, stops being
|
||||
* fitted: it covers 3000 BC to AD 3000, and every planet was within 0.29 degrees of Horizons at
|
||||
* each date measured out to 3000. The start is not the fit's but the date input's, which cannot
|
||||
* go before 0001-01-01. Both are proleptic Gregorian, as a `Date` is, so before 1582 they run
|
||||
* ahead of the Julian-calendar dates history gives: two days at AD 1, ten by 1582. The moons and
|
||||
* dwarf planets hold for far less of it: Phobos is 11 degrees out by 2100, Ceres 11.6 by 2200.
|
||||
*/
|
||||
export const CLOCK_WINDOW = { min: '0001-01-01T00:00', max: '3000-01-01T00:00' } as const;
|
||||
const WINDOW_MS = {
|
||||
min: Date.parse(`${CLOCK_WINDOW.min}Z`),
|
||||
max: Date.parse(`${CLOCK_WINDOW.max}Z`),
|
||||
};
|
||||
|
||||
/**
|
||||
* The date the map is drawn for.
|
||||
*
|
||||
@@ -33,7 +49,9 @@ const JULIAN_DATE_AT_EPOCH = 2440587.5;
|
||||
* watching. The rate *is* a signal, since a reader sets it and the controls read it back.
|
||||
*
|
||||
* Changing the rate re-anchors instead of rewinding: the date carries on from where it had got
|
||||
* to, so speeding up and slowing down never jumps the sky.
|
||||
* to, so speeding up and slowing down never jumps the sky. A negative rate runs the same clock
|
||||
* backwards: every orbit and every rotation is a function of the date, so going back is the same
|
||||
* sum with the sign turned.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class TimeStore {
|
||||
@@ -72,6 +90,23 @@ export class TimeStore {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Jumps the clock to a date, from which it carries on at whatever rate it was running at.
|
||||
* Refuses one outside {@link CLOCK_WINDOW}, rather than draw planets where elements that were
|
||||
* never fitted there put them.
|
||||
*/
|
||||
setDate(date: Date): boolean {
|
||||
const ms = date.getTime();
|
||||
// Written so that NaN, an unparsable field, fails it too.
|
||||
if (!(ms >= WINDOW_MS.min && ms <= WINDOW_MS.max)) {
|
||||
return false;
|
||||
}
|
||||
this.anchorJd = dateToJulianDate(date);
|
||||
this.anchorWallMs = Date.now();
|
||||
this.atNow.set(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Back to now, at real time — the state the map opens in. */
|
||||
reset(): void {
|
||||
this.anchorJd = dateToJulianDate();
|
||||
|
||||
Reference in New Issue
Block a user