deadhead-space-freight-tycoon/memory-bank/change-requests/CR-009-ship-upgrades.md

129 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Change Request CR-009 — Ship Upgrades
> **Numbering note (added when implemented):** this request arrived titled "CR-006 — Ship Upgrades", for "Phase 10F", applying to "CR-001 through CR-005". CR-006 to CR-008 and Phases 10F–10H already existed, so it is stored as **CR-009** and done as **Phase 10I**: every "10F.n" below means **10I.n**. Its decisions 0.5.45–0.5.50 are recorded as **0.5.63 Effective stats, 0.5.64 Where and when, 0.5.65 One level at a time, 0.5.66 Shop time, 0.5.67 Models stay distinct, 0.5.68 Courier to Mars** (0.5.45–0.5.62 were in use). Error codes use the API's kebab-case and existing codes where one already means the same thing: `not_at_hq` → `not-at-hq`, `ship_busy` → `ship-not-idle`, `ship_in_shop` → `ship-in-shop`, `upgrade_maxed` → `upgrade-maxed`, `insufficient_funds` → `insufficient-funds`, `tutorial_active` → `tutorial-active`, `not_upgradable` → `not-upgradable`; `too_slow` is the existing `too-slow`.
**Project:** Deadhead: Space Freight Tycoon
**Applies to:** a build with Phases 1–10 and CR-001 through CR-005 complete
**Do this:** as **Phase 10F**, after Phase 10E and before Phase 11
**Related docs:** `implementation-plan.md`, the earlier change requests, `game-design-document.md`, `DECISIONS.md`
---
## 1. Why
A ship's stats are fixed by its model, so the only way to improve the fleet is to buy a different ship. There is nothing to spend money on between ship purchases and no way to make a favorite ship better.
This adds an ATS-style tuning shop:
- Five upgrades, each with **three levels**, each improving one ship stat.
- Upgrades are installed **at an HQ (bay)**, which gives bays a second purpose besides hiring pilots.
- The ship is **out of service** while it's in the shop: start an upgrade, put the phone down, come back to a better ship.
- **Caps keep ship models distinct.** A maxed Courier is still a small, fast ship, never a hauler. One deliberate exception: a Courier with maxed fuel tanks can reach Mars around closest approach, as an ambitious early goal.
## 2. Upgrades
All numbers are starting values in `packages/content`. Percentages are of the ship model's base stat and add up across levels (level 3 engines = +30% speed).
| Upgrade id | Name | Improves | Per level | Max level |
|---|---|---|---|---|
| `engines` | Engine Tuning | Cruise speed | +10% | 3 |
| `cargo-bay` | Cargo Bay Expansion | Cargo capacity | +15% | 3 |
| `fuel-tanks` | Extended Fuel Tanks | Fuel range | +20% | 3 (see 10F.1 for the Courier) |
| `landing-thrusters` | Landing Thrusters | Takeoff/landing overhead | −15% | 3 |
| `handling-gear` | Cargo Handling Gear | Handling time per tonne | −15% | 3 |
| Level | Cost (% of ship model's purchase price) | Time in shop (real minutes) |
|---|---|---|
| 1 | 10% | 10 |
| 2 | 20% | 20 |
| 3 | 35% | 30 |
Hull plating is left out until random events exist, because hull rating has no effect yet.
## 3. Rules for this change
- All rules in `implementation-plan.md` §0.1 apply: steps in order, one commit per step, each step's test must pass, `packages/sim` stays pure, all numbers live in `packages/content`.
- Every place that reads a ship's speed, capacity, range, overhead, or handling time must use the ship's **effective stats** (model plus upgrades), never the model's stats directly.
- Resetting the dev and test databases is fine. Schema migrations are still required.
- Update existing tests whose expectations change. Update `architecture.md` for schema or API changes and `progress.md` after every step.
- Stop for the user's review at the end of Phase 10F.
## 4. Decisions
Record these in `DECISIONS.md` and `implementation-plan.md` §0.5.
- **0.5.45 Effective stats.** A ship's effective stats are its model's base stats adjusted by its installed upgrade levels. Percentage bonuses are of the base stat and add up linearly.
- **0.5.46 Where and when.** An upgrade can be started only on an owned ship that is idle (not in flight, not already in the shop) at a location with a player bay, with tutorial mode off. The Company Courier loaner can't be upgraded.
- **0.5.47 One level at a time.** Each purchase raises one upgrade by exactly one level. A ship can be in the shop for one upgrade at a time.
- **0.5.48 Shop time.** Starting an upgrade deducts the cost, raises the level immediately, and sets the ship's in-shop-until time. The ship can't be dispatched until then (`ship_in_shop`). Because dispatch is blocked, there is nothing to complete later: no background job is needed. The assigned pilot stays assigned and can rest meanwhile.
- **0.5.49 Models stay distinct.** Content caps guarantee: a maxed Courier's capacity is below a base Freighter's; a maxed Bulk Hauler's speed is below a base Freighter's; a maxed Freighter's range is below a base Bulk Hauler's (so only Bulk Haulers reach Jupiter at its farthest).
- **0.5.50 Courier to Mars.** A Courier with maxed fuel tanks can reach Mars at and around closest approach, but not at Mars's farthest distance. Base and lower-level Couriers still can't reach Mars at all.
## 5. Steps — Phase 10F
### Step 10F.1 — Content: upgrades
- Add an Upgrade schema: id, name, stat affected, change per level (percent), max level, and optional per-model max level overrides.
- Add the five upgrades from §2, and config for cost percent and shop time per level.
- Set the Courier's `fuel-tanks` values so decision 0.5.50 holds (adjust the Courier's per-level bonus or cap, not the Courier's base range, and don't break any Step 3.7 range test).
- **Test:** All upgrades validate. Every upgrade affects a stat that exists on ShipModel. Tests for decision 0.5.49 (all three inequalities). Tests for decision 0.5.50: sampling Earth–Mars distance monthly over two game years, a maxed Courier is in range on at least one sample and out of range on at least one sample; a Courier with fuel tanks at level 2 is never in range.
- **Amended by CR-012:** Caps follow the amended 0.5.49/0.5.50 (this plan's 0.5.67/0.5.68): three more distinctness inequalities, Courier-to-Mars measured from Earth over 16 game years; the Courier's tanks are 450 % per level (Step 10L.5).
- **Amended by CR-026:** Hull Plating is added (+1 hull rating a level, three levels, the usual costs and shop times); upgrades may add a flat amount (`flatPerLevel`) instead of a percent.
- **Amended by CR-027:** Upgrades (one level a visit) and repairs can be booked from anywhere for an HQ; the work starts, and is charged, when the ship is next idle there.
### Step 10F.2 — Sim: effective stats
- Add a pure function that returns a ship's effective stats from its model and installed upgrade levels.
- Change travel time, fuel range checks, capacity checks, handling time, and Priority window checks to take effective stats.
- **Test:** With no upgrades, effective stats equal the model's stats exactly, and all existing Phase 3 tests pass unchanged. Each upgrade changes only its own stat. Level 3 engines give exactly +30% speed. A cargo-bay upgrade turns a contract that failed with the too-heavy reason into a valid one. An engines upgrade turns a contract that failed with `too_slow` into a valid one.
### Step 10F.3 — Sim: upgrade validation and cost
- Add pure functions: next-level cost and shop time for a ship and upgrade; validation for starting an upgrade per decisions 0.5.46 and 0.5.47, with reasons: `not_at_hq`, `ship_busy` (in flight), `ship_in_shop`, `upgrade_maxed`, `insufficient_funds`, `tutorial_active`, `not_upgradable` (the loaner).
- Dispatch validation gains `ship_in_shop`.
- **Test:** One test per reason. Cost for level 1 on a Freighter equals 10% of the Freighter's price; level 3 equals 35%. A ship in the shop fails dispatch validation with `ship_in_shop` one second before in-shop-until and passes at it.
### Step 10F.4 — Schema and database
- Add a `ship_upgrades` table (ship, upgrade id, level) and an in-shop-until time on ships.
- Add a "start upgrade" endpoint (ship id, upgrade id) to the route catalog, and the new error codes.
- Player state includes, per ship: installed upgrade levels, effective stats, and in-shop-until.
- Update `architecture.md`.
- **Test:** Migrations run on a fresh database and are a no-op the second time. New and changed schemas accept valid examples and reject invalid ones. The route catalog test still passes.
### Step 10F.5 — Server: start upgrade
- In one transaction with the company row lock (Step 6.14): validate, deduct the cost, raise the level by one, set in-shop-until.
- Dispatch uses effective stats and returns `ship_in_shop` for ships in the shop.
- **Test:** Starting a level 1 engines upgrade on an idle Freighter at the Earth HQ deducts the right cost, sets the level to 1 and the correct in-shop-until. Dispatching it before then fails with `ship_in_shop`; after, it succeeds and its trip time reflects the faster speed. Starting an upgrade at a location without a bay fails with `not_at_hq`. Two simultaneous upgrade requests the player can afford only one of produce exactly one upgrade and no negative balance.
### Step 10F.6 — Client: upgrade shop
- Each ship on the Fleet screen opens a detail view with an **Upgrades** panel listing all five upgrades: current level, next level's effect as before → after (for example "Speed 180 → 198 km/h"), cost, and shop time.
- Disabled upgrades show the reason (not at an HQ, in flight, in the shop, maxed, can't afford).
- Confirming starts the upgrade. Ships in the shop show "In the shop — mm:ss" on their Fleet card.
- **Test:** Component test: before → after values match the sim's effective stats; each disabled reason displays correctly; confirming calls the endpoint; an in-shop ship shows the countdown.
### Step 10F.7 — Client: effective stats everywhere
- Contract card badges (TOO HEAVY, TOO SLOW), the dispatch sheet, the deadhead picker, the Market's ship comparison for owned ships, and map reachability text all use effective stats.
- **Test:** Component test: after a seeded cargo-bay upgrade, a contract that showed TOO HEAVY for that ship no longer does; the deadhead picker enables a destination that only the upgraded range reaches.
### Step 10F.8 — Client: shop in summaries and on HQ
- The "while you were away" modal shows "[Ship] is out of the shop: Engine Tuning 2" for upgrades that finished while the player was away.
- HQ shows ships currently in the shop with countdowns.
- **Test:** Component test with seeded state: the modal line appears for an upgrade that finished since the last visit; HQ lists in-shop ships with correct countdowns.
### Step 10F.9 — Plan amendments and regression
- Apply the amendments in §6 to `implementation-plan.md`, and update `game-design-document.md` §8 (done by the designer; confirm it matches).
- Run the full suite.
- **Test:** CI is green. Manual check: on a fresh account after the CR-001 deal, earn enough to buy Cargo Bay Expansion 1 for the Courier at the Earth HQ, confirm it's unavailable for the shop time, then confirm it can take a contract that was too heavy before.
## 6. Amendments to `implementation-plan.md`
| Step | Change |
|---|---|
| 0.5 | Add decisions 0.5.45–0.5.50. |
| 2.1 | Content gains the Upgrade schema and upgrade cost/time config (10F.1). |
| 3.5, 3.7, 3.12 | Use effective stats; validation gains `ship_in_shop` (10F.2, 10F.3). |
| 5.3 | `ship_upgrades` table and in-shop-until on ships (10F.4). |
| 9.5, 9.6, 9.8 | Effective stats in dispatch, Fleet, and Market; upgrade shop on Fleet (10F.6, 10F.7). |
| 12.1 | The simulated player may buy upgrades: when it can't yet afford its next planned ship, it buys the cheapest available upgrade on its most-used ship, preferring cargo bay and engines. Re-check all timing targets; adjust content values, not thresholds. |
## 7. Out of scope
Hull plating (until random events exist), selling or removing upgrades, ship resale value, changing a ship's landing class, cosmetic liveries, ship renaming, and anything else in `implementation-plan.md` §0.3.