SimulationClock

class SimulationClock
simulation/src/clock.ts:128

Members

constructor

constructor(options: { maxSteps?: number; startTick?: Tick }) → SimulationClock
options{ maxSteps?: number; startTick?: Tick }
Undocumented

achievedTimeScaleaccessor

achievedTimeScale() → number

The time scale actually being delivered. See ClockStatus.achievedTimeScale.

A getter beside the one in status() because the performance overlay reads it once per frame, and status() allocates an object to answer.

alphaaccessor

alpha() → number

Interpolation factor in [0, 1) between the last tick and the next.

pausedaccessor

paused() → boolean

renderTimeaccessor

renderTime() → number

The instant presentation is drawn at — one tick behind, plus the alpha.

Everything that places something in a frame must agree on this number, and it lives here because the clock owns both halves of it. It was previously written out only inside snapshot, so anything else that wanted "now" for presentation reached for time instead — which is the tick, a quantity that only moves in 1/64 s steps.

The gap between the two is at most one tick, which sounds harmless and is not. A body drawn at renderTime while the camera pointed at it is placed at time disagree by that body's velocity times up to 15.6 ms, and the gap sawtooths as alpha sweeps and resets — so the error is not a constant offset, it is a vibration at the beat between the frame rate and the tick rate. What that costs is the error in units of the thing's own radius, and Phobos and Deimos are 11.3 km and 6.2 km of radius carried around the Sun at 24 km/s: 400 m of it, which is 3.5% and 6.6% of their own radius. Framed in the planetarium they vibrated by 11 and 19 pixels while Mars, Luna and Io — three orders of magnitude larger against the same 400 m — held still inside a twentieth of a pixel.

terrainStreamer already had to learn this ("terrain that disagrees with the ship about what time it is drifts from under it"). This is the same mistake in the camera, and having one definition is what stops it being made a third time.

tickaccessor

tick() → Tick

timeaccessor

time() → number

timeScaleaccessor

timeScale() → number

commitTick

commitTick() → Tick

Called by the world once per completed tick.

commitTicks

commitTicks(count: number) → Tick

Called by the world once per jump over a coast: count ticks at once.

countnumber
Undocumented

plan

plan(realDelta: number) → FramePlan

Consume wall-clock time and say what this frame may do with it.

At 1× and below the stall guard is the whole story: a backgrounded minute comes back as eight ticks and a dropped count, whether the ship is coasting or not, so the world a player returns to is the one they left. Above 1× the player has asked for more simulation per frame, and wanted is what the frame bought at the requested rate over at most MAX_WARP_FRAME of it — never less than the integration budget, so a stall at 100× catches up exactly as it did when integration was the only way to run a tick.

realDeltanumber
Undocumented

renderTimeAt

renderTimeAt(alpha: number) → number

renderTime for an alpha the caller supplies. snapshot takes one.

alphanumber
Undocumented

restore

restore(tick: Tick) → void

Restore from a save. The accumulator is deliberately not persisted.

tickTick
Undocumented

setPaused

setPaused(paused: boolean) → void
pausedboolean
Undocumented

setTimeScale

setTimeScale(scale: number) → void

Time warp multiplies how many ticks a second of wall clock buys. The tick duration never changes, so warped time is bit-identical to real time run for longer — which is what makes a warped session replayable.

scalenumber
Undocumented

settle

settle(ran: number) → void

Record how many of the planned ticks actually ran, and drop the rest.

Ratio rather than a rate, so a frame that wanted one tick and ran one reports 1× and not 0.94× — the accumulator carries the remainder and a sampled rate would oscillate around the truth instead of stating it.

rannumber
Undocumented

status

status() → ClockStatus