import type { Dispatch } from "@deadhead/schema"; import { describe, expect, it } from "vitest"; import { DEFAULT_CAMERA, toScreen, zoomAt } from "../src/map/camera"; import { buildScene, flightMarker } from "../src/map/scene"; import { samplePlayerState } from "./render"; const NOW = Date.parse("2100-01-01T00:00:00Z"); const flight: Dispatch = { id: "d1", shipId: "s1", pilotId: "p1", contract: null, originId: "earth", destinationId: "mars", tutorial: false, departureAt: NOW - 3_600_000, arrivalAt: NOW + 3_600_000, distanceMillionKm: 150, fuelCost: 0, expectedPayout: 0, seed: 1, status: "in-flight", resolvedAt: null, }; describe("ship markers", () => { it("puts a ship at 50% progress on the route line between the endpoints, on screen", () => { const viewport = { width: 375, height: 690 }; const camera = zoomAt(DEFAULT_CAMERA, 2.5, { x: 100, y: 200 }, viewport); const scene = flightMarker(flight, NOW); expect(scene.progress).toBe(0.5); const a = toScreen(scene.from, viewport, camera); const b = toScreen(scene.to, viewport, camera); const m = toScreen(scene.marker, viewport, camera); // On the line: the cross product of (b - a) and (m - a) is zero. const cross = (b.x - a.x) * (m.y - a.y) - (b.y - a.y) * (m.x - a.x); expect(Math.abs(cross)).toBeLessThan(1e-6); // Between the endpoints, halfway along. expect(Math.hypot(m.x - a.x, m.y - a.y)).toBeCloseTo(Math.hypot(b.x - a.x, b.y - a.y) / 2, 6); }); it("starts at the origin and ends at the destination", () => { expect(flightMarker(flight, flight.departureAt).marker).toEqual(flightMarker(flight, NOW).from); const end = flightMarker(flight, flight.arrivalAt + 60_000); expect(end.marker.x).toBeCloseTo(end.to.x, 12); expect(end.marker.y).toBeCloseTo(end.to.y, 12); }); it("builds one flight per active dispatch and badges idle ships at their location", () => { const state = samplePlayerState({ tutorial: null, activeDispatches: [flight], ships: [ { id: "s1", modelId: "courier", name: "A", status: "in-flight", locationId: null, dispatchId: "d1", lastArrivalAt: 0, pilotId: "p1", }, { id: "s2", modelId: "courier", name: "B", status: "idle", locationId: "moon", dispatchId: null, lastArrivalAt: 0, pilotId: null, }, { id: "s3", modelId: "courier", name: "C", status: "idle", locationId: "moon", dispatchId: null, lastArrivalAt: 0, pilotId: null, }, ], }); const scene = buildScene(state, NOW); expect(scene.flights.map((f) => f.dispatchId)).toEqual(["d1"]); expect(scene.locations.find((l) => l.id === "moon")!.idleShips).toBe(2); expect(scene.locations.find((l) => l.id === "earth")!.idleShips).toBe(0); }); it("counts the idle tutorial loaner at its location", () => { const scene = buildScene(samplePlayerState(), NOW); expect(scene.locations.find((l) => l.id === "earth")!.idleShips).toBe(1); }); });