import { content } from "@deadhead/content"; import { describe, expect, it } from "vitest"; import { affordableRefuel, createDispatchRecord, fuelPriceAt, legFuel, legFuelCost, pilotShare, refuel, resolveDispatch, routeDistance, type Contract, type PilotState, type ResolveInput, type ShipState, } from "../src/index"; const { config } = content; const courier = content.shipModel("courier")!; const [earth, moon] = [content.location("earth")!, content.location("moon")!]; const now = Date.parse("2100-01-01T00:30:00Z"); const MOON_PRICE = fuelPriceAt(moon, config); 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 pilot: PilotState = { id: "p1", isAvatar: false, assignedShipId: "s1", inFlight: false, skills: {}, restEndsAt: null, }; /** A Courier at Earth with a full tank bought at Earth's price. */ const ship: ShipState = { id: "s1", modelId: "courier", locationId: "earth", inFlight: false, tank: { fuelFu: courier.tankCapacityFu, avgPrice: fuelPriceAt(earth, config) }, }; const record = createDispatchRecord( { id: "d1", ship, pilot, contract, tutorial: false, now, worldSeed: 7 }, content, ); /** The tank on landing: what was left after the leg's fuel came out at departure. */ const landedTank = { ...ship.tank!, fuelFu: ship.tank!.fuelFu - record.fuelBurnedFu }; function landing(policy: "auto" | "manual", credits: number): ResolveInput { return { record, pilotIsAvatar: false, discoveredIds: new Set(["earth", "moon"]), loanBalance: 0, pilotDutyHours: 0, pilotSkills: {}, now: record.arrivalAt, fuel: { tank: landedTank, capacityFu: courier.tankCapacityFu, refuelPolicy: policy, credits }, }; } function resolve(input: ResolveInput) { const result = resolveDispatch(input, content); if (result.status !== "resolved") throw new Error("expected a resolution"); return result.resolution; } describe("fuel tanks (Step 15.4)", () => { it("burns less from the Moon: the leg is shortened by 1.3 before the pay curve (0.5.95)", () => { const d = routeDistance(earth, moon); // The CR's ÷ 1.3, applied before the economic-distance curve (user decision). expect(legFuel(courier, moon, d, config)).toBeCloseTo(legFuel(courier, earth, d / 1.3, config)); expect(legFuel(courier, moon, d, config)).toBeCloseTo( legFuel(courier, earth, d, config) / 1.3 ** config.economicDistance.exponent, ); }); it("averages the price of what is in the tank", () => { const filled = refuel({ fuelFu: 50, avgPrice: 2 }, 200, 50, 1); expect(filled.tank).toEqual({ fuelFu: 100, avgPrice: 1.5 }); expect(filled.boughtFu).toBe(50); expect(filled.cost).toBe(50); }); it("never fills past capacity", () => { const filled = refuel({ fuelFu: 90, avgPrice: 2 }, 100, 50, 1); expect(filled.boughtFu).toBe(10); expect(filled.tank.fuelFu).toBe(100); }); it("buys exactly what the credits cover, up to a full tank", () => { // Half a tank's worth of credits at 10 cr/fu. const half = affordableRefuel({ fuelFu: 0, avgPrice: 0 }, 100, 10, 500); expect(half.boughtFu).toBe(50); expect(half.cost).toBe(500); expect(affordableRefuel({ fuelFu: 0, avgPrice: 0 }, 100, 10, 5_000).boughtFu).toBe(100); expect(affordableRefuel({ fuelFu: 0, avgPrice: 0 }, 100, 10, 0).boughtFu).toBe(0); }); it("takes the leg's fuel from the tank and costs it at the tank's average price", () => { const burned = legFuel(courier, earth, routeDistance(earth, moon), config); expect(record.fuelBurnedFu).toBeCloseTo(burned); expect(record.fuelCost).toBe(legFuelCost(burned, ship.tank!)); expect(record.fuelCost).toBe(Math.round(burned * 16)); }); it("pays a hired pilot from fuel burned × average price (decision 0.5.98)", () => { expect(resolve(landing("manual", 0)).pilotPay).toBe( pilotShare(contract.payout, record.fuelCost, config), ); }); it("tops up to a full tank on landing with Auto, paying the local price", () => { const { topUp } = resolve(landing("auto", 10_000)); expect(topUp).not.toBeNull(); expect(topUp!.locationId).toBe("moon"); expect(topUp!.tank.fuelFu).toBeCloseTo(courier.tankCapacityFu); expect(topUp!.boughtFu).toBeCloseTo(record.fuelBurnedFu); expect(topUp!.price).toBe(MOON_PRICE); expect(topUp!.cost).toBe(Math.round(record.fuelBurnedFu * MOON_PRICE)); }); it("buys nothing with Manual", () => { expect(resolve(landing("manual", 10_000)).topUp).toBeNull(); }); it("buys only what the credits after this delivery cover", () => { const delivery = resolve(landing("manual", 0)).credits; // Credits that, with this delivery's pay, cover half the fuel burned. const halfCost = (record.fuelBurnedFu / 2) * MOON_PRICE; const { topUp } = resolve(landing("auto", halfCost - delivery)); expect(topUp!.boughtFu).toBeCloseTo(record.fuelBurnedFu / 2); expect(topUp!.cost).toBeLessThanOrEqual(Math.floor(halfCost)); }); it("leaves the tutorial loaner's fuel alone", () => { const loaner: ShipState = { id: "loaner", modelId: "company-courier", locationId: "earth", inFlight: false, }; const avatar = { ...pilot, id: "avatar", isAvatar: true, assignedShipId: null }; const tutorialRun = createDispatchRecord( { id: "t1", ship: loaner, pilot: avatar, contract, tutorial: true, now, worldSeed: 7 }, content, ); expect(tutorialRun.fuelBurnedFu).toBe(0); expect(tutorialRun.fuelCost).toBe(0); expect(resolve({ ...landing("auto", 10_000), record: tutorialRun }).topUp).toBeNull(); }); });