import { content } from "@deadhead/content"; import { boardPeriodMs } from "@deadhead/sim"; import { describe, expect, it } from "vitest"; 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); }); });