51 lines
1.5 KiB
TypeScript
51 lines
1.5 KiB
TypeScript
import type { RealTime } from "@deadhead/sim";
|
|
|
|
/** The engine's only source of time (CR-014): injected, so tests use a fixed one. */
|
|
export interface Clock {
|
|
now(): RealTime;
|
|
}
|
|
|
|
/**
|
|
* The device clock (decision 0.5.88): device time plus a dev-only offset,
|
|
* clamped so it never runs backward. Setting the device clock back leaves
|
|
* the game at the last time it saw; setting it forward is accepted.
|
|
*/
|
|
export class DeviceClock implements Clock {
|
|
#lastSeen: RealTime;
|
|
#offsetMs: number;
|
|
|
|
constructor(
|
|
/** Device time; Date.now outside tests. */
|
|
private readonly deviceNow: () => RealTime = () => Date.now(),
|
|
options: { lastTimeSeen?: RealTime; offsetMs?: number } = {},
|
|
) {
|
|
this.#lastSeen = options.lastTimeSeen ?? 0;
|
|
this.#offsetMs = options.offsetMs ?? 0;
|
|
}
|
|
|
|
now(): RealTime {
|
|
this.#lastSeen = Math.max(this.deviceNow() + this.#offsetMs, this.#lastSeen);
|
|
return this.#lastSeen;
|
|
}
|
|
|
|
/** The latest time handed out, stored in the save (CR-014 §2). */
|
|
get lastTimeSeen(): RealTime {
|
|
return this.#lastSeen;
|
|
}
|
|
|
|
/** After loading a save: never hand out a time before the one it last saw. */
|
|
restore(lastTimeSeen: RealTime): void {
|
|
this.#lastSeen = Math.max(this.#lastSeen, lastTimeSeen);
|
|
}
|
|
|
|
get offsetMs(): number {
|
|
return this.#offsetMs;
|
|
}
|
|
|
|
/** Developer tool: move the game forward (never back). */
|
|
advance(ms: number): void {
|
|
if (ms < 0) throw new RangeError("The clock only moves forward");
|
|
this.#offsetMs += ms;
|
|
}
|
|
}
|