Wind-adjusted fuel burn ±20% + refuel-for-flight buys 150% margin (ISS-070, ISS-071, v0.36.0)
CI / build + unit + e2e (pull_request) Successful in 10m34s

This commit is contained in:
2026-09-21 08:47:42 +00:00
parent 6d8e1ad35e
commit 5030fcd8b4
16 changed files with 309 additions and 81 deletions
+16 -6
View File
@@ -6,8 +6,9 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import * as schema from '@/lib/db/schema';
import { getGameDb } from '@/lib/db/client';
import { loadAirportsGeoJson } from '@/lib/airports';
import { haversineKm } from '@/lib/geo';
import { requiredFuelFor } from '@/lib/sim/movement';
import { haversineKm, initialBearingDeg } from '@/lib/geo';
import { weatherFor } from '@/lib/weather';
import { requiredFuelFor, windBurnFactor } from '@/lib/sim/movement';
import { FUEL_PRICE_PER_L, PLANE_TYPES } from '@/lib/game/catalog';
import { HUB_ICAO } from '@/lib/game/seed';
import { POST } from './route';
@@ -124,7 +125,7 @@ describe('POST /api/planes/:planeId/refuel', () => {
expect(res.status).toBe(400);
});
it('forFlight tops up exactly the liters needed for the loaded package leg', async () => {
it('forFlight buys 150% of the weather-adjusted leg requirement', async () => {
const { db } = await getGameDb();
await db.update(schema.account).set({ cash: 50_000 });
// A loaded EDDF → LEMD package on the starter (direct row — loadPackage is covered in actions tests).
@@ -138,7 +139,14 @@ describe('POST /api/planes/:planeId/refuel', () => {
const to = fc.features.find((f) => f.properties.icao === 'LEMD')!.properties;
const km = haversineKm(from.lat, from.lon, to.lat, to.lon);
const type = PLANE_TYPES.atr72;
const required = requiredFuelFor(type.burnPerKm, type.emptyWeightKg, type.maxFuel, km);
// Same wind-adjusted requirement the server computes (v0.36.0).
const w = weatherFor(HUB_ICAO);
const required = requiredFuelFor(
type.burnPerKm * windBurnFactor(w.windKt, w.windDirDeg, initialBearingDeg(from.lat, from.lon, to.lat, to.lon)),
type.emptyWeightKg,
type.maxFuel,
km,
);
const startFuel = Math.round(required / 2); // partial tank so L > 0
await db.update(schema.planes).set({ fuel: startFuel }).where(eq(schema.planes.id, 'plane-starter'));
@@ -151,9 +159,11 @@ describe('POST /api/planes/:planeId/refuel', () => {
account: { cash: number };
planes: Array<{ id: string; fuel: number }>;
};
const cost = Math.round((required - startFuel) * FUEL_PRICE_PER_L * 100) / 100;
// ISS-071: target is 150% of the requirement, capped at tank capacity.
const targetFuel = Math.min(type.maxFuel, Math.max(startFuel, required * 1.5));
const cost = Math.round((targetFuel - startFuel) * FUEL_PRICE_PER_L * 100) / 100;
expect(body.account.cash).toBeCloseTo(cashBefore - cost, 6);
expect(body.planes.find((p) => p.id === 'plane-starter')!.fuel).toBeCloseTo(required, 6);
expect(body.planes.find((p) => p.id === 'plane-starter')!.fuel).toBeCloseTo(targetFuel, 6);
// Clean up the test package.
await db.delete(schema.packages).where(eq(schema.packages.id, 'p-ff-api'));