CI / build + unit + e2e (pull_request) Failing after 5m28s
Part A: r(F) = k(We + rho*F) L/km, k calibrated so a full tank burns exactly
the old constant; closed forms in lib/sim/movement.ts (fuelAtWeighted,
requiredFuelFor, burnRateConstant, FUEL_DENSITY_KG_PER_L). Catalog gains
emptyWeightKg (ATR72 OEW 9300 kg), exposed in the snapshot. All three burn
call sites (engine landing, state live fuel, fly validation) moved to the
weighted model; old constant-rate fuelAt removed.
Part B: POST /api/planes/{planeId}/refuel takes an optional body
{mode:'full'|'forFlight', packageId} — no body keeps today's fill-to-full
behavior exactly (fuel lands bit-exact on maxFuel); forFlight adds just
enough for the loaded package's destination leg (409 not-loaded/expired/
doesn't-fit, 400 bad body). UI: per-cargo-row 'Refuel' button
(refuel-for-{pkg.id}), market button renamed 'Refuel to full'.
OpenAPI: RefuelRequest schema + optional requestBody + 400 on refuel, fly's
409 description updated, CatalogEntry.emptyWeightKg. Tests: new weighted-burn
suite, forFlight action/route cases (incl. ~7670 km UNNT<->LPPD doesn't-fit),
openapi guards; landing-fuel assertions in engine.test.ts + e2e/actions.spec.ts
use the closed form; the e2e 'return leg unfuelable' scenario reworked — a full
tank now covers the EDDF<->LEZL round trip, so the 409 moves to the second
outbound. New e2e: row Refuel deducts exactly L*price / raises fuel by exactly
L via the endpoint's own response snapshot.
77 lines
2.8 KiB
TypeScript
77 lines
2.8 KiB
TypeScript
// Aircraft catalog — in-code constants for now (no DB table yet). One type is
|
||
// enough for the MVP; add more entries to PLANE_TYPES as they are designed.
|
||
|
||
export interface PlaneType {
|
||
id: string;
|
||
name: string;
|
||
// Fuel capacity, liters.
|
||
maxFuel: number;
|
||
// Fuel burn per km of flight at a FULL tank, liters/km (cruise) — the weighted burn
|
||
// model (v0.16.0) calibrates its rate constant to this value at F = maxFuel.
|
||
burnPerKm: number;
|
||
// Operating empty weight in kg (v0.16.0) — feeds the weight-proportional burn model.
|
||
emptyWeightKg: number;
|
||
// Cruise speed, km/h — used by the sim tick to advance planes along routes.
|
||
speedKmh: number;
|
||
// Market price in € (v0.8.0): buying costs `price`, selling pays out
|
||
// SELL_FRACTION × price.
|
||
price: number;
|
||
// Cargo slot capacity (v0.12.0): how many packages can be loaded on one plane.
|
||
capacity: number;
|
||
}
|
||
|
||
export const PLANE_TYPES: Record<string, PlaneType> = {
|
||
atr72: {
|
||
id: 'atr72',
|
||
name: 'ATR 72-600',
|
||
maxFuel: 6300,
|
||
burnPerKm: 1.75, // full-tank rate: ~893 L/h ≈ 714 kg/h at 510 km/h cruise (Jet-A1 ≈ 0.8 kg/L); the weighted model burns less as fuel drops off
|
||
emptyWeightKg: 9_300, // ATR 72-600 operating empty weight
|
||
speedKmh: 510,
|
||
// €200k — with the starting cash of €20k and typical hop rewards (~€1–3k),
|
||
// a second ATR takes sustained play to afford, as intended for the MVP sink.
|
||
price: 200_000,
|
||
capacity: 6,
|
||
},
|
||
};
|
||
|
||
// The plane the player starts with.
|
||
export const STARTER_MODEL = 'atr72';
|
||
|
||
// Jet-A1 fuel price in €/liter (v0.8.0): refueling cost = (maxFuel − fuel) × this.
|
||
export const FUEL_PRICE_PER_L = 0.8;
|
||
|
||
// Fraction of the catalog price paid out when selling a plane (v0.8.0).
|
||
export const SELL_FRACTION = 0.5;
|
||
|
||
// Snapshot shape of one catalog entry in GET /api/state (v0.10.0): `model` is the
|
||
// id used by buy/dispatch, `label` the display name — clients compute all market
|
||
// previews (buy price, sell payout, refuel cost) from these + the economy constants.
|
||
export interface CatalogEntry {
|
||
model: string;
|
||
label: string;
|
||
price: number;
|
||
maxFuel: number;
|
||
burnPerKm: number;
|
||
// Weighted burn model inputs (v0.16.0) — clients can mirror the closed forms in
|
||
// lib/sim/movement.ts with these + FUEL_DENSITY_KG_PER_L.
|
||
emptyWeightKg: number;
|
||
speedKmh: number;
|
||
capacity: number;
|
||
}
|
||
|
||
// The catalog as a flat array, one entry per plane type — the single source of
|
||
// truth for both the sim (PLANE_TYPES) and the client snapshot.
|
||
export function catalogEntries(): CatalogEntry[] {
|
||
return Object.values(PLANE_TYPES).map((t) => ({
|
||
model: t.id,
|
||
label: t.name,
|
||
price: t.price,
|
||
maxFuel: t.maxFuel,
|
||
burnPerKm: t.burnPerKm,
|
||
emptyWeightKg: t.emptyWeightKg,
|
||
speedKmh: t.speedKmh,
|
||
capacity: t.capacity,
|
||
}));
|
||
}
|