import { content } from "@deadhead/content"; import type { LogbookResponse, LogEntry, Pilot, PlayerState, Ship } from "@deadhead/schema"; import { effectiveStats } from "@deadhead/sim"; // Plain data builders shared by component tests and the Playwright tests (no Vitest imports). /** * The CR-009 fields of a ship with no upgrades and not in the shop: its model's * stats as effective stats. Spread into Ship fixtures, then override as needed. */ export function shipExtras( modelId: string, upgrades: Record = {}, ): Pick< Ship, | "upgrades" | "stats" | "inShopUntil" | "shop" | "fuelFu" | "fuelAvgPrice" | "refuelPolicy" | "hullConditionPct" | "repairing" > { const model = content.shipModel(modelId); if (!model) throw new Error(`Unknown ship model ${modelId}`); const s = effectiveStats(model, upgrades, content); return { upgrades, stats: { cruiseSpeedKmPerGameHour: s.cruiseSpeedKmPerGameHour, cargoCapacityTonnes: s.cargoCapacityTonnes, tankCapacityFu: s.tankCapacityFu, burnFuPerMillionKm: s.burnFuPerMillionKm, overheadGameHours: s.overheadGameHours, handlingGameHoursPerTonne: s.handlingGameHoursPerTonne, }, inShopUntil: null, shop: null, // A full tank bought at Earth (CR-015). fuelFu: s.tankCapacityFu, fuelAvgPrice: 16, refuelPolicy: "auto", // A whole hull, not in for repair (CR-026). hullConditionPct: 100, repairing: false, }; } /** CR-021 and CR-023 fields of a Manual pilot with default orders, home at Earth, not in the shop. Spread into Pilot fixtures. */ export const pilotExtras: Pick = { mode: "manual", restingInShopUntil: null, orders: { destinationIds: null, cargoClassIds: content.cargoTypes.map((c) => c.id), takePriority: true, minProfit: 0, homeLocationId: "earth", }, }; /** A brand-new tutorial player, as guest sign-up returns it. */ export function samplePlayerState(overrides: Partial = {}): PlayerState { return { playerId: "player-1", company: { id: "co1", name: "Deadhead Freight", credits: 0, loanBalance: 0 }, ships: [], pilots: [ { id: "avatar", name: "Captain", isAvatar: true, isVeteran: false, xp: 0, level: 0, skills: {}, skillPointsAvailable: 0, assignedShipId: null, status: "idle", locationId: "earth", dutyHours: 0, dutyLimit: 12, resting: false, restEndsAt: null, ...pilotExtras, }, ], bays: [], locations: [ { id: "earth", discovered: true }, { id: "moon", discovered: true }, { id: "mars", discovered: false }, { id: "ceres-station", discovered: false }, { id: "jupiter-cloud-platform", discovered: false }, ], tutorial: { loanerLocationId: "earth", runInFlight: false, runsCompleted: 0, dealAvailable: false, }, activeDispatches: [], summaries: [], goals: [], dispatchOffice: { owned: false, spendingFloor: 500 }, // Awake: a day after the usual test time (CR-024). radioCalls: [], fleetSleep: { afterHours: 24, sleepsAt: Date.parse("2100-01-02T00:00:00Z"), asleep: false }, welcomeSeen: true, ...overrides, }; } /** * The logbook a state's unseen summaries and finished upgrades stand for * (CR-025): what the engine's `getLogbook` would return. `fakeApi` answers * `getLogbook` with it unless a test gives its own. */ export function logbookFrom(state: PlayerState): LogbookResponse { const entries: LogEntry[] = []; for (const s of state.summaries) { const who = { at: s.resolvedAt, pilotId: s.pilotId, shipId: s.shipId }; entries.push({ id: `${s.dispatchId}:flight`, ...who, kind: "flight", dispatchId: s.dispatchId, deadhead: s.deadhead, tutorial: s.tutorial, independent: s.independent, originId: s.originId, destinationId: s.destinationId, goodId: s.goodId, massTonnes: s.massTonnes, shipper: s.shipper, payout: Math.max(0, s.credits + s.pilotPay + s.loanRepayment), pilotPay: s.pilotPay, fuelCost: s.fuelCost, loanRepayment: s.loanRepayment, xp: s.pilotXp, scriptedId: s.scriptedId, }); if (s.restEndsAt !== null) { entries.push({ id: `${s.dispatchId}:rest`, ...who, kind: "rest", until: s.restEndsAt }); } if (s.discoveredLocationId) { entries.push({ id: `${s.dispatchId}:discovery`, ...who, kind: "discovery", locationId: s.discoveredLocationId, }); } } for (const ship of state.ships) { if (!ship.shop || ship.shop.until > Date.now()) continue; entries.push({ id: `${ship.id}:upgrade`, at: ship.shop.until, pilotId: ship.pilotId, shipId: ship.id, kind: "upgrade", upgradeId: ship.shop.upgradeId, level: ship.shop.level, }); } for (const goal of state.goals) { if (goal.status !== "ready") continue; entries.push({ id: `goal:${goal.id}`, at: Date.now(), pilotId: null, shipId: null, kind: "goal-ready", goalId: goal.id, }); } const from = Math.min(Date.now(), ...entries.map((e) => e.at)) - 60_000; return { entries: entries.sort((a, b) => a.at - b.at), absence: { from, to: Math.max(Date.now(), ...entries.map((e) => e.at)) }, lastAbsence: null, problems: [], }; }