From 4e7ae7505d4865defd1694241032db5195f81a0e Mon Sep 17 00:00:00 2001 From: fireshaper Date: Sat, 10 Oct 2026 19:07:08 -0400 Subject: [PATCH] Step 18.2: content: lower the base fuel price Base fuel price 10 -> 5 cr/fu. No route is above 25% of the median fitting payout but the Clipper's Mars -> Earth; the first purchase window is now 30-95 min (user decisions, 0.5.110). Co-Authored-By: Claude Opus 5.5 --- DECISIONS.md | 5 +++++ apps/web/test/fuel-ui.test.tsx | 5 +++-- memory-bank/progress.md | 2 +- packages/content/src/data/config.ts | 5 +++-- packages/schema/test/save.test.ts | 9 +++++---- packages/sim/test/balance.test.ts | 5 +++-- packages/sim/test/fuel-share.test.ts | 9 +++++++++ packages/sim/test/tank.test.ts | 3 ++- 8 files changed, 31 insertions(+), 12 deletions(-) diff --git a/DECISIONS.md b/DECISIONS.md index 0c61dd3..4aa3803 100644 --- a/DECISIONS.md +++ b/DECISIONS.md @@ -1593,3 +1593,8 @@ Implementation decisions not spelled out in `memory-bank/implementation-plan.md` - **Fuel is the same for any load** (fuel has no mass, decision 0.5.94), so "a typical loaded leg" is just the leg's fuel at the origin's price. - **"Contracts that fit the ship" means the hold**: mass at most the cargo capacity, any cargo class (a pilot can learn the skill), measured on boards generated with that ship as the fleet so the boards fit it as they would in play. - **One price can't meet the 15–25% band** (user decision, 2026-10-10): shares spread over 100× because bigger ships carry richer jobs for the same fuel. The user chose: halve the base price (CR-018's starting point), report every row, and fail only on a row above 25%, with the Clipper's Mars → Earth leg (few small Mars → Earth jobs fit its 30 t hold) as the known exception. + +## Step 18.2 + +- **Base fuel price halved to 5 cr/fu, with no further tuning** (user decision): the gate is "no route above 25%", which the halved price meets on every route but the Clipper's Mars → Earth. Most routes are now under 15% (the Courier's Earth → Moon is 10.5%); fuel is cheap by design. +- **First Market purchase window 30–95 min** (user decision, was 45–95): halving fuel moved the median to 41.6 min, and full deal tanks (Step 18.3) to about 37. Buying sooner is what CR-018 is for. diff --git a/apps/web/test/fuel-ui.test.tsx b/apps/web/test/fuel-ui.test.tsx index e2d6dc8..aab4f35 100644 --- a/apps/web/test/fuel-ui.test.tsx +++ b/apps/web/test/fuel-ui.test.tsx @@ -160,8 +160,9 @@ describe("fuel in the client (Step 15.7)", () => { }); renderApp(fakeApi({ getPlayerState: () => all }), "/locations"); const jupiter = await screen.findByLabelText("Fuel at Jupiter Cloud Platform"); - expect(jupiter).toHaveTextContent("6 cr/fu · cheapest"); - expect(screen.getByLabelText("Fuel at Earth")).toHaveTextContent("16 cr/fu"); + // Base price 5 cr/fu (CR-018): Jupiter × 0.6, Earth × 1.6. + expect(jupiter).toHaveTextContent("3 cr/fu · cheapest"); + expect(screen.getByLabelText("Fuel at Earth")).toHaveTextContent("8 cr/fu"); expect(screen.getByLabelText("Fuel at Earth")).not.toHaveTextContent("cheapest"); }); diff --git a/memory-bank/progress.md b/memory-bank/progress.md index d40a082..4f6d70e 100644 --- a/memory-bank/progress.md +++ b/memory-bank/progress.md @@ -429,7 +429,7 @@ Spec: `memory-bank/change-requests/CR-017-tutorial-xp-skill-prompt.md` (received Spec: `memory-bank/change-requests/CR-018-fuel-tuning.md` (received as "CR-017 … Phase 17"). New decisions recorded as plan §0.5.110–0.5.113. - [x] **Step 18.1 — Sim: fuel-to-payout balance check.** `packages/sim/balance/fuel-share.ts`: for every purchasable ship model and every route it can fly, boards at every origin (all locations discovered, that ship as the fleet, 40 periods × 3 worlds), the contracts that fit its hold, and the leg's fuel at the origin's price against their median payout. The balance script prints the table. At the old price (10 cr/fu) the shares ran from 0.7% (Bulk Hauler Jupiter → Moon) to 77.5% (Clipper Mars → Earth); the Courier's Earth → Moon was 20.9%. No single price can put every row in 15–25%, so the user chose to halve the price and gate only rows above 25% (Step 18.2). Tests (`packages/sim/test/fuel-share.test.ts`): a row for every purchasable ship, each on a route it can fly with contracts offered, Earth → Moon for every ship; the Courier's Earth → Moon row is its leg fuel × Earth's price over the median; the table has one line per row. -- [ ] Step 18.2 — Content: lower the base fuel price +- [x] **Step 18.2 — Content: lower the base fuel price.** `baseFuelPricePerFu` 10 → 5 (Earth 8 cr/fu, Jupiter 3); location multiples unchanged. Every ship and route is now at most 25% of the median fitting payout except the Clipper's Mars → Earth (38.8%); the Courier's Earth → Moon is 10.5%. The first Market purchase target is now 30–95 min (user decision): cheaper fuel brings the Clipper's first long landing, and so the first purchase, forward. Balance medians: deal 3.7 min, first purchase 41.6, discovery 39.3, loan 128.1, no violations. Tests: `packages/sim/test/fuel-share.test.ts` (only the Clipper's Mars → Earth is above 25%); `balance.test.ts` (30–95 window); fuel prices updated in `tank.test.ts`, the save 1 → 2 migration test and the Locations fuel test. - [ ] Step 18.3 — Engine: deal ships start full; Manual by default - [ ] Step 18.4 — Client: low-fuel warning and policy display - [ ] Step 18.5 — Plan amendments and regression diff --git a/packages/content/src/data/config.ts b/packages/content/src/data/config.ts index 1008603..1930bc6 100644 --- a/packages/content/src/data/config.ts +++ b/packages/content/src/data/config.ts @@ -38,8 +38,9 @@ export const gameConfig: GameConfig = { boardRetentionPeriods: 24, savedDispatchRetentionRealHours: 168, // Fuel (CR-015): credits per fu before each location's price multiple, and - // the emergency fuel markup (decisions 0.5.96, 0.5.99). - baseFuelPricePerFu: 10, + // the emergency fuel markup (decisions 0.5.96, 0.5.99). Halved by CR-018 + // (decision 0.5.110): the balance script prints each ship and route's fuel share. + baseFuelPricePerFu: 5, emergencyFuelMultiplier: 2.5, // Pay and fuel scale gently with distance past an Earth–Moon leg (decision 0.5.84). economicDistance: { referenceMillionKm: 0.384, exponent: 0.65 }, diff --git a/packages/schema/test/save.test.ts b/packages/schema/test/save.test.ts index 4e3b1d7..a608c2a 100644 --- a/packages/schema/test/save.test.ts +++ b/packages/schema/test/save.test.ts @@ -85,10 +85,11 @@ describe("save migration 1 → 2: fuel tanks (CR-015 §5)", () => { const { save } = result; const before = fullSave(); expect(save.saveVersion).toBe(2); - // The Courier has Extended Fuel Tanks 1 (40 fu × 4.8) and is in flight: the base price. - expect(save.ships[0]).toMatchObject({ fuelFu: 192, fuelAvgPrice: 10, refuelPolicy: "auto" }); - // The Clipper is at Earth: a full 1,170 fu at Earth's 16 cr/fu. - expect(save.ships[1]).toMatchObject({ fuelFu: 1170, fuelAvgPrice: 16, refuelPolicy: "auto" }); + // The Courier has Extended Fuel Tanks 1 (40 fu × 4.8) and is in flight: the base + // price, 5 cr/fu since CR-018 (decision 0.5.110). + expect(save.ships[0]).toMatchObject({ fuelFu: 192, fuelAvgPrice: 5, refuelPolicy: "auto" }); + // The Clipper is at Earth: a full 1,170 fu at Earth's 8 cr/fu. + expect(save.ships[1]).toMatchObject({ fuelFu: 1170, fuelAvgPrice: 8, refuelPolicy: "auto" }); // Earlier trips paid fuel in credits: nothing came out of a tank, nothing topped up. for (const dispatch of save.dispatches) { expect(dispatch.record.fuelBurnedFu).toBe(0); diff --git a/packages/sim/test/balance.test.ts b/packages/sim/test/balance.test.ts index 20a29c3..3c1cb04 100644 --- a/packages/sim/test/balance.test.ts +++ b/packages/sim/test/balance.test.ts @@ -39,9 +39,10 @@ describe("balance simulation (Step 12.1)", () => { expect(median(lighterFirst, "dealAccepted")).toBeLessThan(10); }); - it("buys the first Market ship in 45–95 minutes (CR-013, widened for CR-016)", () => { + it("buys the first Market ship in 30–95 minutes (CR-013, widened for CR-016 and CR-018)", () => { const minutes = median(lighterFirst, "firstMarketPurchase"); - expect(minutes).toBeGreaterThanOrEqual(45); + // Cheaper fuel and full deal tanks let the player buy sooner (user decision, CR-018). + expect(minutes).toBeGreaterThanOrEqual(30); expect(minutes).toBeLessThanOrEqual(95); }); diff --git a/packages/sim/test/fuel-share.test.ts b/packages/sim/test/fuel-share.test.ts index 0fbcb10..f7da985 100644 --- a/packages/sim/test/fuel-share.test.ts +++ b/packages/sim/test/fuel-share.test.ts @@ -44,6 +44,15 @@ describe("fuel-to-payout check (CR-018 Step 18.1, decision 0.5.110)", () => { expect(row.medianPayout).toBeGreaterThan(0); }); + it("keeps fuel at most 25% of the median payout on every route but the Clipper's Mars → Earth (Step 18.2)", () => { + // One price can't put every row in 15–25% (user decision, CR-018): only the top is gated. + // Few Mars → Earth jobs are small enough for the Clipper's 30 t hold, so their median is low. + const over = rows + .filter((r) => r.share > 0.25) + .map((r) => `${r.modelId} ${r.originId} → ${r.destinationId}`); + expect(over).toEqual(["clipper mars → earth"]); + }); + it("reports the share for each ship and route as a table", () => { const table = formatFuelShares(rows).split("\n"); expect(table).toHaveLength(rows.length + 1); diff --git a/packages/sim/test/tank.test.ts b/packages/sim/test/tank.test.ts index e8648f7..ba7ae7a 100644 --- a/packages/sim/test/tank.test.ts +++ b/packages/sim/test/tank.test.ts @@ -117,7 +117,8 @@ describe("fuel tanks (Step 15.4)", () => { const burned = legFuel(courier, earth, routeDistance(earth, moon), config); expect(record.fuelBurnedFu).toBeCloseTo(burned); expect(record.fuelCost).toBe(legFuelCost(burned, ship.tank!)); - expect(record.fuelCost).toBe(Math.round(burned * 16)); + // Filled at Earth: 1.6 × the base price, 8 cr/fu since CR-018 (decision 0.5.110). + expect(record.fuelCost).toBe(Math.round(burned * 8)); }); it("pays a hired pilot from fuel burned × average price (decision 0.5.98)", () => {