Slow sim to 1× on landing and resume last selected speed (ISS-057, v0.30.0)
CI / build + unit + e2e (pull_request) Successful in 8m57s
CI / build + unit + e2e (pull_request) Successful in 8m57s
This commit is contained in:
@@ -170,16 +170,32 @@ describe('flyPlane', () => {
|
||||
expect(plane.fuel).toBe(PLANE_TYPES.atr72.maxFuel);
|
||||
});
|
||||
|
||||
it('clears paused on dispatch (ISS-041) — speed is left untouched', async () => {
|
||||
const inst = await freshDb('fly-unpause.db');
|
||||
// The last landing pauses the sim at whatever scale was in use.
|
||||
await inst.db.update(schema.simState).set({ paused: true, timeScale: 250 });
|
||||
it('resumes a manual pause and restores lastSelectedTimeScale when the last grounded plane takes off (ISS-057)', async () => {
|
||||
const inst = await freshDb('fly-resume.db');
|
||||
// A landing slowed the sim to 1×; the player's last manually selected speed was 250.
|
||||
await inst.db.update(schema.simState).set({ paused: true, timeScale: 1, lastSelectedTimeScale: 250 });
|
||||
|
||||
await flyPlane(inst, 'plane-starter', 'LEMD'); // sole grounded plane → airborne
|
||||
|
||||
const sim = (await inst.db.select().from(schema.simState))[0];
|
||||
expect(sim.paused).toBe(false); // dispatch still resumes a manual pause…
|
||||
expect(sim.timeScale).toBe(250); // …and restores the last selected speed
|
||||
});
|
||||
|
||||
it('keeps 1× when another plane is still grounded after dispatch (ISS-057)', async () => {
|
||||
const inst = await freshDb('fly-stayslow.db');
|
||||
await inst.db.update(schema.simState).set({ timeScale: 1, lastSelectedTimeScale: 250 });
|
||||
// A second grounded plane — the starter taking off leaves it on the ground.
|
||||
await inst.db.insert(schema.planes).values({
|
||||
id: 'plane-extra', model: 'atr72', status: 'available', fuel: 100, maxFuel: PLANE_TYPES.atr72.maxFuel,
|
||||
locationIcao: HUB_ICAO, destIcao: null, lat: 50, lon: 8, departureSimTime: null,
|
||||
});
|
||||
|
||||
await flyPlane(inst, 'plane-starter', 'LEMD');
|
||||
|
||||
const sim = (await inst.db.select().from(schema.simState))[0];
|
||||
expect(sim.paused).toBe(false); // dispatch auto-resumes…
|
||||
expect(sim.timeScale).toBe(250); // …with the stored speed preserved
|
||||
expect(sim.paused).toBe(false);
|
||||
expect(sim.timeScale).toBe(1); // another plane on ground → stays slowed
|
||||
});
|
||||
|
||||
it('rejects a flying plane with 409', async () => {
|
||||
|
||||
+8
-3
@@ -125,9 +125,14 @@ export async function flyPlane(inst: GameDb, planeId: string, destIcao: string):
|
||||
lon: origin.lon,
|
||||
})
|
||||
.where(eq(schema.planes.id, planeId));
|
||||
// ISS-041: starting a flight resumes a paused sim (the last landing pauses it). Runs inside
|
||||
// runAction(), so tick + unpause + dispatch are atomic under the sim lock.
|
||||
await inst.db.update(schema.simState).set({ paused: false });
|
||||
// ISS-057: dispatch still resumes a MANUALLY paused sim (pause is manual-only now — landings
|
||||
// slow to 1× instead of pausing), and restores the last manually selected speed when this
|
||||
// dispatch took the LAST grounded plane airborne. Runs inside runAction(), so tick + controls
|
||||
// + dispatch are atomic under the sim lock.
|
||||
const sets: Partial<Pick<schema.SimStateRow, 'paused' | 'timeScale'>> = { paused: false };
|
||||
const remaining = await inst.db.select().from(schema.planes).where(eq(schema.planes.status, 'available'));
|
||||
if (remaining.length === 0) sets.timeScale = sim.lastSelectedTimeScale;
|
||||
await inst.db.update(schema.simState).set(sets);
|
||||
await persist(inst);
|
||||
}
|
||||
|
||||
|
||||
@@ -105,8 +105,9 @@ describe('buildSeed', () => {
|
||||
it('seeds the starting account and sim state', () => {
|
||||
const seed = seedWith(42);
|
||||
expect(seed.account).toEqual({ cash: STARTING_CASH, reputation: STARTING_REPUTATION });
|
||||
// lastRealTickMs starts at the seed moment so the first tick-on-read advances ~0.
|
||||
expect(seed.simState).toEqual({ clockMs: NOW, timeScale: 1, paused: false, lastRealTickMs: NOW });
|
||||
// lastRealTickMs starts at the seed moment so the first tick-on-read advances ~0;
|
||||
// lastSelectedTimeScale mirrors the seeded speed (v0.30.0, ISS-057).
|
||||
expect(seed.simState).toEqual({ clockMs: NOW, timeScale: 1, paused: false, lastRealTickMs: NOW, lastSelectedTimeScale: 1 });
|
||||
});
|
||||
|
||||
it('is fully deterministic for a fixed RNG and inputs', () => {
|
||||
|
||||
+4
-2
@@ -62,7 +62,7 @@ export interface SeedData {
|
||||
plane: SeedPlane;
|
||||
packages: SeedPackage[];
|
||||
account: { cash: number; reputation: number };
|
||||
simState: { clockMs: number; timeScale: number; paused: false; lastRealTickMs: number };
|
||||
simState: { clockMs: number; timeScale: number; paused: false; lastRealTickMs: number; lastSelectedTimeScale: number };
|
||||
}
|
||||
|
||||
// --- Tunables (documented so future revenue/cost work can build on them) ---
|
||||
@@ -225,6 +225,8 @@ export function buildSeed(input: {
|
||||
account: { cash: STARTING_CASH, reputation: STARTING_REPUTATION },
|
||||
// lastRealTickMs starts at the seed moment so the first tick-on-read advances
|
||||
// by ~0 instead of treating "time since install" as elapsed play time.
|
||||
simState: { clockMs: nowMs, timeScale: 1, paused: false, lastRealTickMs: nowMs },
|
||||
// lastSelectedTimeScale (v0.30.0, ISS-057) mirrors the seeded speed — nothing has been
|
||||
// selected manually yet, so resume would restore 1× anyway.
|
||||
simState: { clockMs: nowMs, timeScale: 1, paused: false, lastRealTickMs: nowMs, lastSelectedTimeScale: 1 },
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user