# 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.