81 lines
2.7 KiB
TypeScript
81 lines
2.7 KiB
TypeScript
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<string, Set<number>>();
|
|
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);
|
|
});
|
|
});
|