deadhead-space-freight-tycoon/packages/engine/test/completable-boards.test.ts

151 lines
6.3 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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"]);
});
});