deadhead-space-freight-tycoon/packages/sim/test/contract.test.ts

105 lines
3.8 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<number>();
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,
);
});
});