CI / build + unit + e2e (pull_request) Failing after 12m10s
The world always runs at WORLD_TIME_SCALE = 24 (lib/worldClock.ts). A server
background tick (instrumentation.ts -> startBackgroundTick, 1 s interval)
advances the sim with no open browser; idle-pause freezes the clock exactly
while no plane is flying or in repair. Fresh worlds start year 2000
(STARTING_CLOCK_MS in seed.ts).
- Removed: POST /api/sim + OpenAPI entry, applyTimeControls/clampTimeScale,
lib/timeScale.ts, LeftPane pause button + scale slider machinery,
ISS-057 slow-on-landing/resume in flyPlane.
- Snapshot simState is now { clockMs } only; client extrapolates at the fixed
rate between polls and freezes while idle (useSimClock).
- New test-only hook POST /api/test/set-timescale behind FLIGHT_TEST_HOOKS=1
(sibling of set-health); all e2e setScale helpers call it, afterEach restores
the world rate. E2E pause tests replaced by idle-pause assertions; sim.spec.ts
activates the world via set-health + /repair for advance/expiry tests.
46 lines
2.2 KiB
TypeScript
46 lines
2.2 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
||
import { advanceSimClock, type SimClockAnchor } from './useSimClock';
|
||
import { WORLD_TIME_SCALE } from './worldClock';
|
||
|
||
// ISS-007 (v0.16.1) — the pure extrapolation core of lib/useSimClock.ts. The hook itself is a
|
||
// thin setInterval + re-sync wrapper covered by e2e (panes.spec.ts "ticks smoothly between polls");
|
||
// this suite pins the math: game time advances real-time × WORLD_TIME_SCALE, frozen while idle.
|
||
|
||
const anchor = (over: Partial<SimClockAnchor> = {}): SimClockAnchor => ({
|
||
clockMs: 1_750_000_000_000,
|
||
at: 1_000_000,
|
||
advancing: true,
|
||
...over,
|
||
});
|
||
|
||
describe('advanceSimClock', () => {
|
||
it('advances by exactly elapsed real-time × WORLD_TIME_SCALE while running', () => {
|
||
expect(advanceSimClock(anchor(), 1_001_000)).toBe(1_750_000_000_000 + 1_000 * WORLD_TIME_SCALE);
|
||
// The e2e scenario: ~2.6 s real at the fixed world rate.
|
||
expect(advanceSimClock(anchor(), 1_002_600)).toBe(1_750_000_000_000 + 2_600 * WORLD_TIME_SCALE);
|
||
});
|
||
|
||
it('returns the anchor value exactly at zero elapsed', () => {
|
||
expect(advanceSimClock(anchor(), 1_000_000)).toBe(1_750_000_000_000);
|
||
});
|
||
|
||
it('is frozen while idle regardless of elapsed time', () => {
|
||
const a = anchor({ advancing: false });
|
||
expect(advanceSimClock(a, 1_000_000)).toBe(1_750_000_000_000);
|
||
expect(advanceSimClock(a, 2_000_000)).toBe(1_750_000_000_000); // a full second later — still frozen
|
||
});
|
||
|
||
it('clamps to the anchor value when nowMs is before the anchor (clock skew), never backwards', () => {
|
||
expect(advanceSimClock(anchor(), 999_999)).toBe(1_750_000_000_000);
|
||
expect(advanceSimClock(anchor({ advancing: false }), 999_999)).toBe(1_750_000_000_000);
|
||
});
|
||
|
||
it('mirrors the server tick model (tickOnce): advance = elapsed × WORLD_TIME_SCALE while actively polled', () => {
|
||
// lib/sim/engine.ts: clockMs += min(now − lastRealTickMs, MAX_TICK_ELAPSED_MS) × timeScale.
|
||
// For gaps under the 2 s clamp the client extrapolation is exactly the server's own formula.
|
||
for (const gap of [1, 250, 999, 1_999]) {
|
||
expect(advanceSimClock(anchor(), 1_000_000 + gap)).toBe(1_750_000_000_000 + gap * WORLD_TIME_SCALE);
|
||
}
|
||
});
|
||
});
|