168 lines
6.0 KiB
TypeScript
168 lines
6.0 KiB
TypeScript
import { content } from "@deadhead/content";
|
|
import { describe, expect, it } from "vitest";
|
|
import {
|
|
autoRepairBooks,
|
|
boardPeriodMs,
|
|
canReachHq,
|
|
chooseAction,
|
|
defaultOrders,
|
|
fuelPriceAt,
|
|
independentOptions,
|
|
MS_PER_MINUTE,
|
|
nearestReachableHq,
|
|
repairRecommended,
|
|
type Contract,
|
|
type IndependentInput,
|
|
} from "../src/index";
|
|
|
|
const { config } = content;
|
|
const clipper = content.shipModel("clipper")!;
|
|
const PERIOD = boardPeriodMs(config);
|
|
const PERIOD_START = 1000 * PERIOD;
|
|
const AFTER_LOOK =
|
|
PERIOD_START + (config.independentPilots.firstLookRealMinutes + 1) * MS_PER_MINUTE;
|
|
const EARTH_PRICE = fuelPriceAt(content.location("earth")!, config);
|
|
const DISCOVERED = new Set(["earth", "moon", "mars", "ceres-station", "jupiter-cloud-platform"]);
|
|
|
|
const job = (id: string, destinationId: string): Contract => ({
|
|
id,
|
|
originId: "earth",
|
|
destinationId,
|
|
cargoTypeId: "standard",
|
|
goodId: "consumer-goods",
|
|
shipper: "Pacific Orbital Logistics",
|
|
priority: false,
|
|
deliveryWindowGameHours: null,
|
|
tier: 0,
|
|
massTonnes: 10,
|
|
payout: 30_000,
|
|
requiredSkill: null,
|
|
requiredLongHaulLevel: 0,
|
|
offeredAt: PERIOD_START,
|
|
expiresAt: PERIOD_START + PERIOD,
|
|
firstContact: false,
|
|
});
|
|
|
|
function input(board: Contract[], over: Partial<IndependentInput> = {}): IndependentInput {
|
|
return {
|
|
ship: {
|
|
id: "s",
|
|
modelId: "clipper",
|
|
locationId: "earth",
|
|
inFlight: false,
|
|
tank: { fuelFu: clipper.tankCapacityFu, avgPrice: EARTH_PRICE },
|
|
},
|
|
pilot: {
|
|
id: "p",
|
|
isAvatar: false,
|
|
assignedShipId: "s",
|
|
inFlight: false,
|
|
skills: { "long-haul": 3 },
|
|
restEndsAt: null,
|
|
},
|
|
orders: defaultOrders("earth", content),
|
|
board,
|
|
now: AFTER_LOOK,
|
|
credits: 1_000_000,
|
|
spendingFloor: config.independentPilots.defaultSpendingFloor,
|
|
discoveredIds: DISCOVERED,
|
|
emptyPeriods: 0,
|
|
...over,
|
|
};
|
|
}
|
|
|
|
// Mars is one leg from Earth for a Clipper (CR-015); Ceres isn't.
|
|
const mars = { hqId: "mars" };
|
|
|
|
describe("service routing (Step 27.2, decisions 0.5.153, 0.5.154)", () => {
|
|
it("can reach Mars but not Ceres from Earth in a Clipper", () => {
|
|
expect(canReachHq(input([]).ship, "mars", DISCOVERED, content)).toBe(true);
|
|
expect(canReachHq(input([]).ship, "ceres-station", DISCOVERED, content)).toBe(false);
|
|
});
|
|
|
|
it("with a booking at Mars, considers only jobs that end at Mars", () => {
|
|
const board = [job("a", "moon"), job("b", "ceres-station"), job("c", "mars")];
|
|
const options = independentOptions(input(board, { booking: mars }), content);
|
|
expect(options.map((o) => o.contract.destinationId)).toEqual(["mars"]);
|
|
expect(chooseAction(input(board, { booking: mars }), content)).toMatchObject({
|
|
kind: "take",
|
|
contractId: "c",
|
|
});
|
|
});
|
|
|
|
it("deadheads straight to Mars when no job goes there", () => {
|
|
const board = [job("a", "moon")];
|
|
expect(chooseAction(input(board, { booking: mars }), content)).toMatchObject({
|
|
kind: "deadhead-service",
|
|
destinationId: "mars",
|
|
});
|
|
});
|
|
|
|
it("waits at the booked HQ for the work to start", () => {
|
|
const at = input([job("a", "moon")], { booking: mars });
|
|
const there = { ...at, ship: { ...at.ship, locationId: "mars" } };
|
|
expect(chooseAction(there, content)).toEqual({ kind: "wait" });
|
|
});
|
|
|
|
it("works as usual when the booked HQ is out of reach", () => {
|
|
// A Courier can't reach Jupiter from Earth.
|
|
const courier = content.shipModel("courier")!;
|
|
const base = input([job("a", "moon")], { booking: { hqId: "jupiter-cloud-platform" } });
|
|
const small = {
|
|
...base,
|
|
ship: {
|
|
...base.ship,
|
|
modelId: "courier",
|
|
tank: { fuelFu: courier.tankCapacityFu, avgPrice: 5 },
|
|
},
|
|
};
|
|
expect(canReachHq(small.ship, "jupiter-cloud-platform", DISCOVERED, content)).toBe(false);
|
|
expect(chooseAction(small, content)).toMatchObject({ kind: "take", contractId: "a" });
|
|
});
|
|
|
|
it("finds the nearest reachable HQ", () => {
|
|
const ship = input([]).ship;
|
|
expect(nearestReachableHq(ship, ["mars", "moon"], DISCOVERED, content)).toBe("moon");
|
|
expect(nearestReachableHq(ship, ["earth", "moon"], DISCOVERED, content)).toBe("earth");
|
|
const courier = { ...ship, modelId: "courier", tank: { fuelFu: 40, avgPrice: 5 } };
|
|
expect(nearestReachableHq(courier, ["jupiter-cloud-platform"], DISCOVERED, content)).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("auto-repair (Step 27.2, decision 0.5.157)", () => {
|
|
const on = { on: true, thresholdPct: 40 };
|
|
const drop = { hullBefore: 45, hullAfter: 35, autoRepair: on, booking: null };
|
|
|
|
it("books a repair when an Independent crew's hull drops below the threshold", () => {
|
|
expect(autoRepairBooks({ ...drop, pilotMode: "independent" })).toBe(true);
|
|
});
|
|
|
|
it("doesn't with a Manual crew, with auto-repair off, or without crossing the threshold", () => {
|
|
expect(autoRepairBooks({ ...drop, pilotMode: "manual" })).toBe(false);
|
|
expect(
|
|
autoRepairBooks({ ...drop, pilotMode: "independent", autoRepair: { ...on, on: false } }),
|
|
).toBe(false);
|
|
expect(autoRepairBooks({ ...drop, pilotMode: "independent", hullBefore: 38 })).toBe(false);
|
|
expect(autoRepairBooks({ ...drop, pilotMode: "independent", hullAfter: 41 })).toBe(false);
|
|
});
|
|
|
|
it("adds the repair to an upgrade booking, but not a second repair", () => {
|
|
expect(autoRepairBooks({ ...drop, pilotMode: "independent", booking: { repair: false } })).toBe(
|
|
true,
|
|
);
|
|
expect(autoRepairBooks({ ...drop, pilotMode: "independent", booking: { repair: true } })).toBe(
|
|
false,
|
|
);
|
|
});
|
|
|
|
it("recommends a repair for a Manual crew below the threshold", () => {
|
|
const base = { hullPct: 35, autoRepair: on, booking: null };
|
|
expect(repairRecommended({ ...base, pilotMode: "manual" })).toBe(true);
|
|
expect(repairRecommended({ ...base, pilotMode: "independent" })).toBe(false);
|
|
expect(repairRecommended({ ...base, pilotMode: "manual", hullPct: 50 })).toBe(false);
|
|
expect(repairRecommended({ ...base, pilotMode: "manual", booking: { repair: true } })).toBe(
|
|
false,
|
|
);
|
|
});
|
|
});
|