deadhead-space-freight-tycoon/packages/content/test/events.test.ts

120 lines
4.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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,
});
});
});