feat: v0.16.0 — ISS-005 weight-proportional fuel burn + refuel just enough for the loaded package
CI / build + unit + e2e (pull_request) Failing after 5m28s
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.
This commit is contained in:
+38
-7
@@ -197,7 +197,8 @@ export const openApiSpec = {
|
||||
content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } },
|
||||
},
|
||||
409: {
|
||||
description: 'Plane not grounded, or insufficient fuel for the leg (burnPerKm × great-circle km)',
|
||||
description:
|
||||
'Plane not grounded, or insufficient fuel for the leg (v0.16.0 weight-proportional burn — the fuel required to cover the great-circle distance arriving empty)',
|
||||
content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } },
|
||||
},
|
||||
},
|
||||
@@ -271,22 +272,35 @@ export const openApiSpec = {
|
||||
post: {
|
||||
summary: 'Refuel a grounded plane',
|
||||
description:
|
||||
'Fill a grounded plane to maxFuel (v0.8.0). Cost = (maxFuel − fuel) × fuel price per liter, deducted from cash; no body.',
|
||||
'Refuel a grounded plane at the fuel price per added liter, deducted from cash (v0.8.0). The body is optional (v0.16.0): no body or mode "full" fills to maxFuel; mode "forFlight" with packageId adds just enough for that loaded package\'s destination leg — liters = required fuel for the great-circle distance (weight-proportional burn model) minus what is already in the tank.',
|
||||
operationId: 'refuelPlane',
|
||||
parameters: [
|
||||
{ name: 'planeId', in: 'path', required: true, schema: { type: 'string' } },
|
||||
],
|
||||
requestBody: {
|
||||
required: false,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/RefuelRequest' },
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: 'Refueled — full state snapshot (fuel at max, cash reduced by the cost)',
|
||||
description: 'Refueled — full state snapshot (fuel raised by exactly the added liters, cash reduced by the cost)',
|
||||
content: { 'application/json': { schema: { $ref: '#/components/schemas/StateSnapshot' } } },
|
||||
},
|
||||
400: {
|
||||
description: 'Invalid body (not an object, unknown field, unknown mode, or forFlight without packageId)',
|
||||
content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } },
|
||||
},
|
||||
404: {
|
||||
description: 'Unknown plane id',
|
||||
description: 'Unknown plane id (or unknown package id in forFlight mode)',
|
||||
content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } },
|
||||
},
|
||||
409: {
|
||||
description: 'Plane is flying, or cash is insufficient for the fill-up',
|
||||
description:
|
||||
'Plane is flying, the package is not loaded on this plane (or is expired), the leg does not fit a full tank, or cash is insufficient for the refuel',
|
||||
content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } },
|
||||
},
|
||||
},
|
||||
@@ -376,11 +390,12 @@ export const openApiSpec = {
|
||||
label: { type: 'string', description: 'Display name (e.g. "ATR 72-600").' },
|
||||
price: { type: 'number', description: 'Market price in € (buy cost; sell pays out sellFraction × this).' },
|
||||
maxFuel: { type: 'number', description: 'Fuel capacity, liters.' },
|
||||
burnPerKm: { type: 'number', description: 'Cruise fuel burn, liters/km.' },
|
||||
burnPerKm: { type: 'number', description: 'Cruise fuel burn at a full tank, liters/km (v0.16.0 weighted model calibrates to this).' },
|
||||
emptyWeightKg: { type: 'number', description: 'Operating empty weight, kg — with burnPerKm and maxFuel it drives the v0.16.0 weight-proportional burn model.' },
|
||||
speedKmh: { type: 'number', description: 'Cruise speed, km/h.' },
|
||||
capacity: { type: 'integer', description: 'Cargo slot capacity — packages that can be loaded on one plane (v0.12.0).' },
|
||||
},
|
||||
required: ['model', 'label', 'price', 'maxFuel', 'burnPerKm', 'speedKmh', 'capacity'],
|
||||
required: ['model', 'label', 'price', 'maxFuel', 'burnPerKm', 'emptyWeightKg', 'speedKmh', 'capacity'],
|
||||
},
|
||||
AccountState: {
|
||||
type: 'object',
|
||||
@@ -440,6 +455,22 @@ export const openApiSpec = {
|
||||
},
|
||||
required: ['planeId'],
|
||||
},
|
||||
RefuelRequest: {
|
||||
type: 'object',
|
||||
description:
|
||||
'Optional refuel body (v0.16.0) — omit it entirely or send {} to fill the tank; both fields are optional.',
|
||||
properties: {
|
||||
mode: {
|
||||
enum: ['full', 'forFlight'],
|
||||
description:
|
||||
'"full" (default) fills the tank to maxFuel; "forFlight" adds just enough fuel for one loaded package\'s destination leg.',
|
||||
},
|
||||
packageId: {
|
||||
type: 'string',
|
||||
description: 'Required with mode "forFlight": a package LOADED on this plane — its destIcao is the refuel target.',
|
||||
},
|
||||
},
|
||||
},
|
||||
Error: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
|
||||
Reference in New Issue
Block a user