151 lines
6.3 KiB
TypeScript
151 lines
6.3 KiB
TypeScript
import { content } from "@deadhead/content";
|
||
import { boardPeriodMs } from "@deadhead/sim";
|
||
import { describe, expect, it } from "vitest";
|
||
import { boardFleet } from "../src/game/boards";
|
||
import { encodeSave } from "../src/index";
|
||
import {
|
||
addPilot,
|
||
addShip,
|
||
readSave,
|
||
setCredits,
|
||
signUpOwner,
|
||
TEST_NOW,
|
||
useTestApp,
|
||
} from "./support";
|
||
|
||
const testApp = useTestApp();
|
||
const COURIER_HOLD = content.shipModel("courier")!.cargoCapacityTonnes;
|
||
const PERIOD = boardPeriodMs(content.config);
|
||
|
||
describe("completable boards in the engine (Step 19.3, decisions 0.5.114–0.5.117)", () => {
|
||
it("hides a heavy job once the only ship that could take it has left", async () => {
|
||
const { app, clock } = testApp();
|
||
await signUpOwner(app);
|
||
await addShip(app, { id: "f1", modelId: "freighter", name: "Big Wren" });
|
||
await addPilot(app, { id: "p1", name: "Ada", assignedShipId: "f1" });
|
||
|
||
const before = (await app.call("getContractBoard", { locationId: "earth" })).contracts;
|
||
const heavy = before.filter((c) => c.massTonnes > COURIER_HOLD);
|
||
expect(heavy.length).toBeGreaterThan(0);
|
||
|
||
// The Freighter flies empty to the Moon; the Courier is still at Earth.
|
||
await app.call("createDispatch", {
|
||
kind: "reposition",
|
||
shipId: "f1",
|
||
pilotId: "p1",
|
||
destinationId: "moon",
|
||
});
|
||
clock.now += 1_000;
|
||
const after = (await app.call("getContractBoard", { locationId: "earth" })).contracts;
|
||
expect(after.filter((c) => c.massTonnes > COURIER_HOLD)).toEqual([]);
|
||
// What the Courier can take is still there, unchanged.
|
||
expect(after).toEqual(before.filter((c) => c.massTonnes <= COURIER_HOLD));
|
||
});
|
||
|
||
it("adds a bigger ship's jobs at the next board period, not mid-period", async () => {
|
||
const { app, clock } = testApp();
|
||
const owner = await signUpOwner(app);
|
||
const first = (await app.call("getContractBoard", { locationId: "earth" })).contracts;
|
||
expect(first.every((c) => c.massTonnes <= COURIER_HOLD)).toBe(true);
|
||
|
||
await setCredits(app, content.shipModel("freighter")!.price);
|
||
const { ship } = await app.call("buyShip", { modelId: "freighter", bayId: owner.earthBayId });
|
||
await addPilot(app, { id: "p1", name: "Ada", assignedShipId: ship.id });
|
||
clock.now += 1_000;
|
||
expect((await app.call("getContractBoard", { locationId: "earth" })).contracts).toEqual(first);
|
||
|
||
// Next period: the board is made for the Courier and the Freighter.
|
||
clock.now = (Math.floor(TEST_NOW / PERIOD) + 1) * PERIOD + 1_000;
|
||
let heavy = 0;
|
||
for (let period = 0; period < 5; period++, clock.now += PERIOD) {
|
||
const board = (await app.call("getContractBoard", { locationId: "earth" })).contracts;
|
||
heavy += board.filter((c) => c.massTonnes > COURIER_HOLD).length;
|
||
}
|
||
expect(heavy).toBeGreaterThan(0);
|
||
});
|
||
|
||
it("stores the qualifying fleet with each board it generates", async () => {
|
||
const { app } = testApp();
|
||
await signUpOwner(app);
|
||
await app.call("getContractBoard", { locationId: "earth" });
|
||
const [board] = (await readSave(app)).contractBoards.filter((b) => b.kind === "location");
|
||
expect(board!.fleet).toEqual([
|
||
{
|
||
modelId: "courier",
|
||
skills: {},
|
||
upgrades: {},
|
||
fuelFu: content.shipModel("courier")!.tankCapacityFu,
|
||
},
|
||
]);
|
||
});
|
||
|
||
it("opens an older save with no stored boards, and regenerates them on view", async () => {
|
||
const { app } = testApp();
|
||
await signUpOwner(app);
|
||
const before = (await app.call("getContractBoard", { locationId: "earth" })).contracts;
|
||
const old = await readSave(app);
|
||
expect(old.contractBoards.length).toBeGreaterThan(0);
|
||
// As version 3 wrote it: boards without a fleet snapshot.
|
||
const v3 = {
|
||
...old,
|
||
saveVersion: 3,
|
||
contractBoards: old.contractBoards.map(({ fleet: _fleet, ...rest }) => rest),
|
||
};
|
||
|
||
const device = testApp().app;
|
||
await device.call("importSave", { code: await encodeSave(v3 as never) });
|
||
expect((await readSave(device)).contractBoards).toEqual([]);
|
||
// The same inputs regenerate the same board.
|
||
expect((await device.call("getContractBoard", { locationId: "earth" })).contracts).toEqual(
|
||
before,
|
||
);
|
||
expect((await readSave(device)).contractBoards).toHaveLength(1);
|
||
});
|
||
});
|
||
|
||
describe("boards ignore Independent pilots (decision 0.5.133)", () => {
|
||
async function withIndependentFreighter() {
|
||
const { app, clock } = testApp();
|
||
await signUpOwner(app);
|
||
await addShip(app, { id: "f1", modelId: "freighter", name: "Big Wren" });
|
||
await addPilot(app, { id: "p1", name: "Rosa", assignedShipId: "f1" });
|
||
await setCredits(app, content.config.independentPilots.dispatchOfficePrice);
|
||
await app.call("buyDispatchOffice", {});
|
||
return { app, clock };
|
||
}
|
||
|
||
it("shows only jobs a Manual pilot can fly once Rosa goes Independent", async () => {
|
||
const { app, clock } = await withIndependentFreighter();
|
||
const before = (await app.call("getContractBoard", { locationId: "earth" })).contracts;
|
||
expect(before.some((c) => c.massTonnes > COURIER_HOLD)).toBe(true);
|
||
|
||
await app.call("setPilotMode", { pilotId: "p1", mode: "independent" });
|
||
// This period: the Freighter's jobs are hidden at once.
|
||
const now = (await app.call("getContractBoard", { locationId: "earth" })).contracts;
|
||
expect(now.filter((c) => c.massTonnes > COURIER_HOLD)).toEqual([]);
|
||
// Later periods are generated for the player's Courier alone.
|
||
for (let period = 0; period < 5; period++) {
|
||
clock.now += PERIOD;
|
||
const board = (await app.call("getContractBoard", { locationId: "earth" })).contracts;
|
||
expect(board.length).toBeGreaterThan(0);
|
||
expect(board.filter((c) => c.massTonnes > COURIER_HOLD)).toEqual([]);
|
||
}
|
||
});
|
||
|
||
it("falls back to the whole fleet when every pair is Independent", async () => {
|
||
const { app } = await withIndependentFreighter();
|
||
await app.call("setPilotMode", { pilotId: "p1", mode: "independent" });
|
||
const save = await readSave(app);
|
||
// No Manual pair left: only Rosa and her Freighter.
|
||
const alone = {
|
||
...save,
|
||
ships: save.ships.filter((s) => s.id === "f1"),
|
||
pilots: save.pilots.filter((p) => p.id === "p1"),
|
||
};
|
||
expect(boardFleet(alone, "earth", TEST_NOW + PERIOD).map((m) => m.modelId)).toEqual([
|
||
"freighter",
|
||
]);
|
||
expect(boardFleet(save, "earth", TEST_NOW + PERIOD).map((m) => m.modelId)).toEqual(["courier"]);
|
||
});
|
||
});
|