import { content } from "@deadhead/content"; import { describe, expect, it } from "vitest"; import { upgradeCost, upgradeShopMs, validateDispatch, validateUpgrade, type Contract, type PilotState, type ShipState, type UpgradeProposal, } from "../src/index"; /** Fuel enough for any leg in these tests (decision 0.5.94), bought at 10 cr/fu. */ const PLENTY = { fuelFu: 10_000, avgPrice: 10 }; const now = Date.parse("2100-01-01T00:30:00Z"); const freighter = content.shipModel("freighter")!; const engines = content.upgrade("engines")!; const ship: ShipState = { id: "s1", modelId: "freighter", locationId: "earth", inFlight: false, tank: PLENTY, }; const valid: UpgradeProposal = { ship, upgrade: engines, atHq: true, tutorialActive: false, credits: 1_000_000, now, }; const reason = (proposal: UpgradeProposal) => { const result = validateUpgrade(proposal, content); return result.ok ? "valid" : result.reason; }; describe("upgrade cost and shop time (Step 10I.3)", () => { it("costs 10 % of a Freighter's price at level 1 and 35 % at level 3", () => { expect(upgradeCost(freighter, 1, content.config)).toBe(freighter.price * 0.1); expect(upgradeCost(freighter, 3, content.config)).toBe(freighter.price * 0.35); expect(upgradeShopMs(2, content.config)).toBe(20 * 60_000); }); it("starts the next level, with its cost and shop time", () => { expect( validateUpgrade({ ...valid, ship: { ...ship, upgrades: { engines: 1 } } }, content), ).toEqual({ ok: true, level: 2, cost: upgradeCost(freighter, 2, content.config), shopUntil: now + upgradeShopMs(2, content.config), }); }); }); describe("upgrade validation (Step 10I.3)", () => { it("accepts a valid upgrade", () => expect(reason(valid)).toBe("valid")); it("refuses during the tutorial", () => expect(reason({ ...valid, tutorialActive: true })).toBe("tutorial-active")); it("refuses the company loaner", () => expect(reason({ ...valid, ship: { ...ship, modelId: "company-courier" } })).toBe( "not-upgradable", )); it("refuses a ship in flight", () => expect(reason({ ...valid, ship: { ...ship, inFlight: true } })).toBe("ship-not-idle")); it("refuses a ship already in the shop", () => expect(reason({ ...valid, ship: { ...ship, inShopUntil: now + 1000 } })).toBe("ship-in-shop")); it("refuses away from an HQ", () => expect(reason({ ...valid, atHq: false })).toBe("not-at-hq")); it("refuses a maxed upgrade", () => expect(reason({ ...valid, ship: { ...ship, upgrades: { engines: 3 } } })).toBe( "upgrade-maxed", )); it("refuses without the credits", () => expect(reason({ ...valid, credits: upgradeCost(freighter, 1, content.config) - 1 })).toBe( "insufficient-funds", )); }); describe("ships in the shop can't fly (Step 10I.3)", () => { const pilot: PilotState = { id: "p1", isAvatar: false, assignedShipId: "s1", inFlight: false, skills: {}, restEndsAt: null, }; 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: now, expiresAt: now + 3_600_000, firstContact: false, }; const at = (time: number, shopUntil: number) => validateDispatch( { ship: { ...ship, inShopUntil: shopUntil }, pilot, contract, tutorial: false, now: time, }, content, ); it("fails ship-in-shop one second before in-shop-until and passes at it", () => { const until = now + 10 * 60_000; expect(at(until - 1000, until)).toEqual({ ok: false, reason: "ship-in-shop" }); expect(at(until, until)).toEqual({ ok: true }); }); });