Step 18.2: content: lower the base fuel price
CI / check (push) Successful in 6m13s Details

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 <noreply@anthropic.com>
This commit is contained in:
fireshaper 2026-10-10 19:07:08 -04:00
parent 77eaa3ac98
commit 4e7ae7505d
8 changed files with 31 additions and 12 deletions

View File

@ -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. - **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. - **"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. - **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.

View File

@ -160,8 +160,9 @@ describe("fuel in the client (Step 15.7)", () => {
}); });
renderApp(fakeApi({ getPlayerState: () => all }), "/locations"); renderApp(fakeApi({ getPlayerState: () => all }), "/locations");
const jupiter = await screen.findByLabelText("Fuel at Jupiter Cloud Platform"); const jupiter = await screen.findByLabelText("Fuel at Jupiter Cloud Platform");
expect(jupiter).toHaveTextContent("6 cr/fu · cheapest"); // Base price 5 cr/fu (CR-018): Jupiter × 0.6, Earth × 1.6.
expect(screen.getByLabelText("Fuel at Earth")).toHaveTextContent("16 cr/fu"); 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"); expect(screen.getByLabelText("Fuel at Earth")).not.toHaveTextContent("cheapest");
}); });

View File

@ -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. 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. - [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.3 — Engine: deal ships start full; Manual by default
- [ ] Step 18.4 — Client: low-fuel warning and policy display - [ ] Step 18.4 — Client: low-fuel warning and policy display
- [ ] Step 18.5 — Plan amendments and regression - [ ] Step 18.5 — Plan amendments and regression

View File

@ -38,8 +38,9 @@ export const gameConfig: GameConfig = {
boardRetentionPeriods: 24, boardRetentionPeriods: 24,
savedDispatchRetentionRealHours: 168, savedDispatchRetentionRealHours: 168,
// Fuel (CR-015): credits per fu before each location's price multiple, and // Fuel (CR-015): credits per fu before each location's price multiple, and
// the emergency fuel markup (decisions 0.5.96, 0.5.99). // the emergency fuel markup (decisions 0.5.96, 0.5.99). Halved by CR-018
baseFuelPricePerFu: 10, // (decision 0.5.110): the balance script prints each ship and route's fuel share.
baseFuelPricePerFu: 5,
emergencyFuelMultiplier: 2.5, emergencyFuelMultiplier: 2.5,
// Pay and fuel scale gently with distance past an Earth–Moon leg (decision 0.5.84). // Pay and fuel scale gently with distance past an Earth–Moon leg (decision 0.5.84).
economicDistance: { referenceMillionKm: 0.384, exponent: 0.65 }, economicDistance: { referenceMillionKm: 0.384, exponent: 0.65 },

View File

@ -85,10 +85,11 @@ describe("save migration 1 → 2: fuel tanks (CR-015 §5)", () => {
const { save } = result; const { save } = result;
const before = fullSave(); const before = fullSave();
expect(save.saveVersion).toBe(2); expect(save.saveVersion).toBe(2);
// The Courier has Extended Fuel Tanks 1 (40 fu × 4.8) and is in flight: the base price. // The Courier has Extended Fuel Tanks 1 (40 fu × 4.8) and is in flight: the base
expect(save.ships[0]).toMatchObject({ fuelFu: 192, fuelAvgPrice: 10, refuelPolicy: "auto" }); // price, 5 cr/fu since CR-018 (decision 0.5.110).
// The Clipper is at Earth: a full 1,170 fu at Earth's 16 cr/fu. expect(save.ships[0]).toMatchObject({ fuelFu: 192, fuelAvgPrice: 5, refuelPolicy: "auto" });
expect(save.ships[1]).toMatchObject({ fuelFu: 1170, fuelAvgPrice: 16, 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. // Earlier trips paid fuel in credits: nothing came out of a tank, nothing topped up.
for (const dispatch of save.dispatches) { for (const dispatch of save.dispatches) {
expect(dispatch.record.fuelBurnedFu).toBe(0); expect(dispatch.record.fuelBurnedFu).toBe(0);

View File

@ -39,9 +39,10 @@ describe("balance simulation (Step 12.1)", () => {
expect(median(lighterFirst, "dealAccepted")).toBeLessThan(10); 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"); 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); expect(minutes).toBeLessThanOrEqual(95);
}); });

View File

@ -44,6 +44,15 @@ describe("fuel-to-payout check (CR-018 Step 18.1, decision 0.5.110)", () => {
expect(row.medianPayout).toBeGreaterThan(0); 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", () => { it("reports the share for each ship and route as a table", () => {
const table = formatFuelShares(rows).split("\n"); const table = formatFuelShares(rows).split("\n");
expect(table).toHaveLength(rows.length + 1); expect(table).toHaveLength(rows.length + 1);

View File

@ -117,7 +117,8 @@ describe("fuel tanks (Step 15.4)", () => {
const burned = legFuel(courier, earth, routeDistance(earth, moon), config); const burned = legFuel(courier, earth, routeDistance(earth, moon), config);
expect(record.fuelBurnedFu).toBeCloseTo(burned); expect(record.fuelBurnedFu).toBeCloseTo(burned);
expect(record.fuelCost).toBe(legFuelCost(burned, ship.tank!)); 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)", () => { it("pays a hired pilot from fuel burned × average price (decision 0.5.98)", () => {