idle fleet slows world clock to real time instead of freezing (ISS-128)
CI / build + unit + e2e (pull_request) Successful in 10m32s
CI / build + unit + e2e (pull_request) Successful in 10m32s
This commit is contained in:
+16
-9
@@ -8,6 +8,7 @@ import { weatherFor } from '@/lib/weather';
|
||||
import { FUEL_DISCOUNT_PER_REP, FUEL_PRICE_PER_L, MAX_FUEL_DISCOUNT, PLANE_TYPES, REPAIR_DURATION_MS, repRate, WEAR_PER_KM } from '@/lib/game/catalog';
|
||||
import { SPECIALS } from '@/lib/game/specials';
|
||||
import { parseIcaoQueue } from '@/lib/game/state';
|
||||
import { IDLE_WORLD_TIME_SCALE } from '@/lib/worldClock';
|
||||
import {
|
||||
generatePackage,
|
||||
MARKET_TARGET_OPEN_PACKAGES,
|
||||
@@ -22,7 +23,8 @@ import { arrivalTimeFor, fuelAtWeighted, requiredFuelFor, windBurnFactor } from
|
||||
// tick (~1 s, started at boot from instrumentation.ts) AND on every read/action — both through the
|
||||
// same locked tickOnce path. Each tick advances by (real ms since last tick) × timeScale, clamped
|
||||
// per tick so long absences never fast-forward the game; while no plane is flying or in repair the
|
||||
// clock stays frozen (idle-pause) but lastRealTickMs keeps tracking real time.
|
||||
// world slows to real time instead of freezing (IDLE_WORLD_TIME_SCALE, ISS-128 v0.56.0), and
|
||||
// lastRealTickMs always keeps tracking real time.
|
||||
|
||||
// Clamp constant: max real-time gap counted by a single tick. Chosen at 2000 ms — comfortably above
|
||||
// both the client poll interval (~1 s) and the background-tick interval, so active play advances
|
||||
@@ -30,8 +32,9 @@ import { arrivalTimeFor, fuelAtWeighted, requiredFuelFor, windBurnFactor } from
|
||||
// 2 s × scale.
|
||||
export const MAX_TICK_ELAPSED_MS = 2_000;
|
||||
|
||||
// ISS-117: the fixed world rate — re-exported here so server code and tests have one familiar home.
|
||||
export { WORLD_TIME_SCALE } from '@/lib/worldClock';
|
||||
// ISS-117 / ISS-128: the fixed world rates — re-exported here so server code and tests have one
|
||||
// familiar home (full rate while something is active, real-time rate while fully grounded).
|
||||
export { IDLE_WORLD_TIME_SCALE, WORLD_TIME_SCALE } from '@/lib/worldClock';
|
||||
|
||||
// Reputation gain per delivered package: BASE + PER_KM × great-circle km, rounded
|
||||
// to 2 decimals (reputation is REAL). A short hop (~300 km) earns ~1.15 rep, a
|
||||
@@ -58,9 +61,10 @@ function withLock<T>(fn: () => Promise<T>): Promise<T> {
|
||||
|
||||
// --- Tick core (takes a GameDb so unit tests can inject their own instance) ---
|
||||
|
||||
// ISS-117: idle-pause decision — pure, so unit tests feed it synthetic fleets. The world clock
|
||||
// advances only while something is happening: at least one plane flying OR in repair. A fully
|
||||
// grounded fleet freezes the clock (and with it expiries/spawns) until a dispatch or repair starts.
|
||||
// ISS-117 / ISS-128: idle decision — pure, so unit tests feed it synthetic fleets. The world clock
|
||||
// runs at full scale only while something is happening (at least one plane flying OR in repair); a
|
||||
// fully grounded fleet slows it to IDLE_WORLD_TIME_SCALE (real time) instead of freezing it, so
|
||||
// expiries/spawns keep running until a dispatch or repair starts.
|
||||
export function shouldAdvanceTime(planes: Array<{ status: string }>): { advance: boolean; flying: number; repairing: number } {
|
||||
let flying = 0;
|
||||
let repairing = 0;
|
||||
@@ -77,12 +81,15 @@ export async function tickOnce(inst: GameDb): Promise<schema.SimStateRow> {
|
||||
if (!sim) throw new Error('sim_state row missing');
|
||||
|
||||
const nowMs = Date.now();
|
||||
// ISS-117: frozen while nothing is happening (see shouldAdvanceTime). lastRealTickMs still
|
||||
// tracks real time, so a takeoff resumes cleanly with no catch-up jump for the idle duration.
|
||||
// ISS-128: a fully grounded fleet slows the world to real time (IDLE_WORLD_TIME_SCALE) instead of
|
||||
// freezing it — packages keep spawning/expiring while idle. An active fleet keeps its full scale,
|
||||
// including any FLIGHT_TEST_HOOKS override in sim.timeScale. lastRealTickMs always tracks real
|
||||
// time, so resuming activity never fast-forwards the idle duration.
|
||||
const planeRows = await inst.db.select().from(schema.planes);
|
||||
const { advance } = shouldAdvanceTime(planeRows.map((p) => ({ status: p.status })));
|
||||
const elapsed = Math.max(0, nowMs - sim.lastRealTickMs);
|
||||
const advanceMs = advance ? Math.min(elapsed, MAX_TICK_ELAPSED_MS) * sim.timeScale : 0;
|
||||
const scale = advance ? sim.timeScale : IDLE_WORLD_TIME_SCALE;
|
||||
const advanceMs = Math.min(elapsed, MAX_TICK_ELAPSED_MS) * scale;
|
||||
const clockMs = Math.round(sim.clockMs + advanceMs);
|
||||
|
||||
if (advanceMs > 0) {
|
||||
|
||||
Reference in New Issue
Block a user