deadhead-space-freight-tycoon/packages/sim/test/board-completable.test.ts

143 lines
5.2 KiB
TypeScript

import { content } from "@deadhead/content";
import { describe, expect, it } from "vitest";
import {
completability,
generateContractBoard,
isCompletable,
type BoardInput,
type FleetMember,
} from "../src/index";
const { config } = content;
const loc = (id: string) => content.location(id)!;
const allIds = new Set(content.locations.map((l) => l.id));
const maxed = Object.fromEntries(content.skills.map((k) => [k.id, k.maxLevel]));
const avatarCourier: FleetMember = { modelId: "courier", skills: {} };
/** Assorted fleets: lone ships, mixed crews, upgrades, skills. */
const FLEETS: FleetMember[][] = [
[avatarCourier],
[avatarCourier, { modelId: "clipper", skills: { "expensive-cargo": 1 } }],
[{ modelId: "lighter", skills: { "heavy-cargo": 2 }, upgrades: { "cargo-bay": 2 } }],
[{ modelId: "freighter", skills: { "long-haul": 2, "dangerous-cargo": 1 } }],
[{ modelId: "bulk-hauler", skills: maxed }, avatarCourier],
];
describe("boards keep only completable jobs (Step 19.2, decision 0.5.114)", () => {
it("never offers a job the fleet can't complete, over 1,000 boards", () => {
let boards = 0;
const origins = ["earth", "moon", "mars", "ceres-station", "jupiter-cloud-platform"];
for (const [f, fleet] of FLEETS.entries()) {
for (const origin of origins) {
for (let periodIndex = 0; periodIndex < 40; periodIndex++) {
const discoveredIds = periodIndex % 2 ? allIds : new Set(["earth", "moon", "mars"]);
const input: BoardInput = {
location: loc(origin),
discoveredIds,
periodIndex,
worldSeed: 1000 + f,
fleet,
};
for (const c of generateContractBoard(input, content)) {
const { completable, failures } = completability(c, fleet, discoveredIds, content);
expect(completable, `${c.id} ${JSON.stringify(failures)}`).toBe(true);
}
boards++;
}
}
}
expect(boards).toBeGreaterThanOrEqual(1000);
});
it("gives a level-0 avatar in a Courier at Earth full boards of Standard Moon jobs it fits", () => {
for (let periodIndex = 0; periodIndex < 200; periodIndex++) {
const board = generateContractBoard(
{
location: loc("earth"),
discoveredIds: allIds,
periodIndex,
worldSeed: 7,
fleet: [avatarCourier],
},
content,
);
expect(board).toHaveLength(config.contractBoardSize);
for (const c of board) {
expect(c.cargoTypeId).toBe("standard");
expect(c.massTonnes).toBeLessThanOrEqual(content.shipModel("courier")!.cargoCapacityTonnes);
expect(c.destinationId).toBe("moon");
}
}
});
it("stops at the attempt limit with a smaller board when the fleet can do almost nothing", () => {
// From Mars a Courier reaches nowhere: nothing is completable.
const nothing = generateContractBoard(
{
location: loc("mars"),
discoveredIds: allIds,
periodIndex: 3,
worldSeed: 7,
fleet: [avatarCourier],
},
content,
);
expect(nothing).toEqual([]);
// Only Earth known: a Courier can take nothing but the odd first contact to the
// Moon, which random candidates hit only sometimes, so some boards come up short.
const known = new Set(["earth"]);
let short = 0;
for (let periodIndex = 0; periodIndex < 50; periodIndex++) {
const board = generateContractBoard(
{
location: loc("earth"),
discoveredIds: known,
periodIndex,
worldSeed: 7,
fleet: [avatarCourier],
},
content,
);
expect(board.length).toBeLessThanOrEqual(config.contractBoardSize);
if (board.length < config.contractBoardSize) short++;
for (const c of board) {
expect(c.destinationId).toBe("moon");
expect(isCompletable(c, [avatarCourier], known, content)).toBe(true);
}
}
expect(short).toBeGreaterThan(0);
});
it("gives identical boards for identical inputs", () => {
for (const fleet of FLEETS) {
const input: BoardInput = {
location: loc("earth"),
discoveredIds: allIds,
periodIndex: 12,
worldSeed: 99,
fleet,
};
expect(generateContractBoard(input, content)).toEqual(generateContractBoard(input, content));
}
});
it("filters first contacts the same way", () => {
// Only Earth and the Moon known: a Clipper may explore Mars or Venus, a Courier nowhere.
const known = new Set(["earth", "moon"]);
let clipperFirstContacts = 0;
for (let periodIndex = 0; periodIndex < 100; periodIndex++) {
const input = { location: loc("earth"), discoveredIds: known, periodIndex, worldSeed: 5 };
const courierBoard = generateContractBoard({ ...input, fleet: [avatarCourier] }, content);
expect(courierBoard.some((c) => c.firstContact)).toBe(false);
const clipper: FleetMember = { modelId: "clipper", skills: { "long-haul": 1 } };
for (const c of generateContractBoard({ ...input, fleet: [clipper] }, content)) {
if (c.firstContact) {
clipperFirstContacts++;
expect(isCompletable(c, [clipper], known, content)).toBe(true);
}
}
}
expect(clipperFirstContacts).toBeGreaterThan(0);
});
});