import { content } from "@deadhead/content"; import { describe, expect, it } from "vitest"; import { tierFor } from "../src/board"; import { contractPayout, createRng, economicDistance, type PayoutInput } from "../src/index"; /** Plain distance, so each factor's effect is exact; the curve is tested below. */ const config = { ...content.config, economicDistance: { ...content.config.economicDistance, exponent: 1 }, }; const standard = content.cargoType("standard")!; const dangerous = content.cargoType("dangerous")!; const base: PayoutInput = { cargoType: standard, payMultiplier: 1, tier: 0, massTonnes: 10, distanceMillionKm: 100, firstContact: false, priority: false, }; describe("contract payout", () => { it("matches a hand calculation", () => { // Standard rate (credits/t/M km) × 10 t × 100M km. expect(contractPayout(base, config)).toBe(standard.basePayPerTonnePerMillionKm * 10 * 100); }); it("scales linearly with mass and distance", () => { const payout = contractPayout(base, config); expect(contractPayout({ ...base, massTonnes: 30 }, config)).toBe(3 * payout); expect(contractPayout({ ...base, distanceMillionKm: 250 }, config)).toBe(2.5 * payout); }); it("pays more for Dangerous than Standard on the same mass and route", () => { expect(contractPayout({ ...base, cargoType: dangerous, tier: 1 }, config)).toBeGreaterThan( contractPayout(base, config), ); }); it("pays more at a higher tier", () => { const tier1 = contractPayout({ ...base, cargoType: dangerous, tier: 1 }, config); const tier3 = contractPayout({ ...base, cargoType: dangerous, tier: 3 }, config); expect(tier3).toBeGreaterThan(tier1); }); it("pays more for a first-contact contract", () => { expect(contractPayout({ ...base, firstContact: true }, config)).toBe( contractPayout(base, config) * config.firstContactPayoutMultiplier, ); }); it("rounds to whole credits", () => { expect(Number.isInteger(contractPayout({ ...base, distanceMillionKm: 0.384 }, config))).toBe( true, ); }); }); describe("payout on economic distance (decision 0.5.84)", () => { const real = content.config; it("pays a Moon-length leg exactly as before, and long legs on the gentler curve", () => { const moon = { ...base, distanceMillionKm: real.economicDistance.referenceMillionKm }; expect(contractPayout(moon, real)).toBe(contractPayout(moon, config)); const mars = { ...base, distanceMillionKm: 130 }; expect(contractPayout(mars, real)).toBeLessThan(contractPayout(mars, config)); expect(contractPayout(mars, real)).toBe( Math.round(standard.basePayPerTonnePerMillionKm * 10 * economicDistance(130, real)), ); }); }); describe("contracts carry goods (CR-007)", () => { const rng = createRng(42); it("takes the tier of a High-Value or Dangerous contract from its good", () => { for (const good of content.goods.filter( (g) => g.cargoClassId === "high-value" || g.cargoClassId === "dangerous", )) { expect(tierFor(rng, good, content)).toBe(good.tier); } }); it("keeps the random 1–5 tier for Heavy goods and tier 0 for Standard", () => { const parts = content.good("machine-parts")!; const seen = new Set(); for (let i = 0; i < 200; i++) seen.add(tierFor(rng, parts, content)); expect([...seen].sort()).toEqual([1, 2, 3, 4, 5]); expect(tierFor(rng, content.good("mail")!, content)).toBe(0); }); it("doubles the payout when the good's pay multiplier doubles", () => { expect(contractPayout({ ...base, payMultiplier: 2 }, config)).toBe( 2 * contractPayout(base, config), ); }); it("pays a Priority contract exactly the bonus times an identical normal one", () => { expect(contractPayout({ ...base, priority: true }, config)).toBe( contractPayout(base, config) * config.priorityPayoutMultiplier, ); }); });