Fixed world clock: no player time controls (ISS-117)
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.
This commit is contained in:
2026-09-22 10:50:21 +00:00
parent d31bc5d60f
commit a0560ec767
46 changed files with 631 additions and 1502 deletions
+10 -10
View File
@@ -10,7 +10,8 @@ import { REPAIR_COST } from '../lib/game/catalog';
// engine heals it gradually over the gate, landing on exactly 100 when the (hook-shortened) gate
// passes.
const SCALE = 1000; // fastest sim speed (v0.14.0 ladder top) — keeps flights to seconds of real time
const SCALE = 1000; // test rate for fast-forwarding legs — keeps flights to seconds of real time
const WORLD = 24; // the fixed world rate (ISS-117) — must match lib/worldClock.ts
const HUB = 'EDDF'; // must match HUB_ICAO in lib/game/seed.ts
const STARTER = 'plane-starter';
@@ -35,7 +36,7 @@ interface CatalogEntry {
interface StateBody {
account: { cash: number };
simState: { clockMs: number; timeScale: number; paused: boolean };
simState: { clockMs: number }; // ISS-117: the snapshot carries only the game clock
planes: PlaneState[];
catalog: CatalogEntry[];
}
@@ -46,13 +47,13 @@ async function getState(page: Page): Promise<StateBody> {
return (await res.json()) as StateBody;
}
// Set the time scale and VERIFY from the response that it stuck — a silent reset would otherwise
// surface as an unexplained wait timeout. Also clears pause (ISS-041: landings slow to 1×).
// Test hook (FLIGHT_TEST_HOOKS=1): set the world rate and VERIFY from the response that it stuck —
// a silent reset would otherwise surface as an unexplained wait timeout.
async function setScale(page: Page, scale: number): Promise<void> {
const res = await page.request.post('/api/sim', { data: { timeScale: scale, paused: false } });
const res = await page.request.post('/api/test/set-timescale', { data: { timeScale: scale } });
expect(res.status()).toBe(200);
const body = (await res.json()) as { timeScale: number };
expect(body.timeScale, `timeScale should be ${scale} after POST /api/sim`).toBe(scale);
expect(body.timeScale, `timeScale should be ${scale} after POST /api/test/set-timescale`).toBe(scale);
}
// Test hook (FLIGHT_TEST_HOOKS=1): force a plane's health and/or shorten an active repair gate.
@@ -125,7 +126,7 @@ async function healStarterHome(page: Page): Promise<void> {
starter = s.planes.find((p) => p.id === STARTER)!;
}
if (starter.locationIcao !== HUB) {
// ISS-041: the landing above slowed the sim to 1× — setScale restores it for the return leg.
// Make sure the world rate is back at SCALE for the return leg.
await setScale(page, SCALE);
// ISS-082: dispatch auto-refuels, so a grounded plane can always start any full-tank-flyable
// leg when cash allows — no top-up retry needed.
@@ -152,11 +153,10 @@ async function restoreStarter(page: Page): Promise<void> {
test.describe.configure({ mode: 'serial' }); // shared live DB — order matters within this file
// On exit (success or failure): restore scale=1 / unpaused for the other specs (house convention)
// On exit (success or failure): restore the fixed world rate for the other specs (house convention)
// and leave a clean starter — full health, no active repair gate.
test.afterEach(async ({ page }) => {
const res = await page.request.post('/api/sim', { data: { timeScale: 1, paused: false } });
expect(res.status()).toBe(200);
await setScale(page, WORLD);
await restoreStarter(page);
});