import { describe, expect, it } from "vitest"; import { content, gameConfig, tripEventSchema, tripEvents } from "../src/index"; describe("random events content (Step 26.1, decisions 0.5.145–0.5.149)", () => { it("has the catalog's fourteen events, all valid, with unique ids", () => { for (const event of tripEvents) { const parsed = tripEventSchema.safeParse(event); expect(parsed.success, event.id).toBe(true); } expect(tripEvents.map((e) => e.id)).toEqual([ "solar-flare", "micrometeor-strike", "engine-hiccup", "debris-field", "storm-approach", "customs-hold", "hazmat-leak", "load-shift", "spoilage-risk", "solar-tailwind", "grateful-client", "derelict-pod", "distress-beacon", "spot-freight", ]); expect(content.tripEvent("derelict-pod")?.kind).toBe("radio"); }); it("gives every radio call two or three options and exactly one default", () => { for (const event of tripEvents.filter((e) => e.kind === "radio")) { expect(event.options.length, event.id).toBeGreaterThanOrEqual(2); expect( event.options.filter((o) => o.default), event.id, ).toHaveLength(1); } const twoDefaults = { ...content.tripEvent("debris-field")!, options: content.tripEvent("debris-field")!.options.map((o) => ({ ...o, default: true })), }; expect(tripEventSchema.safeParse(twoDefaults).success).toBe(false); }); it("keeps every option's chances to at most 100%", () => { for (const event of tripEvents) { for (const option of event.options) { const total = option.chances.reduce((sum, c) => sum + c.chance, 0); expect(total, `${event.id}/${option.id}`).toBeLessThanOrEqual(1); } } const tooMuch = { ...content.tripEvent("debris-field")!, options: [ content.tripEvent("debris-field")!.options[0]!, { ...content.tripEvent("debris-field")!.options[1]!, chances: [ { chance: 0.7, effects: {} }, { chance: 0.4, effects: {} }, ], }, ], }; expect(tripEventSchema.safeParse(tooMuch).success).toBe(false); }); it("weights about one good event for every two bad (28–38% good)", () => { const total = tripEvents.reduce((sum, e) => sum + e.weight, 0); const good = tripEvents.filter((e) => e.good).reduce((sum, e) => sum + e.weight, 0); expect(good / total).toBeGreaterThanOrEqual(0.28); expect(good / total).toBeLessThanOrEqual(0.38); }); it("only names places, goods and skills that exist", () => { for (const event of tripEvents) { const { arrivalIds, priorityGoods, skillHalving, cargoClassId } = event.when; for (const id of arrivalIds ?? []) expect(content.location(id), id).toBeDefined(); for (const id of priorityGoods ?? []) expect(content.good(id), id).toBeDefined(); if (skillHalving) expect(content.skill(skillHalving), skillHalving).toBeDefined(); if (cargoClassId) expect(content.cargoTypes.some((c) => c.id === cargoClassId)).toBe(true); } expect(content.tripEvent(gameConfig.events.firstEventId)?.good).toBe(true); }); it("sets the CR's pacing, hull and repair numbers, and a region for every place", () => { expect(gameConfig.events).toMatchObject({ baseChance: 0.02, chancePerGameHour: 0.004, maxChance: 0.35, shipCooldownTrips: 2, fleetRadioGapRealMinutes: 45, graceTripsAfterDeal: 3, responseWindowRealMinutes: 30, }); expect(gameConfig.hull).toMatchObject({ floorPct: 5, noDangerousBelowPct: 50, needsRepairBelowPct: 25, }); expect(content.locations.map((l) => [l.id, l.region])).toEqual([ ["earth", "inner"], ["moon", "inner"], ["mars", "inner"], ["ceres-station", "belt"], ["jupiter-cloud-platform", "jupiter"], ["venus-cloud-city", "inner"], ["io-sulfur-works", "jupiter"], ["europa-ice-station", "jupiter"], ]); }); it("adds Hull Plating: +1 hull rating a level, three levels", () => { expect(content.upgrade("hull-plating")).toMatchObject({ stat: "hullRating", flatPerLevel: 1, maxLevel: 3, }); }); });