Reputation economy: rep-scaled buy/refuel discounts + job reward quality (ISS-072, v0.38.0)
CI / build + unit + e2e (pull_request) Successful in 9m3s
CI / build + unit + e2e (pull_request) Successful in 9m3s
This commit is contained in:
+6
-3
@@ -5,6 +5,8 @@ import { expect, test } from '@playwright/test';
|
||||
import { initialBearingDeg } from '../lib/geo';
|
||||
import { weatherFor } from '../lib/weather';
|
||||
import { windBurnFactor } from '../lib/sim/movement';
|
||||
// v0.38.0 (ISS-072) — reputation discounts: expectations computed from the LIVE snapshot's rep with the real helpers.
|
||||
import { FUEL_DISCOUNT_PER_REP, FUEL_PRICE_PER_L, MAX_FUEL_DISCOUNT, repRate } from '../lib/game/catalog';
|
||||
|
||||
// v0.12.0 package model against the live origin (https://flight.dev.kkhost.pl):
|
||||
// the full core loop through the UI — hook creates a short package at EDDF → Load
|
||||
@@ -21,7 +23,7 @@ const FAR = 'LEZL'; // Seville — a long leg: under the v0.16.0 weighted model
|
||||
|
||||
// Mirrors lib/sim/movement.ts (v0.16.0 weight-proportional burn) + lib/game/catalog.ts (atr72).
|
||||
const FUEL_DENSITY_KG_PER_L = 0.8; // Jet-A1 density — must match FUEL_DENSITY_KG_PER_L in movement.ts
|
||||
const FUEL_PRICE_PER_L = 0.8; // must match catalog.ts
|
||||
// FUEL_PRICE_PER_L is imported from ../lib/game/catalog (v0.38.0) instead of mirrored here.
|
||||
const REP_BASE = 1; // lib/sim/engine.ts
|
||||
const REP_PER_KM = 0.0005;
|
||||
|
||||
@@ -239,13 +241,14 @@ test('full core loop via UI: load → fly to destination → exact reward + rep
|
||||
6,
|
||||
);
|
||||
|
||||
// Refuel via the API: cost = (maxFuel − fuel) × FUEL_PRICE_PER_L, deducted exactly.
|
||||
// Refuel via the API: cost = (maxFuel − fuel) × FUEL_PRICE_PER_L, rep-scaled (v0.38.0, ISS-072), deducted exactly.
|
||||
const refuelRes = await page.request.post(`/api/planes/${planeId}/refuel`);
|
||||
expect(refuelRes.ok()).toBeTruthy();
|
||||
s = await getState(page);
|
||||
const afterRefuel = s.planes.find((p) => p.id === planeId)!;
|
||||
expect(afterRefuel.fuel).toBe(afterRefuel.maxFuel); // back to full
|
||||
const refuelCost = Math.round((afterRefuel.maxFuel - landed.fuel) * FUEL_PRICE_PER_L * 100) / 100;
|
||||
const fuelRate = repRate(s.account.reputation, FUEL_DISCOUNT_PER_REP, MAX_FUEL_DISCOUNT);
|
||||
const refuelCost = Math.round((afterRefuel.maxFuel - landed.fuel) * FUEL_PRICE_PER_L * (1 - fuelRate) * 100) / 100;
|
||||
expect(s.account.cash).toBeCloseTo(before.account.cash + pkg.reward - refuelCost, 6);
|
||||
|
||||
// Restore: fly home (repositioning — no package needed in the v0.12.0 model). ISS-041: the
|
||||
|
||||
+20
-4
@@ -5,6 +5,9 @@ import { expect, test } from '@playwright/test';
|
||||
import { initialBearingDeg } from '../lib/geo';
|
||||
import { weatherFor } from '../lib/weather';
|
||||
import { windBurnFactor } from '../lib/sim/movement';
|
||||
// v0.38.0 (ISS-072) — reputation discounts: expectations computed from the LIVE snapshot's rep with the real helpers.
|
||||
import { FUEL_DISCOUNT_PER_REP, MAX_FUEL_DISCOUNT, MAX_PLANE_DISCOUNT, PLANE_DISCOUNT_PER_REP, repRate } from '../lib/game/catalog';
|
||||
import { STARTING_REPUTATION } from '../lib/game/seed';
|
||||
|
||||
// v0.12.0 — right Jobs & Market pane against the live origin: open packages render with
|
||||
// names/reward/deadline and Load (UI click) binds one to the starter; the on-board list
|
||||
@@ -367,11 +370,20 @@ test('market buy/sell render with previews from the catalog in /api/state; left-
|
||||
// v0.28.0 (ISS-047): the market rows live in the Planes market tab — switch to it first.
|
||||
await page.getByTestId('tab-planes-market').click();
|
||||
|
||||
// Buy: the model picker lists the catalog entry and the price preview is its full price.
|
||||
// Buy: the model picker lists the catalog entry and the price preview is its EFFECTIVE price —
|
||||
// rep-scaled (v0.38.0) from the live snapshot's reputation; at the starting rep this equals list price.
|
||||
const buyModel = page.getByTestId('market-buy-model');
|
||||
await expect(buyModel).toBeVisible({ timeout: 15_000 });
|
||||
await expect(buyModel.locator(`option[value="${entry.model}"]`)).toHaveText(entry.label);
|
||||
await expect(page.getByTestId('market-buy-price')).toContainText(eur(entry.price));
|
||||
const planeRate = repRate(body.account.reputation, PLANE_DISCOUNT_PER_REP, MAX_PLANE_DISCOUNT);
|
||||
const buyPrice = Math.round(entry.price * (1 - planeRate) * 100) / 100;
|
||||
await expect(page.getByTestId('market-buy-price')).toContainText(eur(buyPrice));
|
||||
// The discount pill is present iff the live rep earns one, and shows round(rate × 100).
|
||||
if (body.account.reputation > STARTING_REPUTATION) {
|
||||
await expect(page.getByTestId('market-buy-discount')).toHaveText(`−${Math.round(planeRate * 100)}%`);
|
||||
} else {
|
||||
await expect(page.getByTestId('market-buy-discount')).toHaveCount(0);
|
||||
}
|
||||
await expect(page.getByTestId('market-buy')).toBeVisible();
|
||||
|
||||
// Sell: the grounded-plane picker lists plane-starter and the payout preview is
|
||||
@@ -387,7 +399,9 @@ test('market buy/sell render with previews from the catalog in /api/state; left-
|
||||
|
||||
// Refuel through the pane: the flight genuinely burned fuel…
|
||||
if (starter.fuel < starter.maxFuel) {
|
||||
const expectedCost = Math.round((starter.maxFuel - starter.fuel) * body.fuelPricePerL * 100) / 100;
|
||||
// v0.38.0 (ISS-072): rep-scaled cost — same formula as the server's refuelPlane 'full'.
|
||||
const fuelRate = repRate(body.account.reputation, FUEL_DISCOUNT_PER_REP, MAX_FUEL_DISCOUNT);
|
||||
const expectedCost = Math.round((starter.maxFuel - starter.fuel) * body.fuelPricePerL * (1 - fuelRate) * 100) / 100;
|
||||
const cashBefore = body.account.cash;
|
||||
|
||||
// …the refuel picker lists it, the preview matches the exact cost, and the click deducts it.
|
||||
@@ -455,7 +469,9 @@ test('on-board row Refuel buys 150% of the weather-adjusted leg requirement; lef
|
||||
const target = Math.min(entry.maxFuel, required * 1.5); // ISS-071: 150% margin, capped at tank capacity
|
||||
const liters = Math.max(0, target - fuelBefore);
|
||||
expect(liters).toBeGreaterThan(0); // the long leg genuinely needs more than what's left
|
||||
const expectedCost = Math.round(liters * pre.fuelPricePerL * 100) / 100;
|
||||
// v0.38.0 (ISS-072): rep-scaled cost — same formula as the server's refuelPlane 'forFlight'.
|
||||
const fuelRate = repRate(pre.account.reputation, FUEL_DISCOUNT_PER_REP, MAX_FUEL_DISCOUNT);
|
||||
const expectedCost = Math.round(liters * pre.fuelPricePerL * (1 - fuelRate) * 100) / 100;
|
||||
|
||||
await page.goto('/');
|
||||
const refuelBtn = page.getByTestId(`refuel-for-${pkg2.id}`);
|
||||
|
||||
@@ -5,6 +5,8 @@ import { expect, test } from '@playwright/test';
|
||||
import { initialBearingDeg } from '../lib/geo';
|
||||
import { weatherFor } from '../lib/weather';
|
||||
import { windBurnFactor } from '../lib/sim/movement';
|
||||
// v0.38.0 (ISS-072) — reputation discounts: expectations computed from the LIVE snapshot's rep with the real helpers.
|
||||
import { FUEL_DISCOUNT_PER_REP, MAX_FUEL_DISCOUNT, repRate } from '../lib/game/catalog';
|
||||
|
||||
// v0.31.0 — ISS-055: gameplay with TWO planes in the fleet. The reported bug ("can't load cargo
|
||||
// if both planes are on the same airport") was UX-only: LoadControls' multi-plane path rendered a
|
||||
@@ -452,8 +454,10 @@ test('refuel section lists both planes; refueling one tops only its tank', async
|
||||
await expect(refuelSelect.locator(`option[value="${STARTER}"]`)).toHaveCount(1);
|
||||
await expect(refuelSelect.locator(`option[value="${secondId}"]`)).toHaveCount(1);
|
||||
|
||||
// …select the SECOND plane — the preview is its exact fill-up cost (mirrors refuelPlane 'full').
|
||||
const expectedCost = Math.round((second.maxFuel - second.fuel) * pre.fuelPricePerL * 100) / 100;
|
||||
// …select the SECOND plane — the preview is its exact fill-up cost (mirrors refuelPlane 'full'),
|
||||
// rep-scaled (v0.38.0, ISS-072) from the live snapshot's reputation.
|
||||
const fuelRate = repRate(pre.account.reputation, FUEL_DISCOUNT_PER_REP, MAX_FUEL_DISCOUNT);
|
||||
const expectedCost = Math.round((second.maxFuel - second.fuel) * pre.fuelPricePerL * (1 - fuelRate) * 100) / 100;
|
||||
await refuelSelect.selectOption(secondId);
|
||||
await expect(page.getByTestId('market-refuel-cost')).toContainText(eur(expectedCost));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user