139 lines
4.2 KiB
TypeScript
139 lines
4.2 KiB
TypeScript
import { content } from "@deadhead/content";
|
|
import { describe, expect, it } from "vitest";
|
|
import {
|
|
MS_PER_HOUR,
|
|
createDispatchRecord,
|
|
createRepositionRecord,
|
|
dutyLimit,
|
|
resolveDispatch,
|
|
restEndsAt,
|
|
tripGameHours,
|
|
validateDispatch,
|
|
validateReposition,
|
|
type Contract,
|
|
type DispatchProposal,
|
|
type DispatchRecord,
|
|
type PilotState,
|
|
type ResolveInput,
|
|
type ShipState,
|
|
} from "../src/index";
|
|
|
|
const now = Date.parse("2100-01-01T00:30:00Z");
|
|
const contract: Contract = {
|
|
id: "c1",
|
|
originId: "earth",
|
|
destinationId: "moon",
|
|
cargoTypeId: "standard",
|
|
goodId: "consumer-goods",
|
|
shipper: "Pacific Orbital Logistics",
|
|
priority: false,
|
|
deliveryWindowGameHours: null,
|
|
tier: 0,
|
|
massTonnes: 10,
|
|
payout: 2000,
|
|
requiredSkill: null,
|
|
requiredLongHaulLevel: 0,
|
|
offeredAt: Date.parse("2100-01-01T00:00:00Z"),
|
|
expiresAt: Date.parse("2100-01-01T01:00:00Z"),
|
|
firstContact: false,
|
|
};
|
|
const ship: ShipState = { id: "s1", modelId: "courier", locationId: "earth", inFlight: false };
|
|
const pilot: PilotState = {
|
|
id: "p1",
|
|
isAvatar: false,
|
|
assignedShipId: "s1",
|
|
inFlight: false,
|
|
skills: {},
|
|
restEndsAt: null,
|
|
};
|
|
const proposal: DispatchProposal = { ship, pilot, contract, tutorial: false, credits: 10_000, now };
|
|
const limit = dutyLimit({}, content.config);
|
|
|
|
const resolveAt = (record: DispatchRecord, dutyBefore: number, at = record.arrivalAt) => {
|
|
const input: ResolveInput = {
|
|
record,
|
|
pilotIsAvatar: false,
|
|
discoveredIds: new Set(["earth", "moon"]),
|
|
loanBalance: 0,
|
|
pilotDutyHours: dutyBefore,
|
|
pilotSkills: {},
|
|
now: at,
|
|
};
|
|
const result = resolveDispatch(input, content);
|
|
if (result.status !== "resolved") throw new Error("not resolved");
|
|
return result.resolution;
|
|
};
|
|
|
|
describe("duty in validation (Step 10D.3)", () => {
|
|
it("refuses a resting pilot with pilot-resting, before other pilot checks", () => {
|
|
const resting = { ...pilot, restEndsAt: now + 60_000, inFlight: true, assignedShipId: null };
|
|
expect(validateDispatch({ ...proposal, pilot: resting }, content)).toEqual({
|
|
ok: false,
|
|
reason: "pilot-resting",
|
|
});
|
|
expect(
|
|
validateReposition(
|
|
{
|
|
ship,
|
|
pilot: { ...pilot, restEndsAt: now + 1 },
|
|
destinationId: "moon",
|
|
discoveredIds: new Set(["earth", "moon"]),
|
|
credits: 10_000,
|
|
now,
|
|
},
|
|
content,
|
|
),
|
|
).toEqual({ ok: false, reason: "pilot-resting" });
|
|
});
|
|
|
|
it("lets a pilot whose rest has ended fly", () => {
|
|
expect(
|
|
validateDispatch({ ...proposal, pilot: { ...pilot, restEndsAt: now } }, content),
|
|
).toEqual({
|
|
ok: true,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("duty in resolution (Step 10D.3)", () => {
|
|
const record = createDispatchRecord({ ...proposal, id: "d1", worldSeed: 7 }, content);
|
|
const hours = tripGameHours(record.departureAt, record.arrivalAt, content.config);
|
|
|
|
it("adds the trip's hours to duty when under the limit", () => {
|
|
expect(resolveAt(record, 1)).toMatchObject({
|
|
pilotDutyHours: 1 + hours,
|
|
pilotRestEndsAt: null,
|
|
});
|
|
});
|
|
|
|
it("starts rest at the arrival time when duty reaches the limit, even resolved hours later", () => {
|
|
const late = record.arrivalAt + 5 * MS_PER_HOUR;
|
|
expect(resolveAt(record, limit - hours / 2, late)).toMatchObject({
|
|
pilotDutyHours: 0,
|
|
pilotRestEndsAt: restEndsAt(record.arrivalAt, content.config),
|
|
});
|
|
expect(restEndsAt(record.arrivalAt, content.config)).toBe(
|
|
record.arrivalAt +
|
|
(content.config.restGameHours * MS_PER_HOUR) / content.config.timeCompression,
|
|
);
|
|
});
|
|
|
|
it("counts deadheads but not tutorial runs", () => {
|
|
const deadhead = createRepositionRecord(
|
|
{ id: "r1", ship, pilot, destinationId: "moon", now, worldSeed: 7 },
|
|
content,
|
|
);
|
|
const deadheadHours = tripGameHours(deadhead.departureAt, deadhead.arrivalAt, content.config);
|
|
expect(resolveAt(deadhead, 2).pilotDutyHours).toBeCloseTo(2 + deadheadHours);
|
|
|
|
const tutorial = createDispatchRecord(
|
|
{ ...proposal, tutorial: true, pilot: { ...pilot, isAvatar: true }, id: "t1", worldSeed: 7 },
|
|
content,
|
|
);
|
|
expect(resolveAt(tutorial, limit - 0.1)).toMatchObject({
|
|
pilotDutyHours: limit - 0.1,
|
|
pilotRestEndsAt: null,
|
|
});
|
|
});
|
|
});
|