import { content } from "@deadhead/content"; import { describe, expect, it } from "vitest"; import { contractPayout, generateContractBoard, routeDistance, type Contract } from "../src/index"; const { config } = content; const v = config.contractPayVariance; const all = new Set(content.locations.map((l) => l.id)); /** The contract's payout without its market rate, from the same inputs. */ function basePayout(c: Contract): number { const good = content.good(c.goodId)!; return contractPayout( { cargoType: content.cargoType(c.cargoTypeId)!, payMultiplier: good.payMultiplier, tier: c.tier, massTonnes: c.massTonnes, distanceMillionKm: routeDistance( content.location(c.originId)!, content.location(c.destinationId)!, ), firstContact: c.firstContact, priority: c.priority, }, config, ); } const contracts = Array.from({ length: 1000 }, (_, p) => generateContractBoard( { location: content.location("earth")!, discoveredIds: all, periodIndex: p, worldSeed: 99 }, content, ), ).flat(); describe("market rate (CR-008)", () => { it("scales a payout by its rate", () => { // Plain distance keeps the base payout whole, so the rate's effect is exact. const linear = { ...config, economicDistance: { ...config.economicDistance, exponent: 1 }, }; const input = { cargoType: content.cargoType("standard")!, payMultiplier: 1, tier: 0, massTonnes: 10, distanceMillionKm: 100, firstContact: false, priority: false, }; expect(contractPayout({ ...input, marketRate: 1.1 }, linear)).toBe( Math.round(contractPayout(input, linear) * 1.1), ); }); it("keeps every board contract's rate within ±variance, averaging 1", () => { // Ignore tiny payouts, where rounding to whole credits blurs the rate. const rates = contracts .filter((c) => basePayout(c) >= 200) .map((c) => c.payout / basePayout(c)); expect(rates.length).toBeGreaterThan(1000); for (const rate of rates) { expect(rate).toBeGreaterThanOrEqual(1 - v - 0.005); expect(rate).toBeLessThanOrEqual(1 + v + 0.005); } const mean = rates.reduce((a, b) => a + b, 0) / rates.length; expect(Math.abs(mean - 1)).toBeLessThanOrEqual(0.01); }); it("no longer pays the identical amount for the same good, mass and route", () => { const seen = new Map>(); for (const c of contracts) { const key = `${c.goodId}|${c.massTonnes}|${c.destinationId}|${c.priority}|${c.firstContact}`; seen.set(key, (seen.get(key) ?? new Set()).add(c.payout)); } const repeated = [...seen.values()].filter((payouts) => payouts.size > 1); expect(repeated.length).toBeGreaterThan(50); }); });