From 1c115a74652f67545de6eb89937ac946d9c2ce97 Mon Sep 17 00:00:00 2001 From: fireshaper Date: Mon, 5 Oct 2026 19:37:16 -0400 Subject: [PATCH] Step 12.6: end-to-end: pilot progression Co-Authored-By: Claude Opus 5.5 --- DECISIONS.md | 5 ++ apps/web/e2e/12-6-pilot-progression.e2e.ts | 93 ++++++++++++++++++++++ memory-bank/progress.md | 4 +- 3 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 apps/web/e2e/12-6-pilot-progression.e2e.ts diff --git a/DECISIONS.md b/DECISIONS.md index 72ab8dc..eaba860 100644 --- a/DECISIONS.md +++ b/DECISIONS.md @@ -1285,3 +1285,8 @@ Implementation decisions not spelled out in `memory-bank/implementation-plan.md` ## Step 12.5 - **The Mars window is computed, not waited for**: the test finds the next date the Clipper can reach Mars and fly on, and fast-forwards to it. + +## Step 12.6 + +- **A Lighter at the Moon, not the Courier**: with fleet-fit boards (CR-006) a Courier never sees a Heavy job it could carry, and a Lighter at the Moon sees one on about one board in eight, so the test waits for one board period at a time. +- **The new pilot is levelled by real deliveries** (the server's own XP), rests included, rather than by editing XP. diff --git a/apps/web/e2e/12-6-pilot-progression.e2e.ts b/apps/web/e2e/12-6-pilot-progression.e2e.ts new file mode 100644 index 0000000..ea61b46 --- /dev/null +++ b/apps/web/e2e/12-6-pilot-progression.e2e.ts @@ -0,0 +1,93 @@ +import { content } from "@deadhead/content"; +import { expect, test } from "@playwright/test"; +import { + acknowledgeAll, + addCredits, + advance, + advanceTo, + assignPilot, + board, + buyShip, + flyUntil, + hirePilot, + playToDeal, + state, +} from "./support"; + +test.describe("end to end (Phase 12)", () => { + test("12.6: a pilot levels up, learns Heavy Cargo and can take a Heavy job; Long Haul lengthens duty", async ({ + page, + }) => { + const owner = await playToDeal(page); + const bay = owner.bays[0]!; + await addCredits(page, 100_000); + const { ship } = await buyShip(page, "lighter", bay.id); + const { pilot } = await hirePilot(page, "earth"); + await assignPilot(page, pilot.id, ship.id); + + // Deliveries until the new pilot reaches level 1 (two skill points). + await flyUntil( + page, + ship.id, + pilot.id, + (s) => s.pilots.find((p) => p.id === pilot.id)!.level >= 1, + ); + + // Get the Lighter to the Moon, and wait for a Heavy Cargo 1 job it can carry there. + await flyUntil( + page, + ship.id, + pilot.id, + (s) => s.ships.find((x) => x.id === ship.id)!.locationId === "moon", + ); + const heavyJob = async () => + (await board(page, "moon")).contracts.find( + (c) => + c.requiredSkill?.skillId === "heavy-cargo" && + c.requiredSkill.level === 1 && + c.massTonnes <= 80 && + c.requiredLongHaulLevel === 0, + ); + let job = await heavyJob(); + for (let k = 0; !job && k < 60; k++) { + await advance(page, content.config.contractBoardPeriodRealMinutes * 60_000); + job = await heavyJob(); + } + expect(job, "a Heavy Cargo 1 job at the Moon").toBeDefined(); + await acknowledgeAll(page); + const resting = (await state(page)).pilots.find((p) => p.id === pilot.id)!.restEndsAt; + if (resting !== null) await advanceTo(page, resting); + + // In the dispatch sheet the Lighter's pair is blocked by the skill... + const pairFor = async () => { + await page.goto("/contracts?location=moon"); + const card = page + .getByRole("listitem") + .filter({ hasText: content.good(job!.goodId)!.name }) + .filter({ hasText: `${job!.massTonnes} t` }); + await card.getByRole("button", { name: "Dispatch" }).first().click(); + return page.locator(`input[name="pair"][value="${ship.id}"]`); + }; + await expect(await pairFor()).toBeDisabled(); + await expect(page.getByText("Needs Heavy Cargo 1 (pilot has 0)")).toBeVisible(); + + // ...until the pilot spends a point in Heavy Cargo. + await page.goto("/pilots"); + await page.getByRole("button", { name: `Raise Heavy Cargo for ${pilot.name}` }).click(); + await expect + .poll( + async () => + (await state(page)).pilots.find((p) => p.id === pilot.id)!.skills["heavy-cargo"], + ) + .toBe(1); + await expect(await pairFor()).toBeEnabled(); + + // CR-004: Long Haul raises the duty limit. + const limitBefore = (await state(page)).pilots.find((p) => p.id === pilot.id)!.dutyLimit; + await page.goto("/pilots"); + await page.getByRole("button", { name: `Raise Long Haul for ${pilot.name}` }).click(); + await expect + .poll(async () => (await state(page)).pilots.find((p) => p.id === pilot.id)!.dutyLimit) + .toBe(limitBefore + content.config.longHaulDutyBonusGameHours); + }); +}); diff --git a/memory-bank/progress.md b/memory-bank/progress.md index 9394063..8759929 100644 --- a/memory-bank/progress.md +++ b/memory-bank/progress.md @@ -311,4 +311,6 @@ Spec: `memory-bank/change-requests/CR-013-veterans-clipper.md` (received as "CR- - [x] **Step 12.3 — End-to-end: tutorial to first ship.** Playwright now also starts the real API server (port 3135) on `TEST_DATABASE_URL`, migrated first, next to the production web build with `/api` proxied to it; tests run one at a time because the developer clock is shared. `e2e/support.ts` drives play: start a company, fly the tutorial board, shake on the deal, dispatch from a board through the sheet, dismiss the logbook, and fast-forward with the developer routes (CR-011). Test (`e2e/12-3-first-ship.e2e.ts`): guest → four tutorial runs in the UI → deal → the state has the Courier (avatar) and Clipper (veteran) at Earth, one Earth bay, the 66,600 cr loan and no tutorial → one contract dispatched in the dispatch sheet and resolved, its logbook entry shows the repayment and the loan went down. - [x] **Step 12.4 — End-to-end: offline progress.** Test (`e2e/12-4-offline-progress.e2e.ts`): dispatch a contract, close the page, move the server clock past the arrival, open a new page: the logbook shows the delivery, credits went up by exactly the delivery's net payout, and the ship is at the destination. - [x] **Step 12.5 — End-to-end: discovery.** Test (`e2e/12-5-discovery.e2e.ts`): after the deal, the clock moves to a date when a base Clipper can fly Earth → Mars (computed with the sim, so the test doesn't depend on today's planets); the veteran's points go into Long Haul in the Pilots screen; the Earth board offers a first contact to Mars the Clipper can take (decision 0.5.85); on arrival the logbook says "Discovered Mars!", the Locations screen offers "Lease bay at Mars", and Mars's board loads with no bay there (CR-002). -- [ ] Step 12.6 — End-to-end: pilot progression +- [x] **Step 12.6 — End-to-end: pilot progression.** Test (`e2e/12-6-pilot-progression.e2e.ts`): buy a Lighter, hire and assign a new pilot, fly them until level 1; take the Lighter to the Moon and wait for a Heavy Cargo 1 job it can carry; in the dispatch sheet the Lighter's pair is disabled with "Needs Heavy Cargo 1 (pilot has 0)"; after raising Heavy Cargo in the Pilots screen the pair is enabled; raising Long Haul adds the Long Haul duty bonus to the pilot's duty limit (CR-004). + +**Phase 12 complete — waiting for review.**