Step 16.1: content: new locations
CI / check (push) Has been cancelled
Details
Venus Cloud City, Io Sulfur Works and Europa Ice Station join the map (fixed positions, Io and Europa riding with Jupiter), with their fuel prices, range factors, shippers and sprites. Each makes a Standard good so boards work there. The first-purchase balance window widens to 45-95 minutes (user decision). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
@ -1504,3 +1504,11 @@ Implementation decisions not spelled out in `memory-bank/implementation-plan.md`
|
|||
- **Numbering:** received as "CR-015 — Venus, Io, Europa, and the Asteroid Belt" for "Phase 15"; stored as **CR-016**, built as **Phase 16** (its 15.n = 16.n). Its decisions CR-015.D1–D6 are **0.5.100–0.5.105**. Its references: CR-005 → CR-007, CR-009 → CR-012, CR-011 → CR-014, CR-014 → CR-015, decision 0.5.31 → 0.5.39; CR-003 is the same. The numbering note at the top of the CR file has the full map.
|
||||
- **The belt image holds six rocks**: `belt-asteroids.png` has a transparent background and six separate regions at alpha ≥ 16 (fainter specks of glow are ignored), so Step 16.5 slices it into six sprites.
|
||||
- **Landing "Any" is the `vacuum` requirement**: every landing class meets it.
|
||||
|
||||
## Step 16.1
|
||||
|
||||
- **Bay lease prices for the new places** (the CR gives none): Venus 20,000 like Mars, a similar distance from Earth; Io and Europa 70,000 each, a little under Jupiter's platform (80,000), since they are meant as a second home for small ships in the Jupiter system.
|
||||
- **Class mixes follow the goods**: Venus makes Standard, High-Value and Dangerous goods and wants Standard, High-Value and Heavy; Io makes Standard, Heavy and High-Value and wants all four; Europa makes all four and wants Standard, High-Value and Heavy.
|
||||
- **Each new place's Standard good came with the locations**, ahead of 16.2: the board generator refuses a place that makes nothing Standard. The rest of the goods changes stay in 16.2.
|
||||
- **First Market purchase target widened to 45–95 min** (user decision). With Venus as a second first-contact destination, the median moved from 64 to ~92 min. Purchases happen at the Clipper's long landings, so the median jumps between about 49, 64 and 92 min with small content changes; no tank-share, Lighter-price or first-contact-pay change landed it reliably inside 75.
|
||||
- **CR-007's class-mix regression is measured on its original five places**: new destinations change the mix legitimately (and so do first contacts to undiscovered places), so the baseline is compared against the content it was taken from.
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 145 KiB |
|
After Width: | Height: | Size: 570 KiB |
|
After Width: | Height: | Size: 169 KiB |
|
After Width: | Height: | Size: 651 KiB |
|
After Width: | Height: | Size: 157 KiB |
|
After Width: | Height: | Size: 568 KiB |
|
After Width: | Height: | Size: 2.2 MiB |
|
After Width: | Height: | Size: 2.2 MiB |
|
After Width: | Height: | Size: 1.7 MiB |
|
|
@ -29,6 +29,18 @@
|
|||
"kind": "body",
|
||||
"file": "body-jupiter.png"
|
||||
},
|
||||
"body-venus": {
|
||||
"kind": "body",
|
||||
"file": "body-venus.png"
|
||||
},
|
||||
"body-io": {
|
||||
"kind": "body",
|
||||
"file": "body-io.png"
|
||||
},
|
||||
"body-europa": {
|
||||
"kind": "body",
|
||||
"file": "body-europa.png"
|
||||
},
|
||||
"badge-cloud-platform": {
|
||||
"kind": "badge",
|
||||
"file": null
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ const TYPE_LABELS: Record<Location["type"], string> = {
|
|||
"airless-body": "Airless body",
|
||||
"cloud-platform": "Cloud platform",
|
||||
"belt-station": "Belt station",
|
||||
"orbital-station": "Orbital station",
|
||||
"surface-base": "Surface base",
|
||||
};
|
||||
/** What a bay is for (CR-002: bays are HQs, not access to contracts). */
|
||||
export const HQ_EXPLANATION =
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ const TYPE_LABELS: Record<Location["type"], string> = {
|
|||
"airless-body": "Airless body",
|
||||
"cloud-platform": "Cloud platform",
|
||||
"belt-station": "Belt station",
|
||||
"orbital-station": "Orbital station",
|
||||
"surface-base": "Surface base",
|
||||
};
|
||||
|
||||
/** The solar system map (Phase 10). Tap a location or ship for details. */
|
||||
|
|
|
|||
|
|
@ -31,10 +31,11 @@ describe("the fixed map (Step 15.8, decision 0.5.93)", () => {
|
|||
}
|
||||
});
|
||||
|
||||
it("draws an orbit ring for every body but the Moon, which rides with Earth", () => {
|
||||
it("draws an orbit ring for every body but those riding with a parent (Moon, Io, Europa)", () => {
|
||||
const scene = buildScene(samplePlayerState(), NOW);
|
||||
const riders = ["moon", "io-sulfur-works", "europa-ice-station"];
|
||||
for (const loc of scene.locations) {
|
||||
if (loc.id === "moon") expect(loc.orbitRadius).toBeNull();
|
||||
if (riders.includes(loc.id)) expect(loc.orbitRadius, loc.id).toBeNull();
|
||||
else expect(loc.orbitRadius, loc.id).toBeGreaterThan(0);
|
||||
}
|
||||
});
|
||||
|
|
@ -52,7 +53,9 @@ describe("reach wording without dates (Step 15.8)", () => {
|
|||
);
|
||||
const clipper = (await screen.findByRole("button", { name: "Buy Clipper" })).closest("li")!;
|
||||
const reach = within(clipper).getByLabelText("Reach").textContent ?? "";
|
||||
expect(reach).toBe("Reach From Earth: Moon, Mars · By hopping: Ceres Station");
|
||||
expect(reach).toBe(
|
||||
"Reach From Earth: Moon, Mars, Venus Cloud City · By hopping: Ceres Station",
|
||||
);
|
||||
expect(reach).not.toMatch(DATED);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -393,7 +393,7 @@ Spec: `memory-bank/change-requests/CR-015-fixed-map-fuel-tanks.md` (received as
|
|||
|
||||
Spec: `memory-bank/change-requests/CR-016-venus-io-europa-belt.md` (received as "CR-015 … Phase 15"). New decisions recorded as plan §0.5.100–0.5.105.
|
||||
|
||||
- [ ] Step 16.1 — Content: new locations
|
||||
- [x] **Step 16.1 — Content: new locations.** Venus Cloud City (cloud platform, atmospheric, 108M km at 110°, range factor 1.0, fuel × 1.3, bay 20,000), Io Sulfur Works (new type `orbital-station`, `vacuum` landing so any ship can land, 0.42M km out from Jupiter, factor 1.2, fuel × 1.0, bay 70,000) and Europa Ice Station (new type `surface-base`, `vacuum`, 0.67M km out from Jupiter, factor 1.3, fuel × 0.7, bay 70,000); all undiscovered, selling fuel, with the CR's shippers and class mixes. Pulled forward so the game keeps working: each new place's Standard good (Cloud-Grown Botanicals at Venus, a new good; Mail & Parcels also produced at Io and Europa), since boards can't be generated where nothing Standard is made; and the three body sprites in the art manifest, with their generated art (the belt is still 16.5). The balance check's first Market purchase moved from 64 to ~92 min (the Clipper's landings decide it, and Venus changed which first contacts boards offer); the user widened the window to 45–95. Tests: `packages/content/test/fixed-map.test.ts` (Earth/Moon–Venus ≈ 200, Venus–Mars ≈ 332, Venus–Ceres ≈ 520, Venus–Jupiter ≈ 885, Ceres–Io/Europa ≈ 393, all within 2 %; the Jupiter system within 1.1M km; Io and Europa ride with Jupiter at their offsets; the three undiscovered, selling fuel, not Hardened; only Earth and the Moon start discovered); `locations.test.ts` (eight locations; fuel prices ordered Jupiter < Europa < Ceres < Io < Moon < Venus < Mars < Earth). Updated: bundle count, first contacts may go to Mars or Venus, the CR-007 class-mix baseline measured on its original five places, orbit rings for every body but those riding a parent, the Market reach line now listing Venus.
|
||||
- [ ] Step 16.2 — Content: goods
|
||||
- [ ] Step 16.3 — Content tuning: reach
|
||||
- [ ] Step 16.4 — Engine: save migration and boards
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ const MOON = "moon";
|
|||
const MARS = "mars";
|
||||
const CERES = "ceres-station";
|
||||
const JUPITER = "jupiter-cloud-platform";
|
||||
const VENUS = "venus-cloud-city";
|
||||
const IO = "io-sulfur-works";
|
||||
const EUROPA = "europa-ice-station";
|
||||
|
||||
/**
|
||||
* The goods catalog (CR-007, decision 0.5.54). Pay multipliers scale the
|
||||
|
|
@ -20,7 +23,7 @@ export const goods: Good[] = [
|
|||
massTonnes: { min: 1, max: 8 },
|
||||
payMultiplier: 1.2,
|
||||
priorityEligible: true,
|
||||
producedAt: [EARTH, MARS],
|
||||
producedAt: [EARTH, MARS, IO, EUROPA],
|
||||
demandedAt: [MOON, MARS, CERES, JUPITER, EARTH],
|
||||
},
|
||||
{
|
||||
|
|
@ -73,6 +76,17 @@ export const goods: Good[] = [
|
|||
producedAt: [MARS],
|
||||
demandedAt: [MOON, CERES, JUPITER],
|
||||
},
|
||||
{
|
||||
// Venus's Standard good for no-skill pilots (CR-016 §4).
|
||||
id: "cloud-botanicals",
|
||||
name: "Cloud-Grown Botanicals",
|
||||
cargoClassId: "standard",
|
||||
massTonnes: { min: 2, max: 20 },
|
||||
payMultiplier: 1.1,
|
||||
priorityEligible: true,
|
||||
producedAt: [VENUS],
|
||||
demandedAt: [EARTH, MOON, MARS],
|
||||
},
|
||||
{
|
||||
id: "salvaged-components",
|
||||
name: "Salvaged Components",
|
||||
|
|
|
|||
|
|
@ -9,6 +9,10 @@ import { goodsDemandedAt, goodsProducedAt } from "./goods";
|
|||
*/
|
||||
const EARTH: FixedPosition = { radiusMillionKm: 150, angleDegrees: 210 };
|
||||
const MOON_OFFSET_MILLION_KM = 0.384;
|
||||
const JUPITER: FixedPosition = { radiusMillionKm: 778, angleDegrees: 300 };
|
||||
// Io and Europa ride with Jupiter (CR-016, decision 0.5.100).
|
||||
const IO_OFFSET_MILLION_KM = 0.42;
|
||||
const EUROPA_OFFSET_MILLION_KM = 0.67;
|
||||
|
||||
/** `parent` moved straight out from the Sun by `offset` million km. */
|
||||
export function offsetPosition(parent: FixedPosition, offset: number): FixedPosition {
|
||||
|
|
@ -105,7 +109,7 @@ export const locations: Location[] = [
|
|||
name: "Jupiter Cloud Platform",
|
||||
type: "cloud-platform",
|
||||
landingRequirement: "atmospheric",
|
||||
position: { radiusMillionKm: 778, angleDegrees: 300 },
|
||||
position: JUPITER,
|
||||
sprite: "body-jupiter",
|
||||
badge: "badge-cloud-platform",
|
||||
fuelPrice: 0.6,
|
||||
|
|
@ -118,4 +122,75 @@ export const locations: Location[] = [
|
|||
cargoClassMix: { produces: ["dangerous"], demands: ["standard", "high-value", "heavy"] },
|
||||
shippers: ["Galilean Fuel Combine", "Zephyr Gas Works"],
|
||||
},
|
||||
// CR-016 (decision 0.5.100): an inner-system alternative to Mars, and two
|
||||
// short hops from Jupiter's platform. All undiscovered at the start.
|
||||
{
|
||||
id: "venus-cloud-city",
|
||||
name: "Venus Cloud City",
|
||||
type: "cloud-platform",
|
||||
landingRequirement: "atmospheric",
|
||||
// About 200M km from Earth, the other way from Mars.
|
||||
position: { radiusMillionKm: 108, angleDegrees: 110 },
|
||||
sprite: "body-venus",
|
||||
fuelPrice: 1.3,
|
||||
sellsFuel: true,
|
||||
// Floats above the gravity well, like Jupiter's platform.
|
||||
launchRangeFactor: 1,
|
||||
startsDiscovered: false,
|
||||
bayLeasePrice: 20000,
|
||||
produces: goodsProducedAt("venus-cloud-city"),
|
||||
demands: goodsDemandedAt("venus-cloud-city"),
|
||||
cargoClassMix: {
|
||||
produces: ["standard", "high-value", "dangerous"],
|
||||
demands: ["standard", "high-value", "heavy"],
|
||||
},
|
||||
shippers: ["Aphrodite Atmospherics", "Ishtar Cloudworks"],
|
||||
},
|
||||
{
|
||||
id: "io-sulfur-works",
|
||||
name: "Io Sulfur Works",
|
||||
type: "orbital-station",
|
||||
// An orbital station: no Hardened landing class needed (decision 0.5.101).
|
||||
landingRequirement: "vacuum",
|
||||
position: offsetPosition(JUPITER, IO_OFFSET_MILLION_KM),
|
||||
orbitParent: { locationId: "jupiter-cloud-platform", offsetMillionKm: IO_OFFSET_MILLION_KM },
|
||||
sprite: "body-io",
|
||||
fuelPrice: 1,
|
||||
sellsFuel: true,
|
||||
launchRangeFactor: 1.2,
|
||||
startsDiscovered: false,
|
||||
bayLeasePrice: 70000,
|
||||
produces: goodsProducedAt("io-sulfur-works"),
|
||||
demands: goodsDemandedAt("io-sulfur-works"),
|
||||
cargoClassMix: {
|
||||
produces: ["standard", "heavy", "high-value"],
|
||||
demands: ["standard", "high-value", "heavy", "dangerous"],
|
||||
},
|
||||
shippers: ["Loki Sulfur Co.", "Pele Orbital Mining"],
|
||||
},
|
||||
{
|
||||
id: "europa-ice-station",
|
||||
name: "Europa Ice Station",
|
||||
type: "surface-base",
|
||||
landingRequirement: "vacuum",
|
||||
position: offsetPosition(JUPITER, EUROPA_OFFSET_MILLION_KM),
|
||||
orbitParent: {
|
||||
locationId: "jupiter-cloud-platform",
|
||||
offsetMillionKm: EUROPA_OFFSET_MILLION_KM,
|
||||
},
|
||||
sprite: "body-europa",
|
||||
// Ice cracked into fuel.
|
||||
fuelPrice: 0.7,
|
||||
sellsFuel: true,
|
||||
launchRangeFactor: 1.3,
|
||||
startsDiscovered: false,
|
||||
bayLeasePrice: 70000,
|
||||
produces: goodsProducedAt("europa-ice-station"),
|
||||
demands: goodsDemandedAt("europa-ice-station"),
|
||||
cargoClassMix: {
|
||||
produces: ["standard", "high-value", "heavy", "dangerous"],
|
||||
demands: ["standard", "high-value", "heavy"],
|
||||
},
|
||||
shippers: ["Conamara Ice Works", "Europa Deep Survey"],
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ export const locationTypeSchema = z.enum([
|
|||
"airless-body",
|
||||
"cloud-platform",
|
||||
"belt-station",
|
||||
"orbital-station",
|
||||
"surface-base",
|
||||
]);
|
||||
export type LocationType = z.infer<typeof locationTypeSchema>;
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { content } from "../src/index";
|
|||
|
||||
describe("content bundle", () => {
|
||||
it("contains every kind of content", () => {
|
||||
expect(content.locations).toHaveLength(5);
|
||||
expect(content.locations).toHaveLength(8);
|
||||
expect(content.shipModels).toHaveLength(6);
|
||||
expect(content.purchasableShipModels).toHaveLength(5);
|
||||
expect(content.cargoTypes).toHaveLength(4);
|
||||
|
|
|
|||
|
|
@ -56,3 +56,46 @@ describe("fixed map (Step 15.1, decision 0.5.93)", () => {
|
|||
expect(lockfile).not.toContain("astronomy-engine");
|
||||
});
|
||||
});
|
||||
|
||||
describe("new locations (Step 16.1, decision 0.5.100)", () => {
|
||||
it.each([
|
||||
["earth", "venus-cloud-city", 200],
|
||||
["moon", "venus-cloud-city", 200],
|
||||
["venus-cloud-city", "mars", 332],
|
||||
["venus-cloud-city", "ceres-station", 520],
|
||||
["venus-cloud-city", "jupiter-cloud-platform", 885],
|
||||
["ceres-station", "io-sulfur-works", 393],
|
||||
["ceres-station", "europa-ice-station", 393],
|
||||
])("puts %s–%s at about %i M km (CR-016 §2, within 2 %)", (from, to, expected) => {
|
||||
expect(Math.abs(distance(from, to) - expected) / expected).toBeLessThan(0.02);
|
||||
});
|
||||
|
||||
it("keeps the Jupiter system within 1.1M km of the platform", () => {
|
||||
const system = ["jupiter-cloud-platform", "io-sulfur-works", "europa-ice-station"];
|
||||
for (const a of system) for (const b of system) expect(distance(a, b)).toBeLessThan(1.1);
|
||||
});
|
||||
|
||||
it("rides Io and Europa with Jupiter, at their offsets", () => {
|
||||
expect(content.location("io-sulfur-works")!.orbitParent).toEqual({
|
||||
locationId: "jupiter-cloud-platform",
|
||||
offsetMillionKm: 0.42,
|
||||
});
|
||||
expect(content.location("europa-ice-station")!.orbitParent).toEqual({
|
||||
locationId: "jupiter-cloud-platform",
|
||||
offsetMillionKm: 0.67,
|
||||
});
|
||||
});
|
||||
|
||||
it("starts all three undiscovered, selling fuel, landable without Hardened ships", () => {
|
||||
for (const id of ["venus-cloud-city", "io-sulfur-works", "europa-ice-station"]) {
|
||||
const location = content.location(id)!;
|
||||
expect(location.startsDiscovered, id).toBe(false);
|
||||
expect(location.sellsFuel, id).toBe(true);
|
||||
expect(location.landingRequirement, id).not.toBe("hardened");
|
||||
}
|
||||
expect(content.locations.filter((l) => l.startsDiscovered).map((l) => l.id)).toEqual([
|
||||
"earth",
|
||||
"moon",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@ describe("locations", () => {
|
|||
}
|
||||
});
|
||||
|
||||
it("has exactly five locations with unique ids", () => {
|
||||
expect(locations).toHaveLength(5);
|
||||
expect(new Set(locations.map((l) => l.id)).size).toBe(5);
|
||||
it("has exactly eight locations with unique ids (CR-016)", () => {
|
||||
expect(locations).toHaveLength(8);
|
||||
expect(new Set(locations.map((l) => l.id)).size).toBe(8);
|
||||
});
|
||||
|
||||
it("starts with exactly Earth and Moon discovered", () => {
|
||||
|
|
@ -35,9 +35,18 @@ describe("locations", () => {
|
|||
for (const location of locations) expect(location.shippers.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("orders fuel prices Jupiter < Ceres < Moon < Mars < Earth (decision 0.5.96)", () => {
|
||||
it("orders fuel prices Jupiter < Europa < Ceres < Io < Moon < Venus < Mars < Earth (CR-016)", () => {
|
||||
const price = (id: string) => locations.find((l) => l.id === id)!.fuelPrice;
|
||||
const order = ["jupiter-cloud-platform", "ceres-station", "moon", "mars", "earth"];
|
||||
const order = [
|
||||
"jupiter-cloud-platform",
|
||||
"europa-ice-station",
|
||||
"ceres-station",
|
||||
"io-sulfur-works",
|
||||
"moon",
|
||||
"venus-cloud-city",
|
||||
"mars",
|
||||
"earth",
|
||||
];
|
||||
for (let i = 1; i < order.length; i++) {
|
||||
expect(price(order[i - 1]!), order[i]).toBeLessThan(price(order[i]!));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,10 +39,10 @@ describe("balance simulation (Step 12.1)", () => {
|
|||
expect(median(lighterFirst, "dealAccepted")).toBeLessThan(10);
|
||||
});
|
||||
|
||||
it("buys the first Market ship in 45–75 minutes (CR-013)", () => {
|
||||
it("buys the first Market ship in 45–95 minutes (CR-013, widened for CR-016)", () => {
|
||||
const minutes = median(lighterFirst, "firstMarketPurchase");
|
||||
expect(minutes).toBeGreaterThanOrEqual(45);
|
||||
expect(minutes).toBeLessThanOrEqual(75);
|
||||
expect(minutes).toBeLessThanOrEqual(95);
|
||||
});
|
||||
|
||||
it("discovers a new location in under 90 minutes", () => {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,13 @@
|
|||
import { content } from "@deadhead/content";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { chooseGood, priorityWindow, routeGoods } from "../src/board";
|
||||
import { createRng, generateContractBoard, type BoardInput, type Contract } from "../src/index";
|
||||
import {
|
||||
createRng,
|
||||
generateContractBoard,
|
||||
type BoardInput,
|
||||
type Contract,
|
||||
type SimContent,
|
||||
} from "../src/index";
|
||||
|
||||
const { config } = content;
|
||||
const bands = config.contractMassBands;
|
||||
|
|
@ -15,12 +21,19 @@ const earthInput: BoardInput = {
|
|||
worldSeed: 12345,
|
||||
};
|
||||
|
||||
/** Pre-change class shares, 1,000 Earth boards, all discovered, seed 12345 (CR-007 §6). */
|
||||
/** The five places CR-007's baseline was measured with (CR-016 adds more destinations). */
|
||||
const baseFive = new Set(["earth", "moon", "mars", "ceres-station", "jupiter-cloud-platform"]);
|
||||
const fivePlaces: SimContent = {
|
||||
...content,
|
||||
locations: content.locations.filter((l) => baseFive.has(l.id)),
|
||||
};
|
||||
|
||||
/** Pre-change class shares, 1,000 Earth boards, all five discovered, seed 12345 (CR-007 §6). */
|
||||
const BASELINE: Record<string, number> = { standard: 76.79, "high-value": 23.21 };
|
||||
|
||||
function boards(input: BoardInput, count: number): Contract[][] {
|
||||
function boards(input: BoardInput, count: number, at: SimContent = content): Contract[][] {
|
||||
return Array.from({ length: count }, (_, p) =>
|
||||
generateContractBoard({ ...input, periodIndex: p }, content),
|
||||
generateContractBoard({ ...input, periodIndex: p }, at),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -81,7 +94,7 @@ describe("goods in board generation (CR-007)", () => {
|
|||
it("keeps each cargo class within ±3 points of the pre-change mix from Earth", () => {
|
||||
const counts: Record<string, number> = {};
|
||||
let total = 0;
|
||||
for (const board of boards(earthInput, 1000)) {
|
||||
for (const board of boards({ ...earthInput, discoveredIds: baseFive }, 1000, fivePlaces)) {
|
||||
for (const c of board) {
|
||||
counts[c.cargoTypeId] = (counts[c.cargoTypeId] ?? 0) + 1;
|
||||
total++;
|
||||
|
|
|
|||
|
|
@ -52,13 +52,20 @@ describe("a first contact the fleet can fly (decision 0.5.85)", () => {
|
|||
const contracts = board(firstPeriod + k, ["earth", "moon"], [clipper]);
|
||||
const flyable = contracts.filter((c) => c.firstContact && clipperCanFly(c));
|
||||
expect(flyable.length, `period ${k}`).toBeGreaterThanOrEqual(1);
|
||||
expect(flyable.every((c) => c.destinationId === "mars")).toBe(true);
|
||||
// Mars and Venus Cloud City are both a Clipper leg from Earth (CR-016).
|
||||
expect(flyable.every((c) => ["mars", "venus-cloud-city"].includes(c.destinationId))).toBe(
|
||||
true,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("stops once the reachable places are discovered", () => {
|
||||
for (let k = 0; k < 24; k++) {
|
||||
const contracts = board(firstPeriod + k, ["earth", "moon", "mars"], [clipper]);
|
||||
const contracts = board(
|
||||
firstPeriod + k,
|
||||
["earth", "moon", "mars", "venus-cloud-city"],
|
||||
[clipper],
|
||||
);
|
||||
expect(contracts.filter((c) => c.firstContact && clipperCanFly(c))).toEqual([]);
|
||||
}
|
||||
});
|
||||
|
|
|
|||