v0.62.0 — Fixed 24x world clock (#135) #138

Merged
kamilkrzywda merged 1 commits from feat/fixed-world-clock into master 2026-09-23 10:24:07 +02:00
Owner

Closes #135

Finalizes the closed issue #117 spec: the world clock runs at the fixed WORLD_TIME_SCALE = 24 while any plane is flying or in repair, and stays frozen when the whole fleet is grounded. Replaces v0.56.0's idle slowdown (IDLE_WORLD_TIME_SCALE, ISS-128).

Production rate is exactly 24x regardless of legacy stored values

The live instance ran at ×1000 because pre-v0.52.0 saves hold timeScale: 1000 (the old UI ladder) — and the production compose set FLIGHT_TEST_HOOKS=1, so nothing ignored it. Now:

  • tickOnce in lib/sim/engine.ts: advanceMs = advance ? min(elapsed, MAX_TICK_ELAPSED_MS) × scale : 0 where scale = FLIGHT_TEST_HOOKS === '1' ? sim.timeScale : WORLD_TIME_SCALE. Production (env var unset) always advances at ×24 no matter what the save holds; only CI/test deployments honor the stored override written by POST /api/test/set-timescale (range [1, 100_000] — kept for tests per #117).
  • Separate finding + fix: production docker-compose.yml set FLIGHT_TEST_HOOKS: "1" — it enabled the /api/test/* hooks in prod AND made the live instance honor its stale stored 1000. Removed (with a comment explaining why it must stay out); e2e runs only in CI against docker-compose.ci.yml, which sets the var itself, so CI is unaffected.
  • No DB migration — per #117 saves reset when multiplayer ships; the env gate needs no schema change.

Freeze semantics (kept from first revision)

A frozen tick skips expiry/delivery/repair/spawn entirely and only updates lastRealTickMs, so resuming activity never fast-forwards the frozen span. The client mirror (advanceSimClock in lib/useSimClock.ts) holds its anchor value while grounded, so polls re-anchor without any jump. Removed: IDLE_WORLD_TIME_SCALE from lib/worldClock.ts. Unchanged on purpose: the ~1 s background tick (started at boot), the test hook endpoint, and the inert legacy columns paused/lastSelectedTimeScale (nothing reads them). The OpenAPI description of /api/state ("while no plane is flying or in repair the clock stays frozen") now matches the code.

UI time controls — already gone

The #117 spec's "remove ALL player-facing time controls" was completed in v0.52.0 (commit a0560ec): LeftPane pause button + scale slider and POST /api/sim were deleted then; no component renders any ladder/pause today (verified: full testid inventory of all components, no /api/sim route, prod v0.59.0 serves none), and e2e/panes.spec.ts:143-145 already pins their absence (pause-toggle, time-scale-slider → count 0). Nothing to remove in this PR; that e2e assertion is kept as the guard.

Tests

  • Unit: lib/sim/engine.test.ts — both rate branches pinned: with FLIGHT_TEST_HOOKS=1 stubbed (file default, like CI) stored overrides are honored; a new production test unsets the var and proves a legacy stored timeScale: 1000 still advances at exactly ×24. Grounded-fleet tests pin a FROZEN clock (no idle creep, resume-without-catch-up after takeoff, raised timeScale ignored while grounded); describe renamed to "grounded fleet freezes the world clock (#135)" and pins WORLD_TIME_SCALE = 24.
  • E2E: e2e/sim.spec.ts's idle test becomes an exact freeze (delta === 0 after ~2.5 s of idleness); e2e/state.spec.ts's restart test tightens from bounded delta to EXACT clock equality across docker compose restart; e2e/right-pane.spec.ts verifies the frozen clock, then asserts the abandon-penalty preview matches the server exactly (zero drift under a frozen clock — replaces the DRIFT_MS slope tolerance); e2e/events.spec.ts relaxes strict clock advance between SSE frames to non-decreasing (a grounded fleet pushes identical-clockMs frames; advance itself is pinned in sim.spec.ts). The activateWorld/deactivateWorld wrappers are unchanged — a repairing plane still means full scale.

Verification (local, per AGENTS.md)

Rebased onto master after v0.61.0 (#137) merged — no conflicts beyond the expected package.json/CHANGELOG version entries; no spec depends on the old 1119-airport dataset size.

  • npx tsc --noEmit — clean
  • npx vitest run — 386/386 passed (incl. v0.61.0's new airport tests)
  • npx playwright test --list — 62 tests in 16 files
Closes #135 Finalizes the closed issue #117 spec: the world clock runs at the fixed `WORLD_TIME_SCALE = 24` while any plane is flying or in repair, and stays **frozen** when the whole fleet is grounded. Replaces v0.56.0's idle slowdown (`IDLE_WORLD_TIME_SCALE`, ISS-128). ## Production rate is exactly 24x regardless of legacy stored values The live instance ran at ×1000 because pre-v0.52.0 saves hold `timeScale: 1000` (the old UI ladder) — and the production compose set `FLIGHT_TEST_HOOKS=1`, so nothing ignored it. Now: - `tickOnce` in `lib/sim/engine.ts`: `advanceMs = advance ? min(elapsed, MAX_TICK_ELAPSED_MS) × scale : 0` where **`scale = FLIGHT_TEST_HOOKS === '1' ? sim.timeScale : WORLD_TIME_SCALE`**. Production (env var unset) always advances at ×24 no matter what the save holds; only CI/test deployments honor the stored override written by `POST /api/test/set-timescale` (range [1, 100_000] — kept for tests per #117). - **Separate finding + fix:** production `docker-compose.yml` set `FLIGHT_TEST_HOOKS: "1"` — it enabled the `/api/test/*` hooks in prod AND made the live instance honor its stale stored 1000. Removed (with a comment explaining why it must stay out); e2e runs only in CI against `docker-compose.ci.yml`, which sets the var itself, so CI is unaffected. - No DB migration — per #117 saves reset when multiplayer ships; the env gate needs no schema change. ## Freeze semantics (kept from first revision) A frozen tick skips expiry/delivery/repair/spawn entirely and only updates `lastRealTickMs`, so resuming activity never fast-forwards the frozen span. The client mirror (`advanceSimClock` in `lib/useSimClock.ts`) holds its anchor value while grounded, so polls re-anchor without any jump. Removed: `IDLE_WORLD_TIME_SCALE` from `lib/worldClock.ts`. Unchanged on purpose: the ~1 s background tick (started at boot), the test hook endpoint, and the inert legacy columns `paused`/`lastSelectedTimeScale` (nothing reads them). The OpenAPI description of `/api/state` ("while no plane is flying or in repair the clock stays frozen") now matches the code. ## UI time controls — already gone The #117 spec's "remove ALL player-facing time controls" was completed in v0.52.0 (commit a0560ec): LeftPane pause button + scale slider and `POST /api/sim` were deleted then; no component renders any ladder/pause today (verified: full testid inventory of all components, no `/api/sim` route, prod v0.59.0 serves none), and `e2e/panes.spec.ts:143-145` already pins their absence (`pause-toggle`, `time-scale-slider` → count 0). Nothing to remove in this PR; that e2e assertion is kept as the guard. ## Tests - Unit: `lib/sim/engine.test.ts` — both rate branches pinned: with `FLIGHT_TEST_HOOKS=1` stubbed (file default, like CI) stored overrides are honored; a new production test unsets the var and proves a legacy stored `timeScale: 1000` still advances at exactly ×24. Grounded-fleet tests pin a FROZEN clock (no idle creep, resume-without-catch-up after takeoff, raised timeScale ignored while grounded); describe renamed to "grounded fleet freezes the world clock (#135)" and pins `WORLD_TIME_SCALE = 24`. - E2E: `e2e/sim.spec.ts`'s idle test becomes an exact freeze (delta === 0 after ~2.5 s of idleness); `e2e/state.spec.ts`'s restart test tightens from bounded delta to EXACT clock equality across `docker compose restart`; `e2e/right-pane.spec.ts` verifies the frozen clock, then asserts the abandon-penalty preview matches the server exactly (zero drift under a frozen clock — replaces the DRIFT_MS slope tolerance); `e2e/events.spec.ts` relaxes strict clock advance between SSE frames to non-decreasing (a grounded fleet pushes identical-clockMs frames; advance itself is pinned in sim.spec.ts). The activateWorld/deactivateWorld wrappers are unchanged — a repairing plane still means full scale. ## Verification (local, per AGENTS.md) Rebased onto master after v0.61.0 (#137) merged — no conflicts beyond the expected package.json/CHANGELOG version entries; no spec depends on the old 1119-airport dataset size. - `npx tsc --noEmit` — clean - `npx vitest run` — 386/386 passed (incl. v0.61.0's new airport tests) - `npx playwright test --list` — 62 tests in 16 files
kamilkrzywda scheduled this pull request to auto merge when all checks succeed 2026-09-23 09:42:10 +02:00
kamilkrzywda force-pushed feat/fixed-world-clock from 65226a9375 to 71dea3ffb8 2026-09-23 10:02:17 +02:00 Compare
kamilkrzywda added 1 commit 2026-09-23 10:09:58 +02:00
v0.62.0: fixed 24x world clock — frozen when fully grounded (#135)
CI / build + unit + e2e (pull_request) Successful in 14m3s
bb4e3b96f3
kamilkrzywda force-pushed feat/fixed-world-clock from 71dea3ffb8 to bb4e3b96f3 2026-09-23 10:09:58 +02:00 Compare
kamilkrzywda merged commit 673365f49c into master 2026-09-23 10:24:07 +02:00
kamilkrzywda deleted branch feat/fixed-world-clock 2026-09-23 10:24:07 +02:00
Sign in to join this conversation.