99 lines
3.8 KiB
TypeScript
99 lines
3.8 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
content,
|
|
dealLoanAmount,
|
|
gameConfig,
|
|
gameConfigSchema,
|
|
locations,
|
|
shipModels,
|
|
skills,
|
|
} from "../src/index";
|
|
|
|
describe("game config", () => {
|
|
it("passes the schema", () => {
|
|
expect(() => gameConfigSchema.parse(gameConfig)).not.toThrow();
|
|
});
|
|
|
|
it("uses 1 real minute = 1 game hour", () => {
|
|
expect(gameConfig.timeCompression).toBe(60);
|
|
});
|
|
|
|
it("has one Long Haul cut-off per Long Haul level", () => {
|
|
const longHaul = skills.find((s) => s.id === "long-haul")!;
|
|
expect(gameConfig.longHaulCutoffsMillionKm).toHaveLength(longHaul.maxLevel);
|
|
});
|
|
|
|
// The "fuel for three Courier runs after the deal" check (Step 2.5 as amended
|
|
// by CR-001) runs on measured numbers in packages/sim/test/balance.test.ts (Step 12.2).
|
|
|
|
it("sizes the tutorial board and loaner for the three mass tiers", () => {
|
|
expect(gameConfig.tutorialBoardSize).toBe(3);
|
|
const loaner = shipModels.find((s) => s.id === gameConfig.tutorialLoanerShipModelId)!;
|
|
expect(loaner.purchasable).toBe(false);
|
|
expect(gameConfig.tutorialMassTonnes.full).toBeLessThanOrEqual(loaner.cargoCapacityTonnes);
|
|
});
|
|
|
|
it("lends the Courier and Clipper prices plus an Earth bay lease in the deal (0.5.83)", () => {
|
|
const price = (id: string) => shipModels.find((s) => s.id === id)!.price;
|
|
const earth = locations.find((l) => l.id === "earth")!;
|
|
expect(gameConfig.deal).toEqual({
|
|
avatarShipModelId: "courier",
|
|
veteranShipModelId: "clipper",
|
|
bayLocationId: "earth",
|
|
});
|
|
expect(content.dealLoanAmount).toBe(price("courier") + price("clipper") + earth.bayLeasePrice);
|
|
});
|
|
|
|
it("recomputes the deal loan when any of those prices change", () => {
|
|
const base = dealLoanAmount({ config: gameConfig, shipModels, locations });
|
|
const bump = (id: string) =>
|
|
shipModels.map((s) => (s.id === id ? { ...s, price: s.price + 100 } : s));
|
|
expect(dealLoanAmount({ config: gameConfig, shipModels: bump("courier"), locations })).toBe(
|
|
base + 100,
|
|
);
|
|
expect(dealLoanAmount({ config: gameConfig, shipModels: bump("clipper"), locations })).toBe(
|
|
base + 100,
|
|
);
|
|
const pricierEarth = locations.map((l) =>
|
|
l.id === "earth" ? { ...l, bayLeasePrice: l.bayLeasePrice + 7 } : l,
|
|
);
|
|
expect(dealLoanAmount({ config: gameConfig, shipModels, locations: pricierEarth })).toBe(
|
|
base + 7,
|
|
);
|
|
});
|
|
|
|
it("has a contract mass band each purchasable ship can carry", () => {
|
|
for (const ship of shipModels.filter((s) => s.purchasable)) {
|
|
expect(
|
|
gameConfig.contractMassBands.some((band) => band.max <= ship.cargoCapacityTonnes),
|
|
).toBe(true);
|
|
}
|
|
const total = gameConfig.contractMassBands.reduce((sum, b) => sum + b.count, 0);
|
|
expect(total).toBe(gameConfig.contractBoardSize);
|
|
});
|
|
|
|
it("rests a pilot for at least one contract board period of real time (CR-004)", () => {
|
|
const restRealMinutes = (gameConfig.restGameHours * 60) / gameConfig.timeCompression;
|
|
expect(restRealMinutes).toBeGreaterThanOrEqual(gameConfig.contractBoardPeriodRealMinutes);
|
|
});
|
|
|
|
it("has positive duty numbers (CR-004)", () => {
|
|
expect(gameConfig.dutyLimitGameHours).toBeGreaterThan(0);
|
|
expect(gameConfig.longHaulDutyBonusGameHours).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
|
|
describe("tutorial settings (Step 17.1, decisions 0.5.106, 0.5.107)", () => {
|
|
it("runs five tutorial runs before the deal, at double XP", () => {
|
|
expect(gameConfig.tutorialRunsBeforeDeal).toBe(5);
|
|
expect(gameConfig.tutorialXpMultiplier).toBe(2);
|
|
});
|
|
|
|
it("never lets the tutorial XP multiplier fall below 1", () => {
|
|
expect(gameConfigSchema.safeParse(gameConfig).success).toBe(true);
|
|
expect(gameConfigSchema.safeParse({ ...gameConfig, tutorialXpMultiplier: 0.5 }).success).toBe(
|
|
false,
|
|
);
|
|
});
|
|
});
|