Step 10L.5: upgrade caps for the new lineup
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
776a890757
commit
8d8df71ba9
|
|
@ -1157,3 +1157,10 @@ Implementation decisions not spelled out in `memory-bank/implementation-plan.md`
|
|||
- **Clipper stays at 198**: Earth→Mars is in range on just over 30 % of dates, and Moon→Ceres (198 × 1.3 = 257.4) stays below Ceres's closest approach (262.8). The margins are small, so the Clipper's range shouldn't move without re-running this table.
|
||||
- **Decision 0.5.25 is superseded** (noted in the plan): the Freighter no longer reaches Jupiter from Earth at all.
|
||||
- **No other stats changed.** The existing upgrade-cap tests (0.5.67) and the Courier-to-Mars test (0.5.68) still pass at these values; 10L.5 adds the new caps.
|
||||
|
||||
## Step 10L.5
|
||||
|
||||
- **Courier tanks 600 → 450 % per level.** Over 16 years Mars comes as close as 58.9M km from Earth, so the old level 2 (65M km) would have reached it, breaking decision 0.5.68. At 450 % level 2 is 50M km (never) and level 3 is 72.5M km, which reaches Mars only in the closest approaches (months 150 and 176–177 of the 16 years sampled). A maxed Courier now reaches Mars rarely, which fits "near closest approach only".
|
||||
- **The Freighter's 15 % tanks override stays**: 450 → 652.5M km maxed, short of the Bulk Hauler (1,250) and of Jupiter from Earth at its farthest (965). Its comment now quotes the new numbers.
|
||||
- **No caps for the new models**: with the default percentages a maxed Clipper holds 43.5 t (Freighter 100), a maxed Courier flies 3.9M km/h (Clipper 6.5M), and a maxed Lighter's range is 16M km (20.8 from the Moon), far below Mars's closest 58.5.
|
||||
- **The Lighter cap is a sim test** because it needs distances; the plain stat inequalities stay in the content test.
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|||
import { useClock } from "../src/clock/clock";
|
||||
import { fakeApi, renderApp, samplePlayerState, shipExtras } from "./render";
|
||||
|
||||
/** Mars near closest approach in the first two game years (~82M km, decision 0.5.68). */
|
||||
/** Mars at its closest in 16 game years (~59M km, month 176; decision 0.5.68). */
|
||||
const gameStart = realToGame(Date.parse(content.config.epoch.realStart), content.config);
|
||||
const NOW = gameToReal(gameStart + 23 * 30.44 * 24 * 3_600_000, content.config);
|
||||
const NOW = gameToReal(gameStart + 176 * 30.44 * 24 * 3_600_000, content.config);
|
||||
|
||||
const courier = (upgrades: Record<string, number>): Ship => ({
|
||||
id: "s1",
|
||||
|
|
@ -99,7 +99,7 @@ describe("effective stats in the client (Step 10I.7)", () => {
|
|||
content.location("mars")!,
|
||||
realToGame(NOW, content.config),
|
||||
);
|
||||
expect(distance).toBeLessThan(95);
|
||||
expect(distance).toBeLessThan(72.5);
|
||||
|
||||
const open = async () => {
|
||||
const card = (await screen.findByText("Dusty Mule")).closest("li")!;
|
||||
|
|
|
|||
|
|
@ -273,7 +273,7 @@ Spec: `memory-bank/change-requests/CR-012-lighter-clipper-range.md` (received as
|
|||
- [x] **Step 10L.2 — Sim: effective range from origin.** `rangeFrom(ship, from)` = range × origin's `launchRangeFactor`; `checkRoute` compares the leg against it (dispatch, reposition, board eligibility and the map all go through it). The dispatch sheet quotes range from the origin. Tests (`range-from.test.ts`): 100 → 130 from the Moon and 115 from Mars; a leg between 1× and 1.3× fails from Earth and passes from the Moon; with every factor 1 the result matches the old check over 16 years of samples.
|
||||
- [x] **Step 10L.3 — Sim: no-strand rule.** `wouldStrand(stats, origin, destination, mass, departure, discoveredIds, content)`: at arrival plus `noStrandMarginGameHours` (48), can the ship reach any other discovered location from the destination with its range from there? `validateDispatch` (new optional `discoveredIds`; absent = all) and `validateReposition` fail with `would-strand` after range and landing. The server passes the player's discovered ids for contract dispatches; server and web reason maps gained the code (full wording in 10L.6/10L.9). Tests (`no-strand.test.ts`): a fixture heavy-Moon hop is refused, then passes once a reachable location is discovered; the same for a contract; across all six models, every ordered pair and monthly samples over 16 years, an allowed trip always leaves somewhere to fly at arrival plus margin.
|
||||
- [x] **Step 10L.4 — Content tuning: reach table.** Ranges: Courier 5, Lighter 10, Clipper 198 (unchanged from 10L.1), **Freighter 650 → 450**, **Bulk Hauler 1000 → 1250**. Step 3.7's range tests (`eligibility.test.ts`) now name their origin; the Freighter-to-Ceres-at-farthest and Freighter-to-Jupiter-at-closest tests become "reaches Ceres at its closest, not its farthest" and "never reaches Jupiter from Earth". Tests (`reach-table.test.ts`): one per §3 cell over monthly samples for 16 game years, plus the two hop tests.
|
||||
- [ ] Step 10L.5 — Upgrade caps for the new lineup
|
||||
- [x] **Step 10L.5 — Upgrade caps for the new lineup.** Courier fuel-tanks override 600 → **450 %** per level (5 → 27.5 → 50 → 72.5M km); the Freighter override stays at 15 % (450 → 652.5). No Lighter or Clipper caps were needed. Tests: the new 0.5.67 inequalities (content `upgrades.test.ts`; sim `upgrade-caps.test.ts`: a maxed Lighter never reaches Mars from Earth or the Moon, only a Bulk Hauler reaches Jupiter from Earth at its farthest even with every ship maxed); `courier-mars.test.ts` now samples 16 years from Earth. The web deadhead-to-Mars test moved to month 176, Mars's closest approach.
|
||||
- [ ] Step 10L.6 — Schema and server
|
||||
- [ ] Step 10L.7 — Client: Market shows roles and reach
|
||||
- [ ] Step 10L.8 — Client: reachability from where ships actually are
|
||||
|
|
|
|||
|
|
@ -27,9 +27,9 @@ export const upgrades: Upgrade[] = [
|
|||
percentPerLevel: 20,
|
||||
maxLevel: 3,
|
||||
modelOverrides: {
|
||||
// 5 → 35 → 65 → 95 M km: Mars (≈ 55–400 M km) only near closest approach, and only at level 3.
|
||||
courier: { percentPerLevel: 600 },
|
||||
// 650 → 942.5 M km at level 3: still short of a base Bulk Hauler's 1000 (and of Jupiter at its farthest).
|
||||
// 5 → 27.5 → 50 → 72.5 M km: Mars from Earth (≈ 59–400 M km) only near closest approach, and only at level 3.
|
||||
courier: { percentPerLevel: 450 },
|
||||
// 450 → 652.5 M km at level 3: short of a base Bulk Hauler's 1250 and of Jupiter from Earth at its farthest (≈ 965).
|
||||
freighter: { percentPerLevel: 15 },
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -71,5 +71,12 @@ describe("ship upgrades content (CR-009)", () => {
|
|||
expect(maxed("freighter", "fuelRangeMillionKm")).toBeLessThan(
|
||||
model("bulk-hauler").fuelRangeMillionKm,
|
||||
);
|
||||
// Amended by CR-012 (the Lighter's Mars cap is a sim test: upgrade-caps.test.ts).
|
||||
expect(maxed("clipper", "cargoCapacityTonnes")).toBeLessThan(
|
||||
model("freighter").cargoCapacityTonnes,
|
||||
);
|
||||
expect(maxed("courier", "cruiseSpeedKmPerGameHour")).toBeLessThan(
|
||||
model("clipper").cruiseSpeedKmPerGameHour,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -14,14 +14,15 @@ const withTanks = (level: number): ShipModel => ({
|
|||
courier.fuelRangeMillionKm * (1 + (upgradePercentPerLevel(tanks, "courier") * level) / 100),
|
||||
});
|
||||
|
||||
/** Monthly samples over the first two game years. */
|
||||
/** Monthly samples over 16 game years (CR-012). */
|
||||
const MONTH = 30.44 * 24 * 3_600_000;
|
||||
const start = realToGame(Date.parse(content.config.epoch.realStart), content.config);
|
||||
const samples = Array.from({ length: 25 }, (_, i) => start + i * MONTH);
|
||||
const samples = Array.from({ length: 16 * 12 + 1 }, (_, i) => start + i * MONTH);
|
||||
const inRange = (ship: ShipModel) =>
|
||||
samples.map((t) => checkRoute(ship, earth, mars, t)).map((r) => r.ok);
|
||||
|
||||
describe("Courier to Mars (decision 0.5.68)", () => {
|
||||
// Measured from Earth (decision 0.5.68 as amended by CR-012).
|
||||
describe("Courier to Mars from Earth (decision 0.5.68)", () => {
|
||||
it("reaches Mars with maxed fuel tanks near closest approach, but not at its farthest", () => {
|
||||
const reach = inRange(withTanks(3));
|
||||
expect(reach.some(Boolean)).toBe(true);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
import { content, upgradeMaxLevel, upgradePercentPerLevel, upgrades } from "@deadhead/content";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { checkRoute, effectiveStats, realToGame, routeDistance } from "../src/index";
|
||||
|
||||
/** Every upgrade at its highest level for the model (decision 0.5.63). */
|
||||
const maxedStats = (modelId: string) =>
|
||||
effectiveStats(
|
||||
content.shipModel(modelId)!,
|
||||
Object.fromEntries(upgrades.map((u) => [u.id, upgradeMaxLevel(u, modelId)])),
|
||||
content,
|
||||
);
|
||||
|
||||
const MONTH = 30.44 * 24 * 3_600_000;
|
||||
const start = realToGame(Date.parse(content.config.epoch.realStart), content.config);
|
||||
const samples = Array.from({ length: 16 * 12 + 1 }, (_, i) => start + i * MONTH);
|
||||
const earth = content.location("earth")!;
|
||||
const moon = content.location("moon")!;
|
||||
const mars = content.location("mars")!;
|
||||
const jupiter = content.location("jupiter-cloud-platform")!;
|
||||
|
||||
describe("upgrade caps for the CR-012 lineup (decision 0.5.67)", () => {
|
||||
it("a maxed Lighter never reaches Mars from Earth or the Moon", () => {
|
||||
const lighter = maxedStats("lighter");
|
||||
for (const from of [earth, moon]) {
|
||||
for (const t of samples) expect(checkRoute(lighter, from, mars, t).ok).toBe(false);
|
||||
}
|
||||
});
|
||||
|
||||
it("a maxed Clipper carries less than a base Freighter", () => {
|
||||
expect(maxedStats("clipper").cargoCapacityTonnes).toBeLessThan(
|
||||
content.shipModel("freighter")!.cargoCapacityTonnes,
|
||||
);
|
||||
});
|
||||
|
||||
it("a maxed Courier is slower than a base Clipper", () => {
|
||||
expect(maxedStats("courier").cruiseSpeedKmPerGameHour).toBeLessThan(
|
||||
content.shipModel("clipper")!.cruiseSpeedKmPerGameHour,
|
||||
);
|
||||
});
|
||||
|
||||
it("only Bulk Haulers reach Jupiter from Earth at its farthest, even maxed", () => {
|
||||
const farthest = samples.reduce((a, b) =>
|
||||
routeDistance(earth, jupiter, b) > routeDistance(earth, jupiter, a) ? b : a,
|
||||
);
|
||||
for (const ship of content.shipModels) {
|
||||
const reaches = checkRoute(maxedStats(ship.id), earth, jupiter, farthest).ok;
|
||||
expect(reaches, ship.id).toBe(ship.id === "bulk-hauler");
|
||||
}
|
||||
});
|
||||
|
||||
it("gives the Courier 450 % range per tanks level", () => {
|
||||
expect(upgradePercentPerLevel(content.upgrade("fuel-tanks")!, "courier")).toBe(450);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue