deadhead-space-freight-tycoon/memory-bank/change-requests/CR-007-cargo-goods.md

207 lines
18 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-007 — Cargo Goods, Priority Contracts, and Shippers
> **Implementation notes (added when implemented).** The user supplied this CR as "CR-005, Phase 10E", written before the UI overhaul (CR-005) and fleet-fitted boards (CR-006) existed. It is recorded and built as **CR-007, Phase 10G**, after Phase 10F.
>
> - **Numbering:** decisions 0.5.39–0.5.44 below are recorded as **0.5.54 Goods, 0.5.55 Tier from goods, 0.5.56 Priority, 0.5.57 Board mix unchanged, 0.5.58 Shippers, 0.5.59 Tutorial goods**. Steps 10E.1–10E.11 are **10G.1–10G.11**. The error code `too_slow` is `too-slow` (the API's kebab-case convention).
> - **Mass: the band wins** (user decision). Boards draw mass from per-ship-size bands (`contractMassBands`, playtest fix and CR-006). A contract picks a good of its class whose mass range overlaps its slot's band and draws mass inside both; if no good of that class overlaps, it uses the class's good with the largest maximum and draws from the band (e.g. a 300 t bulk lot of Packaged Food). The lightest band has no floor for goods, so light goods (Research Samples, Antimatter Traps) still appear. Masses under 10 t are drawn to 0.1 t, heavier ones to whole tonnes.
> - **Class mix:** to keep decision 0.5.57 (and the ±3-point test), class selection still uses the class-level lists. They are kept on each location as `cargoClassMix` (`produces`/`demands` of class ids, unchanged values), while `produces`/`demands` now list goods, derived from the catalog. Baseline before the change (1,000 Earth boards, all discovered, seed 12345, no fleet): **Standard 76.79 %, High-Value 23.21 %, Heavy 0 %, Dangerous 0 %**.
> - **Fleet-fitted boards (CR-006):** fit jobs pick goods too: High-Value and Dangerous goods only up to the pilot's skill, mass within the ship's capacity, and Priority only when the ship meets the window.
> - **Tutorial contracts are never Priority.**
> - **Database:** contracts live in JSONB (stored boards and dispatch records), so no column changes; migration `0005` backfills existing rows with a default good, shipper and no Priority so saves survive.
**Project:** Deadhead: Space Freight Tycoon
**Applies to:** a build with Phases 1–10 and CR-001 through CR-004 complete
**Do this:** as **Phase 10E**, after Phase 10D and before Phase 11 *(built as Phase 10G, see notes)*
**Related docs:** `implementation-plan.md`, the earlier change requests, `game-design-document.md`, `DECISIONS.md`
---
## 1. Why
Contract cards currently say only "Standard · 14 t". The board already mixes cargo classes well, but nothing says what is being shipped, who is shipping it, or why.
This change:
- Adds a **catalog of named goods** (Medical Supplies, Lunar Titanium, Helium-3 Canisters…). Each good belongs to a cargo class, has its own mass range and pay, and is produced and demanded at specific locations. Contracts carry a good, which is pillar 3 of the GDD: every load goes somewhere for a reason.
- Adds **Priority** contracts: a time-sensitive good that must arrive within a delivery window, paying a bonus. It needs no skill, only a fast enough ship, which gives the Courier a real role.
- Adds **shipper names** as flavor (no mechanics yet; reputation later can use them).
**The existing board mix stays as it is.** The generator keeps choosing cargo classes the way it does now; this change only picks a named good within the chosen class and may mark it Priority.
## 2. Concepts
| Term | Meaning |
|---|---|
| Cargo class | The existing four: Standard, Heavy, High-Value, Dangerous. Classes keep their skill requirements. |
| Good | A named item in the catalog. Has a class, mass range, pay multiplier, origins, destinations, and optional Priority eligibility. |
| Tier | 1–5 for non-Standard classes, the required skill level. High-Value and Dangerous now take their tier from the good; Heavy keeps its current tier rule (see §4). |
| Priority | A modifier on a Priority-eligible good. The contract has a delivery window; the ship's trip time must fit inside it. Pays a bonus. |
| Shipper | A flavor company name per location shown on the card. |
## 3. Goods catalog
Numbers are starting values for balance. All of them live in `packages/content`. Pay × multiplies the class's base pay per tonne per million km.
### Standard (no skill)
| Id | Name | Mass (t) | Pay × | Priority | Produced at | Demanded at |
|---|---|---|---|---|---|---|
| `mail` | Mail & Parcels | 1–8 | 1.2 | Yes | Earth, Mars | Moon, Mars, Ceres, Jupiter, Earth |
| `packaged-food` | Packaged Food | 10–60 | 1.0 | No | Earth | Moon, Mars, Ceres, Jupiter |
| `consumer-goods` | Consumer Goods | 5–40 | 1.0 | No | Earth | Moon, Mars, Ceres, Jupiter |
| `optical-glass` | Lunar Optical Glass | 4–25 | 1.1 | No | Moon | Earth, Mars |
| `moon-souvenirs` | Moon Dust Souvenirs | 1–6 | 0.9 | No | Moon | Earth |
| `hydroponic-produce` | Hydroponic Produce | 5–30 | 1.1 | Yes | Mars | Moon, Ceres, Jupiter |
| `salvaged-components` | Salvaged Components | 5–30 | 0.9 | No | Ceres | Moon, Mars |
| `bioplastics` | Atmospheric Bioplastics | 5–40 | 1.0 | No | Jupiter | Earth, Mars, Ceres |
### High-Value (Expensive Cargo skill)
| Id | Name | Mass (t) | Pay × | Tier | Priority | Produced at | Demanded at |
|---|---|---|---|---|---|---|---|
| `medical-supplies` | Medical Supplies | 1–10 | 1.0 | 1 | Yes | Earth | Moon, Mars, Ceres, Jupiter |
| `electronics` | Electronics | 2–15 | 1.0 | 1 | No | Earth | Moon, Mars, Ceres, Jupiter |
| `seed-stock` | Seed Stock & Embryos | 0.5–4 | 1.3 | 2 | Yes | Earth | Moon, Mars, Ceres |
| `low-g-pharma` | Low-G Pharmaceuticals | 0.5–5 | 1.4 | 2 | Yes | Moon | Earth, Mars |
| `research-samples` | Research Samples | 0.2–3 | 1.5 | 2 | Yes | Moon, Mars, Ceres | Earth |
| `rare-earths` | Martian Rare Earths | 5–30 | 1.4 | 3 | No | Mars | Earth |
| `platinum` | Platinum Ingots | 2–20 | 1.8 | 4 | No | Ceres | Earth |
### Heavy (Heavy Cargo skill)
Tier follows the generator's existing Heavy tier rule.
| Id | Name | Mass (t) | Pay × | Priority | Produced at | Demanded at |
|---|---|---|---|---|---|---|
| `machine-parts` | Machine Parts | 20–90 | 1.0 | Yes ("Emergency Parts") | Earth, Mars | Moon, Mars, Ceres, Jupiter |
| `habitat-prefabs` | Habitat Prefabs | 40–180 | 1.0 | No | Earth | Moon, Mars, Ceres |
| `lunar-titanium` | Lunar Titanium | 30–120 | 1.1 | No | Moon | Earth, Mars |
| `iron-ingots` | Iron Ingots | 50–200 | 0.9 | No | Mars | Moon, Ceres |
| `nickel-iron-ore` | Nickel-Iron Ore | 80–300 | 0.8 | No | Ceres | Earth, Mars, Moon |
| `water-ice` | Water Ice | 60–250 | 0.8 | No | Ceres | Mars, Moon, Jupiter |
### Dangerous (Dangerous Cargo skill)
Tier is the good's hazard class.
| Id | Name | Mass (t) | Pay × | Tier | Priority | Produced at | Demanded at |
|---|---|---|---|---|---|---|---|
| `industrial-chemicals` | Industrial Chemicals | 5–40 | 1.0 | 1 | No | Earth | Moon, Mars, Ceres, Jupiter |
| `liquid-oxygen` | Liquid Oxygen | 20–100 | 1.0 | 1 | No | Moon | Mars, Ceres, Jupiter |
| `mining-charges` | Mining Charges | 2–20 | 1.3 | 2 | No | Earth, Mars | Moon, Ceres |
| `methane-fuel` | Methane Fuel | 20–80 | 1.1 | 2 | No | Mars | Moon, Ceres |
| `liquid-hydrogen` | Liquid Hydrogen | 30–150 | 1.1 | 2 | No | Jupiter | Mars, Ceres, Earth |
| `helium-3` | Helium-3 Canisters | 1–10 | 2.0 | 3 | Yes | Jupiter | Earth, Moon |
| `reactor-cores` | Fusion Reactor Cores | 10–40 | 1.8 | 4 | No | Earth | Ceres, Jupiter |
| `antimatter-traps` | Antimatter Containment Traps | 0.1–1 | 4.0 | 5 | No | Jupiter | Earth |
### Shippers (flavor only)
| Location | Shippers |
|---|---|
| Earth | Pacific Orbital Logistics, Meridian Health, Atlas Heavy Industries, Hollis & Crane Consumer |
| Moon | Shackleton Mining Co., Tranquility Labs, Copernicus Glassworks |
| Mars | Jezero Agricultural Co-op, Red Line Metals, Tharsis Chemical |
| Ceres | Belt Ore Consolidated, Piazzi Station Authority |
| Jupiter Cloud Platform | Galilean Fuel Combine, Zephyr Gas Works |
All names are invented for this game.
## 4. Decisions
Record these in `DECISIONS.md` and `implementation-plan.md` §0.5. *(Recorded as 0.5.54–0.5.59.)*
- **0.5.39 Goods.** Contracts carry a good from the catalog. A location's produces and demands lists now name goods, not cargo classes. After the generator picks a contract's cargo class (unchanged logic), it picks a good of that class produced at the origin and demanded at the destination, and draws the mass from that good's range. If no good of that class fits the route, fall back to a Standard good that does; if none does (for example a first-contact destination), use any Standard good produced at the origin. *(Mass: see the implementation notes, the band wins.)*
- **0.5.40 Tier from goods.** For High-Value and Dangerous contracts, the tier is the good's tier, replacing the random tier. Standard stays tier 0. Heavy keeps its current tier rule.
- **0.5.41 Priority.** A Priority-eligible good becomes a Priority contract with the configured chance. Its delivery window is the trip time of the configured reference ship model (default: Freighter) carrying this cargo on this route at offer time, times the window factor (default 1.0). At dispatch, the chosen ship's trip time must be no longer than the window, or validation fails with `too_slow`. Priority pays the configured bonus (default ×1.5). No skill is required.
- **0.5.42 Board mix unchanged.** Class selection, board size, and first-contact chance keep their current logic and config.
- **0.5.43 Shippers.** Each contract gets a shipper name drawn from its origin's list. No mechanics.
- **0.5.44 Tutorial goods.** The tutorial board uses Standard goods produced at the loaner's location and demanded at the other end (Earth→Moon: mail, packaged food, consumer goods; Moon→Earth: optical glass, souvenirs). The scripted "Unmarked crates" contract is unchanged.
## 5. 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`.
- Resetting the dev and test databases is fine (stored boards and contract snapshots change shape). 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 10E.
## 6. Steps — Phase 10E *(built as 10G)*
### Step 10E.1 — Content: goods catalog
- Add a Good schema: id, name, class, mass min and max, pay multiplier, tier (High-Value and Dangerous only), Priority eligible (with optional Priority display name), produced at, demanded at.
- Add all goods from §3. Change each location's produces and demands lists to good ids (derived from the catalog's produced-at and demanded-at, or kept in sync by a test).
- Add config: Priority chance (default 15% of contracts whose good is Priority-eligible), Priority bonus, Priority reference ship model, Priority window factor. Add the shipper lists.
- **Test:** Every good validates and references existing classes and locations. Mass min ≤ max. High-Value and Dangerous goods have a tier from 1 to 5; Standard and Heavy goods don't. Every location produces at least one Standard good. Earth→Moon and Moon→Earth each have at least two Standard goods. Every location has at least one shipper. Location produces/demands match the catalog.
### Step 10E.2 — Sim: contracts carry goods
- Contract shape gains: good id, shipper name, Priority flag, and delivery window (game hours, Priority only).
- Tier is derived per decision 0.5.40.
- Payout becomes: class base pay × good pay multiplier × tier multiplier × mass × distance × first-contact multiplier (if flagged) × Priority bonus (if flagged).
- **Test:** A High-Value or Dangerous contract's tier equals its good's tier. A Heavy contract's tier follows the existing rule. Doubling the good's pay multiplier doubles payout. A Priority contract pays exactly the bonus times an otherwise identical non-Priority contract.
### Step 10E.3 — Sim: goods, Priority, and shippers in board generation
- Keep the existing class selection. After a class is chosen, pick the good, mass, Priority flag and window, and shipper per decisions 0.5.39, 0.5.41, and 0.5.43.
- Keep everything deterministic from the existing inputs.
- **Test:** Same inputs give identical boards. Every contract's good is produced at the origin and (except first-contact and fallbacks) demanded at the destination, and its mass is within the good's range. Over 1,000 boards from Earth with all locations discovered: the share of each cargo class is within ±3 percentage points of what the pre-change generator produces for the same inputs (record the baseline before changing the generator); Priority appears only on eligible goods and on 10–20% of eligible contracts; every contract has a shipper from its origin's list. First-contact rate is still 5–15%. *(Mass within the good's range except band fallbacks, per the implementation notes.)*
- **Amended by CR-019:** The class-mix test keeps its fleet-free inputs: with any fleet, CR-006's fit slots pick goods the crew can carry instead of drawing from the class mix. A second test shows a full-capability fleet filters nothing (Step 19.2).
### Step 10E.4 — Sim: Priority validation
- Dispatch validation gains `too_slow`: for a Priority contract, the ship's trip time (with handling time, at departure) must not exceed the delivery window.
- **Test:** On a Priority Earth→Moon contract with the default reference (Freighter), a Courier passes and a Bulk Hauler fails with `too_slow`. A non-Priority contract never fails with `too_slow`.
### Step 10E.5 — Sim: tutorial goods
- The tutorial board picks goods per decision 0.5.44. The three mass tiers from CR-001 still apply, clamped to each good's range.
- **Test:** With the loaner at Earth, every tutorial good is a Standard good produced at Earth and demanded at the Moon; at the Moon, the reverse. "Unmarked crates" still appears on the scripted run.
### Step 10E.6 — Schema and database
- Stored boards and dispatch contract snapshots include good id, shipper, Priority flag, and window.
- Add the `too_slow` error code. API contract shape gains the new fields.
- Update `architecture.md`.
- **Test:** Migrations run on a fresh database and are a no-op the second time. Changed schemas accept valid examples and reject invalid ones.
### Step 10E.7 — Server: pass-through and validation
- Boards and dispatch use the new sim functions; no new endpoints.
- **Test:** A Priority contract dispatched with a too-slow ship returns `too_slow` and changes nothing; with a fast ship it succeeds. A fetched board's contracts all include a good name and shipper.
### Step 10E.8 — Client: contract card redesign
- Card layout, top to bottom:
- Route and payout (as now).
- **Good name** as the headline, with a class chip showing tier (for example "HIGH-VALUE · T1") and, for Priority, a chip "PRIORITY · within 5h" (game hours, with real minutes on tap or hover).
- Shipper · mass · distance.
- Requirement ("No skills needed", "Expensive Cargo 1") and expiry.
- Status badges as now, plus "TOO SLOW" when no idle ship at the origin can meet the window.
- **Test:** Component test: a card shows good name, class and tier chip, shipper, mass, and requirement; a Priority card shows the window chip; a Priority card with only slow idle ships at the origin shows "TOO SLOW".
- **Amended by CR-019:** The "TOO SLOW" badge is removed: boards show only jobs some pair can complete; the dispatch sheet still gives each slow pair's reason (Step 19.4).
### Step 10E.9 — Client: Priority in the dispatch sheet
- For a Priority contract, each ship+pilot pair shows its trip time against the window. Pairs that don't fit are disabled with "Too slow — 7h vs 5h window".
- **Test:** Component test: a fast enough pair is enabled and shows its margin; a slow pair is disabled with the correct numbers from the sim.
### Step 10E.10 — Client: goods in summaries
- Delivery summaries name the good and shipper, for example "Delivered 6 t Medical Supplies for Meridian Health to the Moon".
- **Test:** Component test: a seeded summary shows the good, mass, shipper, and destination.
### Step 10E.11 — Plan amendments and regression
- Apply the amendments in §7 to `implementation-plan.md`.
- Run the full suite.
- **Test:** CI is green. Manual check: an Earth board after the CR-001 deal shows the same kind of class mix as before, now with named goods and shippers, and at least one Priority contract appears within a few board refreshes.
## 7. Amendments to `implementation-plan.md`
| Step | Change |
|---|---|
| 0.5.6 | Amended by 0.5.40 (High-Value and Dangerous tiers come from goods). |
| 0.5.17 | Cargo fallback now uses goods (0.5.39). |
| 2.2 | Location produces/demands list good ids (10E.1). |
| 2.4 | Cargo types become cargo classes; goods catalog added (10E.1). |
| 3.8 | Contract shape and payout include goods and Priority (10E.2). |
| 3.9 | Class selection unchanged; goods, Priority, and shippers added (10E.3). |
| 3.12 | Validation gains `too_slow` (10E.4). |
| CR-001 10A.3 | Tutorial board uses named goods (10E.5). |
| 9.4, 9.5 | Card redesign and Priority in dispatch (10E.8, 10E.9). |
| 12.1 | The simulated player considers Priority contracts and skips ones its ships are too slow for. Re-check all timing targets; adjust content values, not thresholds. |
## 8. Out of scope
Changes to how the board mixes cargo classes, board filters, Fragile cargo and the Fragile skill, the Urgent Delivery skill, fixed "deliver by" clock deadlines, late-delivery penalties, cargo damage, supply and demand prices that react to deliveries, and shipper reputation. Anything else in `implementation-plan.md` §0.3.