feat: v0.14.0 — ISS-006 time-scale slider values up to 1000x; tests use fastest time
CI / build + unit + e2e (pull_request) Failing after 4m52s
CI / build + unit + e2e (pull_request) Failing after 4m52s
This commit is contained in:
@@ -11,9 +11,12 @@ import { PLANE_TYPES } from '@/lib/game/catalog';
|
||||
import { MARKET_TARGET_OPEN_PACKAGES, mulberry32 } from '@/lib/game/seed';
|
||||
import { arrivalTimeFor } from './movement';
|
||||
import {
|
||||
clampTimeScale,
|
||||
deliverPackages,
|
||||
expirePackages,
|
||||
MAX_TICK_ELAPSED_MS,
|
||||
MAX_TIME_SCALE,
|
||||
MIN_TIME_SCALE,
|
||||
reputationGainFor,
|
||||
spawnCoveragePackages,
|
||||
spawnOpenPackages,
|
||||
@@ -374,6 +377,32 @@ describe('spawnCoveragePackages (v0.8.0 spawn coverage rule)', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('clampTimeScale — v0.14.0 ladder bounds [1, 1000]', () => {
|
||||
it('clamps below-minimum values to MIN_TIME_SCALE', () => {
|
||||
expect(clampTimeScale(0)).toBe(MIN_TIME_SCALE);
|
||||
expect(clampTimeScale(-5)).toBe(MIN_TIME_SCALE);
|
||||
expect(clampTimeScale(0.1)).toBe(MIN_TIME_SCALE);
|
||||
});
|
||||
|
||||
it('clamps above-maximum values to MAX_TIME_SCALE', () => {
|
||||
expect(clampTimeScale(1001)).toBe(MAX_TIME_SCALE);
|
||||
expect(clampTimeScale(2000)).toBe(MAX_TIME_SCALE);
|
||||
expect(clampTimeScale(Number.MAX_SAFE_INTEGER)).toBe(MAX_TIME_SCALE);
|
||||
});
|
||||
|
||||
it('passes in-range values through unclamped — ladder and off-ladder alike', () => {
|
||||
for (const s of [1, 2, 5, 10, 25, 50, 100, 250, 500, 1000]) {
|
||||
expect(clampTimeScale(s)).toBe(s); // the full v0.14.0 slider ladder
|
||||
}
|
||||
expect(clampTimeScale(37)).toBe(37); // off-ladder but in range (e.g. a legacy value)
|
||||
});
|
||||
|
||||
it('keeps the endpoints exact', () => {
|
||||
expect(clampTimeScale(MIN_TIME_SCALE)).toBe(MIN_TIME_SCALE);
|
||||
expect(clampTimeScale(MAX_TIME_SCALE)).toBe(MAX_TIME_SCALE);
|
||||
});
|
||||
});
|
||||
|
||||
// Local helper mirroring lib/sim/movement.ts (kept private there on purpose — the
|
||||
// engine composes arrivalTimeFor; tests just need the duration for fixtures).
|
||||
function travelMs(km: number, speedKmh: number): number {
|
||||
|
||||
+3
-3
@@ -22,12 +22,12 @@ import { arrivalTimeFor, fuelAt } from './movement';
|
||||
// Clamp constant: max real-time gap counted by a single tick. Chosen at 2000 ms —
|
||||
// comfortably above a normal client poll interval (~1 s), so active play advances
|
||||
// ~real time × scale; small enough that one stale read after an absence moves game
|
||||
// time by at most 2 s × scale (≈ 2 min even at max scale 64). The sim therefore
|
||||
// time by at most 2 s × scale (≈ 33 min even at max scale 1000). The sim therefore
|
||||
// only meaningfully progresses while actively polled.
|
||||
export const MAX_TICK_ELAPSED_MS = 2_000;
|
||||
|
||||
export const MIN_TIME_SCALE = 1;
|
||||
export const MAX_TIME_SCALE = 64; // top of the power-of-two ladder (v0.9.2)
|
||||
export const MAX_TIME_SCALE = 1000; // top of the slider ladder (v0.14.0)
|
||||
|
||||
// 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
|
||||
@@ -264,7 +264,7 @@ export function runAction<T>(fn: (inst: GameDb) => Promise<T>): Promise<T> {
|
||||
}
|
||||
|
||||
// Tick first so the returned state reflects current time, then apply the new
|
||||
// controls. timeScale is clamped to [1, 64]; validation of raw input happens in
|
||||
// controls. timeScale is clamped to [1, 1000]; validation of raw input happens in
|
||||
// the route handler (400 on non-number / non-boolean).
|
||||
export function applyTimeControls(input: SimControlsInput): Promise<schema.SimStateRow> {
|
||||
return withLock(async () => {
|
||||
|
||||
Reference in New Issue
Block a user