Fixed world clock: no player time controls (ISS-117)
CI / build + unit + e2e (pull_request) Failing after 12m10s
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:
+21
-26
@@ -11,7 +11,8 @@ import { test, expect, type Page } from '@playwright/test';
|
||||
// cargo rows and the Relocate section; the relocate preview rings only its TARGET airport, which
|
||||
// additionally gets a distinct green ring in `airports-target-highlight` (ISS-022).
|
||||
|
||||
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
|
||||
// Madrid–Barajas — same long leg as e2e/map-interactions.spec.ts: ~1420 km → ~10 s at scale 1000.
|
||||
const DEST = 'LEMD';
|
||||
@@ -41,7 +42,7 @@ interface PackageState {
|
||||
|
||||
interface StateBody {
|
||||
account: { cash: number; reputation: number };
|
||||
simState: { clockMs: number; timeScale: number; paused: boolean };
|
||||
simState: { clockMs: number }; // ISS-117: the snapshot carries only the game clock
|
||||
planes: PlaneState[];
|
||||
packages: PackageState[];
|
||||
}
|
||||
@@ -52,24 +53,18 @@ async function getState(page: Page): Promise<StateBody> {
|
||||
return (await res.json()) as StateBody;
|
||||
}
|
||||
|
||||
async function postSim(page: Page, body: unknown) {
|
||||
return page.request.post('/api/sim', { data: body });
|
||||
}
|
||||
|
||||
// Set the time scale and VERIFY from the response that it stuck — a silent reset would
|
||||
// otherwise surface as an unexplained landing-wait timeout. ISS-041 also clears pause: a
|
||||
// landing pauses the sim, so any fast-forward after one must resume it or the clock stays frozen.
|
||||
// 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 landing-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);
|
||||
}
|
||||
|
||||
// Leave the live instance in a sane state for the other specs (scale 1, running).
|
||||
// Leave the live instance in a sane state for the other specs (the fixed world rate).
|
||||
test.afterEach(async ({ page }) => {
|
||||
const res = await postSim(page, { timeScale: 1, paused: false });
|
||||
expect(res.status()).toBe(200);
|
||||
await setScale(page, WORLD);
|
||||
});
|
||||
|
||||
// Test hook (FLIGHT_TEST_HOOKS=1): create an open package on a known route.
|
||||
@@ -106,8 +101,7 @@ async function flyHomeViaApi(page: Page): Promise<void> {
|
||||
.toBe(true);
|
||||
}
|
||||
if (starter.locationIcao !== HUB) {
|
||||
// ISS-041: the landing above paused the sim — setScale clears pause so the return leg
|
||||
// fast-forwards instead of taking real-time hours.
|
||||
// Make sure the world rate is back at SCALE so the return leg fast-forwards.
|
||||
await setScale(page, SCALE);
|
||||
const res = await page.request.post('/api/planes/plane-starter/fly', { data: { destIcao: HUB } });
|
||||
expect(res.status()).toBe(200); // repositioning needs no package (v0.12.0 manual flight)
|
||||
@@ -472,8 +466,9 @@ test('hovering the fleet row of a flying plane highlights its route', async ({ p
|
||||
await expect(block).toContainText(/in flight/i, { timeout: 15_000 });
|
||||
|
||||
// Freeze mid-flight so the ~10 s leg can't land under us while we assert (same pattern as
|
||||
// e2e/map-interactions.spec.ts' marker click): the route line stays drawn while paused.
|
||||
await postSim(page, { paused: true });
|
||||
// e2e/map-interactions.spec.ts' marker click): at scale 1 the leg takes minutes of real time, and
|
||||
// the route line stays drawn while frozen.
|
||||
await setScale(page, 1);
|
||||
|
||||
await block.hover();
|
||||
await expect.poll(async () => {
|
||||
@@ -489,7 +484,7 @@ test('hovering the fleet row of a flying plane highlights its route', async ({ p
|
||||
|
||||
// Resume and fast-forward to arrival, then fly home so the live instance stays in its
|
||||
// onboarding state (starter grounded at EDDF) for the other specs.
|
||||
await postSim(page, { timeScale: SCALE, paused: false });
|
||||
await setScale(page, SCALE);
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const p = (await getState(page)).planes.find((x) => x.id === 'plane-starter');
|
||||
@@ -497,7 +492,7 @@ test('hovering the fleet row of a flying plane highlights its route', async ({ p
|
||||
}, { timeout: 120_000, message: `waiting for plane-starter to land at ${DEST}` })
|
||||
.toBe(true);
|
||||
|
||||
// ISS-041: the arrival above paused the sim, so re-set SCALE for this leg (setScale clears pause).
|
||||
// Re-set SCALE for the return leg.
|
||||
await setScale(page, SCALE);
|
||||
const home = await page.request.post('/api/planes/plane-starter/fly', { data: { destIcao: HUB } });
|
||||
expect(home.status()).toBe(200);
|
||||
@@ -535,10 +530,10 @@ test('hovering an on-board package while its carrier is mid-flight traces from t
|
||||
expect(flyBody.planes.find((p) => p.id === 'plane-starter')?.status).toBe('flying');
|
||||
|
||||
// Freeze immediately: at a leftover scale of 1000 the ~10 s leg could otherwise land under us and
|
||||
// auto-chain the queued EHAM leg, which would change (or empty) the trace while we assert. Paused,
|
||||
// every /api/state snapshot reports the same state — and the new rule's start point is the LANDING
|
||||
// airport regardless of progress, so no mid-flight position window is needed at all.
|
||||
await postSim(page, { paused: true });
|
||||
// auto-chain the queued EHAM leg, which would change (or empty) the trace while we assert. At
|
||||
// scale 1 every /api/state snapshot reports the same state for minutes — and the new rule's start
|
||||
// point is the LANDING airport regardless of progress, so no mid-flight position window is needed at all.
|
||||
await setScale(page, 1);
|
||||
|
||||
const row = page.locator(`[data-testid="onboard-package-${pkg.id}"]`);
|
||||
await expect(row, 'the loaded package appears in the On board section').toBeVisible({ timeout: 15_000 });
|
||||
@@ -584,7 +579,7 @@ test('hovering an on-board package while its carrier is mid-flight traces from t
|
||||
// not DEST — it never delivered), then fly home.
|
||||
const cancelRes = await page.request.post('/api/planes/plane-starter/queue/cancel', { data: { index: 0 } });
|
||||
expect(cancelRes.status()).toBe(200);
|
||||
await setScale(page, SCALE); // also clears the pause above
|
||||
await setScale(page, SCALE);
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const p = (await getState(page)).planes.find((x) => x.id === 'plane-starter');
|
||||
@@ -595,7 +590,7 @@ test('hovering an on-board package while its carrier is mid-flight traces from t
|
||||
const abandon = await page.request.post(`/api/packages/${pkg.id}/abandon`);
|
||||
expect(abandon.status()).toBe(200); // only possible once grounded — the poll above guarantees it
|
||||
|
||||
// ISS-041: the arrival above paused the sim, so re-set SCALE for this leg (setScale clears pause).
|
||||
// Re-set SCALE for the return leg.
|
||||
await setScale(page, SCALE);
|
||||
const home = await page.request.post('/api/planes/plane-starter/fly', { data: { destIcao: HUB } });
|
||||
expect(home.status()).toBe(200);
|
||||
|
||||
Reference in New Issue
Block a user