import { content } from "@deadhead/content"; import { describe, expect, it } from "vitest"; import { boardPeriodIndex, boardPeriodMs, generateContractBoard, requiredLongHaulLevel, type BoardInput, } from "../src/index"; const earth = content.location("earth")!; const discoveredIds = new Set(["earth", "moon"]); const periodIndex = boardPeriodIndex(Date.parse("2100-01-01T00:00:00Z"), content.config); const input: BoardInput = { location: earth, discoveredIds, periodIndex, worldSeed: 12345 }; /** A band's lower bound for goods: the lightest band has none (CR-007, light goods). */ const floorOf = (band: { min: number }) => band.min === Math.min(...content.config.contractMassBands.map((b) => b.min)) ? 0 : band.min; describe("contract board generation", () => { it("produces an identical board from the same inputs", () => { expect(generateContractBoard(input, content)).toEqual(generateContractBoard(input, content)); }); it("produces a different board for a different period", () => { const next = generateContractBoard({ ...input, periodIndex: periodIndex + 1 }, content); expect(next).not.toEqual(generateContractBoard(input, content)); }); it("has the configured number of contracts, none to its own origin", () => { const board = generateContractBoard(input, content); expect(board).toHaveLength(content.config.contractBoardSize); for (const contract of board) { expect(contract.originId).toBe("earth"); expect(contract.destinationId).not.toBe("earth"); } }); it("expires every contract at the end of its board period", () => { const periodEnd = (periodIndex + 1) * boardPeriodMs(content.config); for (const contract of generateContractBoard(input, content)) { expect(contract.expiresAt).toBe(periodEnd); } }); it("ties tier to skill: Standard is tier 0 with no skill, others 1–5 matching the skill level", () => { const moon = content.location("moon")!; for (let p = 0; p < 50; p++) { for (const c of generateContractBoard( { ...input, location: moon, periodIndex: p }, content, )) { if (c.cargoTypeId === "standard") { expect(c.tier).toBe(0); expect(c.requiredSkill).toBeNull(); } else { expect(c.tier).toBeGreaterThanOrEqual(1); expect(c.tier).toBeLessThanOrEqual(5); expect(c.requiredSkill?.level).toBe(c.tier); } const bands = content.config.contractMassBands; expect(c.massTonnes).toBeGreaterThan(0); expect(c.massTonnes).toBeLessThanOrEqual(Math.max(...bands.map((b) => b.max))); } } }); it("fills each mass band with its configured number of contracts", () => { const board = generateContractBoard(input, content); let slot = 0; for (const band of content.config.contractMassBands) { for (let k = 0; k < band.count; k++, slot++) { expect(board[slot]!.massTonnes).toBeGreaterThanOrEqual(floorOf(band)); expect(board[slot]!.massTonnes).toBeLessThanOrEqual(band.max); } } expect(slot).toBe(board.length); }); it("always offers each ship size a Standard job to a discovered place (no skill needed)", () => { for (let p = 0; p < 500; p++) { const board = generateContractBoard({ ...input, periodIndex: p }, content); for (const ship of content.shipModels.filter((s) => s.purchasable)) { const flyable = board.some( (c) => c.massTonnes <= ship.cargoCapacityTonnes && c.requiredSkill === null && !c.firstContact && discoveredIds.has(c.destinationId), ); expect(flyable).toBe(true); } } }); it("makes 5–15% of contracts first contact over 1,000 boards, all to undiscovered places", () => { let total = 0; let firstContact = 0; for (let p = 0; p < 1000; p++) { for (const contract of generateContractBoard({ ...input, periodIndex: p }, content)) { total++; if (contract.firstContact) { firstContact++; expect(discoveredIds.has(contract.destinationId)).toBe(false); } else { expect(discoveredIds.has(contract.destinationId)).toBe(true); } } } const share = firstContact / total; expect(share).toBeGreaterThanOrEqual(0.05); expect(share).toBeLessThanOrEqual(0.15); }); }); describe("location boards (CR-002)", () => { it("generates a deterministic Moon board from the location alone, with no bay data", () => { const moon = content.location("moon")!; const moonInput: BoardInput = { location: moon, discoveredIds, periodIndex, worldSeed: 12345 }; const board = generateContractBoard(moonInput, content); expect(board).toHaveLength(content.config.contractBoardSize); for (const c of board) { expect(c.originId).toBe("moon"); expect(c.destinationId).not.toBe("moon"); } expect(generateContractBoard(moonInput, content)).toEqual(board); }); }); describe("required Long Haul level", () => { it("counts the cut-offs a distance exceeds", () => { expect(requiredLongHaulLevel(0.4, content.config)).toBe(0); expect(requiredLongHaulLevel(100, content.config)).toBe(0); expect(requiredLongHaulLevel(101, content.config)).toBe(1); expect(requiredLongHaulLevel(900, content.config)).toBe(5); }); });