65 lines
2.7 KiB
TypeScript
65 lines
2.7 KiB
TypeScript
import { content } from "@deadhead/content";
|
||
import { beforeAll, describe, expect, it } from "vitest";
|
||
import { formatFuelShares, fuelShares, type FuelShare } from "../balance/fuel-share";
|
||
import { checkRoute, fuelPriceAt, legFuel, routeDistance } from "../src/index";
|
||
|
||
const loc = (id: string) => content.location(id)!;
|
||
|
||
let rows: FuelShare[];
|
||
beforeAll(() => {
|
||
rows = fuelShares(content);
|
||
});
|
||
|
||
describe("fuel-to-payout check (CR-018 Step 18.1, decision 0.5.110)", () => {
|
||
it("reports every purchasable ship on every route it can fly", () => {
|
||
const models = content.shipModels.filter((m) => m.purchasable);
|
||
expect(new Set(rows.map((r) => r.modelId))).toEqual(new Set(models.map((m) => m.id)));
|
||
for (const row of rows) {
|
||
const model = content.shipModel(row.modelId)!;
|
||
expect(checkRoute(model, loc(row.originId), loc(row.destinationId), content.config).ok).toBe(
|
||
true,
|
||
);
|
||
expect(row.contracts).toBeGreaterThan(0);
|
||
}
|
||
// Every Earth–Moon leg is flown by every purchasable ship, and boards always offer it.
|
||
for (const model of models) {
|
||
expect(
|
||
rows.some(
|
||
(r) => r.modelId === model.id && r.originId === "earth" && r.destinationId === "moon",
|
||
),
|
||
).toBe(true);
|
||
}
|
||
});
|
||
|
||
it("prices a leg's fuel at the origin and divides by the median fitting payout", () => {
|
||
const row = rows.find(
|
||
(r) => r.modelId === "courier" && r.originId === "earth" && r.destinationId === "moon",
|
||
)!;
|
||
const courier = content.shipModel("courier")!;
|
||
const fuel =
|
||
legFuel(courier, loc("earth"), routeDistance(loc("earth"), loc("moon")), content.config) *
|
||
fuelPriceAt(loc("earth"), content.config);
|
||
expect(row.fuelCost).toBeCloseTo(fuel, 9);
|
||
expect(row.share).toBeCloseTo(row.fuelCost / row.medianPayout, 12);
|
||
expect(row.medianPayout).toBeGreaterThan(0);
|
||
});
|
||
|
||
it("keeps fuel at most 25% of the median payout on every route (Step 18.2)", () => {
|
||
// One price can't put every row in 15–25% (user decision, CR-018): only the top is gated.
|
||
// The Clipper's Mars → Earth was the one exception until boards kept only jobs the
|
||
// fleet can complete (CR-019): fitted boards offer it more jobs its 30 t hold takes.
|
||
const over = rows
|
||
.filter((r) => r.share > 0.25)
|
||
.map((r) => `${r.modelId} ${r.originId} → ${r.destinationId}`);
|
||
expect(over).toEqual([]);
|
||
});
|
||
|
||
it("reports the share for each ship and route as a table", () => {
|
||
const table = formatFuelShares(rows).split("\n");
|
||
expect(table).toHaveLength(rows.length + 1);
|
||
expect(
|
||
table.find((line) => line.startsWith("courier") && line.includes("earth → moon")),
|
||
).toMatch(/\d+\.\d%$/);
|
||
});
|
||
});
|