v0.62.0: fixed 24x world clock — frozen when fully grounded (#135)
CI / build + unit + e2e (pull_request) Successful in 14m3s

This commit is contained in:
2026-09-23 08:09:29 +00:00
parent e970f95036
commit bb4e3b96f3
14 changed files with 122 additions and 124 deletions
+6 -16
View File
@@ -8,9 +8,6 @@ import { test, expect } from '@playwright/test';
const repoRoot = path.join(__dirname, '..');
const composeArgs = process.env.E2E_COMPOSE_ARGS ?? '';
// Must match lib/sim/engine.ts — kept in sync manually (same pattern as e2e/sim.spec.ts).
const MAX_TICK_ELAPSED_MS = 2_000;
interface StateBody {
account: { cash: number; reputation: number };
simState: { clockMs: number }; // ISS-117: the snapshot carries only the game clock
@@ -45,9 +42,8 @@ test('GET /api/state returns a seeded snapshot', async ({ page }) => {
test('state survives a container restart (sim clock + account persisted)', async ({ page }) => {
test.setTimeout(180_000);
// Self-heal to a fully grounded fleet first — while idle the clock advances at real time
// (ISS-128), so the post-restart snapshot is bounded by one clamped tick of downtime plus live
// ticks, not byte-for-byte identical.
// Self-heal to a fully grounded fleet first — a grounded fleet freezes the world clock (#135),
// so no tick advances it across the restart and the post-restart clock is exactly unchanged.
await expect
.poll(async () => {
const res = await page.request.get('/api/state');
@@ -60,7 +56,6 @@ test('state survives a container restart (sim clock + account persisted)', async
const resBefore = await page.request.get('/api/state');
expect(resBefore.status()).toBe(200);
const before = (await resBefore.json()) as StateBody;
const tBefore = Date.now();
execSync(`docker compose ${composeArgs} restart flight`, { cwd: repoRoot, stdio: 'pipe' });
@@ -80,17 +75,12 @@ test('state survives a container restart (sim clock + account persisted)', async
expect(after).toBeDefined();
const a = after!;
const tAfter = Date.now();
// The DB file on the volume must be loaded, not re-seeded (PLAN.md: "sim clock persisted across
// sessions"): account and entity ids are byte-identical. With the fleet grounded above, ISS-128
// advances the clock at real time while idle — bounded tolerance instead of exact equality: no
// ticks run while the container is down, so the first post-restart tick counts the downtime
// clamped to MAX_TICK_ELAPSED_MS × IDLE rate (= 1), and every other counted gap lies inside the
// live wall span [tBefore, tAfter] at that same rate. A re-seed would reset clockMs backwards.
const delta = a.simState.clockMs - before.simState.clockMs;
expect(delta).toBeGreaterThanOrEqual(0); // never goes backwards — not re-seeded
expect(delta).toBeLessThanOrEqual(MAX_TICK_ELAPSED_MS + (tAfter - tBefore));
// sessions"): account and entity ids are byte-identical. With the fleet grounded above, the world
// clock is FROZEN (#135) — no tick advances it while the container is down or after restart, so
// the post-restart clock is EXACTLY unchanged; a re-seed would reset clockMs backwards instead.
expect(a.simState.clockMs - before.simState.clockMs).toBe(0);
expect(a.account.cash).toBe(before.account.cash);
expect(a.account.reputation).toBe(before.account.reputation);
expect(a.planes.map((p) => p.id).sort()).toEqual(before.planes.map((p) => p.id).sort());