Step 10F.1: highlight jobs too heavy for your ships
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
81e71e8436
commit
bd2d2df14d
|
|
@ -874,3 +874,11 @@ Implementation decisions not spelled out in `memory-bank/implementation-plan.md`
|
||||||
- **`contractBoardPeriodRealMinutes` 60 → 10 and `restGameHours` 60 → 10** (user request, after Phase 10D). At 60× compression a rest is now 10 real minutes, exactly one board period, so decision 0.5.46 still holds and its content test (rest in real time ≥ board period) is unchanged and passing.
|
- **`contractBoardPeriodRealMinutes` 60 → 10 and `restGameHours` 60 → 10** (user request, after Phase 10D). At 60× compression a rest is now 10 real minutes, exactly one board period, so decision 0.5.46 still holds and its content test (rest in real time ≥ board period) is unchanged and passing.
|
||||||
- **Effects:** boards refresh every 10 real minutes and every contract expires at the end of its period, so offers turn over six times as fast. A rested pilot is back in 10 minutes instead of an hour. This also resolves the earlier note that the CR-004 example expected a short rest.
|
- **Effects:** boards refresh every 10 real minutes and every contract expires at the end of its period, so offers turn over six times as fast. A rested pilot is back in 10 minutes instead of an hour. This also resolves the earlier note that the CR-004 example expected a short rest.
|
||||||
- **Two tests assumed a 60-minute period** and now derive their timing from config instead: the server's `testContract` helper (expiry = end of the board period) and the board-refresh countdown test. No game code changed.
|
- **Two tests assumed a 60-minute period** and now derive their timing from config instead: the server's `testContract` helper (expiry = end of the board period) and the board-refresh countdown test. No game code changed.
|
||||||
|
|
||||||
|
## CR-006 — Boards That Fit Your Fleet (Phase 10F)
|
||||||
|
|
||||||
|
- **Requested by the user** after CR-005: highlight weight a ship can't carry, and offer more jobs that fit the player's ships and pilot skills than the one guaranteed job per band. Stored in `memory-bank/change-requests/`, inserted as Phase 10F before Phase 11; decisions are plan §0.5.52–0.5.53.
|
||||||
|
|
||||||
|
## Step 10F.1
|
||||||
|
|
||||||
|
- **"Your ship" means the ships at the board's location**, because those are the ones that can take the job now; with none there, the whole fleet (one of them could deadhead in). Ships in flight have no location, so they only count in the fallback.
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,18 @@ export function requirementsText(contract: Contract): string | null {
|
||||||
return parts.length > 0 ? parts.join(", ") : null;
|
return parts.length > 0 ? parts.join(", ") : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The most a player's ship can carry for a board at this location: the largest
|
||||||
|
* ship here, or in the whole fleet when none are here; null with no ships
|
||||||
|
* (decision 0.5.53).
|
||||||
|
*/
|
||||||
|
export function carryLimit(state: PlayerState, locationId: string): number | null {
|
||||||
|
const here = state.ships.filter((s) => s.locationId === locationId);
|
||||||
|
const ships = here.length > 0 ? here : state.ships;
|
||||||
|
if (ships.length === 0) return null;
|
||||||
|
return Math.max(...ships.map((s) => content.shipModel(s.modelId)?.cargoCapacityTonnes ?? 0));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The location the Contracts screen opens on: where the most recently arrived
|
* The location the Contracts screen opens on: where the most recently arrived
|
||||||
* idle ship is, otherwise Earth (CR-002).
|
* idle ship is, otherwise Earth (CR-002).
|
||||||
|
|
@ -107,7 +119,9 @@ function LocationBoard({
|
||||||
onSelect?: (contract: Contract) => void;
|
onSelect?: (contract: Contract) => void;
|
||||||
}) {
|
}) {
|
||||||
const api = useApi();
|
const api = useApi();
|
||||||
|
const state = useLoadedPlayer();
|
||||||
const now = useClock((s) => s.now);
|
const now = useClock((s) => s.now);
|
||||||
|
const limit = carryLimit(state, locationId);
|
||||||
const board = useQuery({
|
const board = useQuery({
|
||||||
queryKey: locationBoardKey(locationId),
|
queryKey: locationBoardKey(locationId),
|
||||||
queryFn: () => api.call("getContractBoard", { locationId }),
|
queryFn: () => api.call("getContractBoard", { locationId }),
|
||||||
|
|
@ -161,6 +175,7 @@ function LocationBoard({
|
||||||
<ul className="grid gap-3 sm:grid-cols-2">
|
<ul className="grid gap-3 sm:grid-cols-2">
|
||||||
{board.data.contracts.map((contract) => {
|
{board.data.contracts.map((contract) => {
|
||||||
const requirements = requirementsText(contract);
|
const requirements = requirementsText(contract);
|
||||||
|
const tooHeavy = limit !== null && contract.massTonnes > limit;
|
||||||
return (
|
return (
|
||||||
<Panel
|
<Panel
|
||||||
as="li"
|
as="li"
|
||||||
|
|
@ -191,9 +206,17 @@ function LocationBoard({
|
||||||
<Icon name="cargo" className="size-4 shrink-0 text-ink-400" />
|
<Icon name="cargo" className="size-4 shrink-0 text-ink-400" />
|
||||||
<span>
|
<span>
|
||||||
{cargoName(contract.cargoTypeId)}
|
{cargoName(contract.cargoTypeId)}
|
||||||
{contract.tier > 0 && ` (tier ${contract.tier})`} · {contract.massTonnes} t ·{" "}
|
{contract.tier > 0 && ` (tier ${contract.tier})`} ·{" "}
|
||||||
{formatMillionKm(distanceAt(contract.originId, contract.destinationId, now))}
|
<span className={tooHeavy ? "font-semibold text-rose-300" : undefined}>
|
||||||
|
{contract.massTonnes} t
|
||||||
|
</span>{" "}
|
||||||
|
· {formatMillionKm(distanceAt(contract.originId, contract.destinationId, now))}
|
||||||
</span>
|
</span>
|
||||||
|
{tooHeavy && (
|
||||||
|
<Pill tone="rose" icon="alert" className="ml-auto">
|
||||||
|
Too heavy
|
||||||
|
</Pill>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-auto flex items-end justify-between gap-2 border-t border-space-700/70 pt-2 text-sm">
|
<div className="mt-auto flex items-end justify-between gap-2 border-t border-space-700/70 pt-2 text-sm">
|
||||||
<span className="text-ink-400">
|
<span className="text-ink-400">
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
import type { Contract, Ship } from "@deadhead/schema";
|
||||||
|
import { screen } from "@testing-library/react";
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { carryLimit } from "../src/screens/LocationBoards";
|
||||||
|
import { fakeApi, renderApp, samplePlayerState } from "./render";
|
||||||
|
|
||||||
|
const NOW = Date.now();
|
||||||
|
|
||||||
|
const contract = (id: string, massTonnes: number): Contract => ({
|
||||||
|
id,
|
||||||
|
originId: "earth",
|
||||||
|
destinationId: "moon",
|
||||||
|
cargoTypeId: "standard",
|
||||||
|
tier: 0,
|
||||||
|
massTonnes,
|
||||||
|
payout: 1000 + massTonnes,
|
||||||
|
requiredSkill: null,
|
||||||
|
requiredLongHaulLevel: 0,
|
||||||
|
offeredAt: NOW,
|
||||||
|
expiresAt: NOW + 30 * 60_000,
|
||||||
|
firstContact: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const ship = (id: string, overrides: Partial<Ship>): Ship => ({
|
||||||
|
id,
|
||||||
|
modelId: "courier",
|
||||||
|
name: id,
|
||||||
|
status: "idle",
|
||||||
|
locationId: "earth",
|
||||||
|
dispatchId: null,
|
||||||
|
pilotId: null,
|
||||||
|
lastArrivalAt: 0,
|
||||||
|
...overrides,
|
||||||
|
});
|
||||||
|
|
||||||
|
const owner = (ships: Ship[]) =>
|
||||||
|
samplePlayerState({ tutorial: null, bays: [{ id: "b", locationId: "earth" }], ships });
|
||||||
|
|
||||||
|
const render = (ships: Ship[]) =>
|
||||||
|
renderApp(
|
||||||
|
fakeApi({
|
||||||
|
getPlayerState: () => owner(ships),
|
||||||
|
getContractBoard: () => ({
|
||||||
|
contracts: [contract("light", 10), contract("heavy", 50)],
|
||||||
|
periodEndsAt: NOW + 600_000,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
"/contracts?location=earth",
|
||||||
|
);
|
||||||
|
|
||||||
|
const card = async (payout: string) => (await screen.findByText(payout)).closest("li")!;
|
||||||
|
|
||||||
|
describe("weight warning (Step 10F.1)", () => {
|
||||||
|
it("flags a job heavier than every ship at the location", async () => {
|
||||||
|
render([ship("Dusty Mule", {})]);
|
||||||
|
expect(await card("1,050 cr")).toHaveTextContent("Too heavy");
|
||||||
|
expect(await card("1,010 cr")).not.toHaveTextContent("Too heavy");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses the ships at the location, not elsewhere", async () => {
|
||||||
|
render([
|
||||||
|
ship("Dusty Mule", { locationId: "moon" }),
|
||||||
|
ship("Big Ox", { modelId: "freighter", locationId: "earth" }),
|
||||||
|
]);
|
||||||
|
expect(await card("1,050 cr")).not.toHaveTextContent("Too heavy");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls back to the whole fleet when no ship is at the location", () => {
|
||||||
|
const state = owner([
|
||||||
|
ship("a", { locationId: "moon" }),
|
||||||
|
ship("b", { modelId: "freighter", locationId: null, status: "in-flight" }),
|
||||||
|
]);
|
||||||
|
expect(carryLimit(state, "earth")).toBe(100);
|
||||||
|
expect(carryLimit(owner([]), "earth")).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
# Change Request CR-006 — Boards That Fit Your Fleet
|
||||||
|
|
||||||
|
**Project:** Deadhead: Space Freight Tycoon
|
||||||
|
**Applies to:** a build with Phases 1–10 and CR-001 to CR-005 complete
|
||||||
|
**Do this:** as **Phase 10F**, after Phase 10E and before Phase 11
|
||||||
|
**Related docs:** `implementation-plan.md`, `CR-002-open-markets.md`, `DECISIONS.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Why
|
||||||
|
|
||||||
|
User feedback after CR-005:
|
||||||
|
|
||||||
|
> On the jobs list the weight should also be highlighted if your ship can't carry it. But I'd also like there to be more options that do fit your current stats and ship size instead of just one.
|
||||||
|
|
||||||
|
Each board has 8 contracts in three mass bands (3 up to a Courier's 20 t, 3 up to a Freighter's 100 t, 2 up to a Bulk Hauler's 400 t). Only the first contract of each band is guaranteed to be Standard cargo to a discovered place. The other two Courier-band jobs are usually skill-gated (High-Value tier 1–5) or first contact out of a Courier's range. A player with one Courier and an unskilled pilot typically has **one** job per board they can fly, and five of the eight contracts are for ships they don't own.
|
||||||
|
|
||||||
|
Boards are already generated per player (decision 0.5.21), so generation can look at the player's fleet.
|
||||||
|
|
||||||
|
## 2. How it works
|
||||||
|
|
||||||
|
| Rule | Detail |
|
||||||
|
|---|---|
|
||||||
|
| Fleet | Every ship the player owns, with its assigned pilot's skills (no pilot: no skills, like a fresh hire). Taken when the board is first generated in a period, like the discovered locations. |
|
||||||
|
| Flyable band | A band is flyable when some ship in the fleet can carry its minimum mass. |
|
||||||
|
| Preview slots | A band no ship can carry keeps `boardPreviewSlots` contracts (default 1): a look at what a bigger ship would earn. Its other slots move to flyable bands, largest flyable band first, round-robin. The board size does not change. |
|
||||||
|
| Fit slots | In a flyable band, all but `boardOpenSlotsPerBand` contracts (default 1) are **fit**: built for one ship of the fleet that can carry the band's minimum. Mass is within the band and that ship's capacity; the destination is discovered, reachable (range and landing) and needs no more Long Haul than its pilot has; the cargo is Standard or a skill cargo the pilot has, at a tier up to the pilot's level. Never first contact. |
|
||||||
|
| Open slots | The remaining contracts in a flyable band are generated as today (skill-gated, first contact or any). |
|
||||||
|
| No fit possible | If no ship of the fleet can reach any discovered place from this location, the slot is generated as today. |
|
||||||
|
| No fleet | With an empty fleet (or no fleet input) boards are generated exactly as before. |
|
||||||
|
|
||||||
|
Example, one Courier with an unskilled pilot at Earth: Courier band 3 + 2 freed (Freighter band keeps 1) + 1 freed (Hauler band keeps 1) = 6 contracts, **5 fit** (Earth → Moon, 5–20 t, Standard) and 1 open, plus one Freighter and one Bulk Hauler preview.
|
||||||
|
|
||||||
|
On the board, a contract's mass is highlighted when none of the player's ships can carry it: the ships at that location, or the whole fleet when none are there.
|
||||||
|
|
||||||
|
## 3. Rules for this change
|
||||||
|
|
||||||
|
- All rules in `implementation-plan.md` §0.1 apply. Update `progress.md` after every step.
|
||||||
|
- Board generation stays a pure, deterministic sim function: the same inputs (now including the fleet) give the same board.
|
||||||
|
- The two new numbers are config, not code.
|
||||||
|
- More flyable jobs means more income per board; the payout balance is Phase 12's job. Note anything that looks off.
|
||||||
|
|
||||||
|
## 4. Decisions
|
||||||
|
|
||||||
|
Record as plan §0.5.52–0.5.53.
|
||||||
|
|
||||||
|
- **0.5.52 Boards fit the fleet.** Location boards are generated with the player's fleet (ship models and pilot skills) as input. Bands the fleet can't carry shrink to a preview; in bands it can, all but a configured number of contracts are ones some ship-and-pilot pair can take.
|
||||||
|
- **0.5.53 Weight warning.** The board highlights a contract's mass when none of the player's ships at that location (or in the fleet, when none are there) can carry it. Display only.
|
||||||
|
|
||||||
|
## 5. Steps — Phase 10F
|
||||||
|
|
||||||
|
### Step 10F.1 — Client: weight warning
|
||||||
|
- On location boards, the mass is shown in rose with a "Too heavy" pill when it exceeds the largest capacity among the player's ships at that location (all ships when none are there). No ships at all: no warning.
|
||||||
|
- **Test:** a board with a 50 t job and a Courier at the location shows the warning on it and not on a 10 t job; with the Courier elsewhere and a Freighter at the location, no warning on 50 t.
|
||||||
|
|
||||||
|
### Step 10F.2 — Content: board config
|
||||||
|
- GameConfig gains `boardPreviewSlots` (default 1) and `boardOpenSlotsPerBand` (default 1), non-negative integers.
|
||||||
|
- **Test:** schema accepts and rejects them like other counts; defaults are as listed.
|
||||||
|
|
||||||
|
### Step 10F.3 — Sim: fleet-aware boards
|
||||||
|
- `BoardInput.fleet` (optional): `{ modelId, skills }[]`. Implement §2.
|
||||||
|
- **Test:** no fleet gives the same board as before (existing tests unchanged); determinism with a fleet; Courier-only fleet at Earth: board size unchanged, at least `bands' total − previews − open` fit contracts, each passing `checkRoute`, `checkPilotEligibility` and capacity for the Courier and pilot; a skilled pilot (High-Value 3) gets High-Value fit jobs at tiers ≤ 3 over many periods; a full fleet keeps the configured band counts.
|
||||||
|
|
||||||
|
### Step 10F.4 — Server: pass the fleet
|
||||||
|
- `locationBoard` reads the player's ships and their assigned pilots' skills and passes them as the fleet.
|
||||||
|
- **Test:** a player with one Courier gets at least 5 Earth contracts that pass sim `validateDispatch` for that Courier and pilot.
|
||||||
|
|
||||||
|
### Step 10F.5 — Regression
|
||||||
|
- Plan amendments (§6), full suite, coverage, lint, build. Stop for review.
|
||||||
|
|
||||||
|
## 6. Amendments to earlier steps
|
||||||
|
|
||||||
|
- **Step 3.9 (board generation):** boards take the player's fleet as input (decision 0.5.52).
|
||||||
|
- **Step 6.9 (location board endpoint):** passes the player's fleet to generation.
|
||||||
|
- **Step 9.4 (contracts screen):** contracts too heavy for the player's ships are highlighted (decision 0.5.53).
|
||||||
|
|
@ -107,6 +107,8 @@ These answer the pre-build review. Log each one in `DECISIONS.md` as it is imple
|
||||||
49. **Navigation** (CR-005). Icon tabs: a bottom bar on phones, a left rail from 768 px wide. Tab order and labels are unchanged. Attention badges are decorative (`aria-hidden`), so each tab's accessible name stays its label.
|
49. **Navigation** (CR-005). Icon tabs: a bottom bar on phones, a left rail from 768 px wide. Tab order and labels are unchanged. Attention badges are decorative (`aria-hidden`), so each tab's accessible name stays its label.
|
||||||
50. **Art on screens** (CR-005). Ships and locations show their content `sprite` from the generated map art as plain images with empty `alt`; the name is always in text beside it. A missing image hides itself.
|
50. **Art on screens** (CR-005). Ships and locations show their content `sprite` from the generated map art as plain images with empty `alt`; the name is always in text beside it. A missing image hides itself.
|
||||||
51. **Presentation only** (CR-005). The overhaul changes no game rules, API calls, copy or accessible names.
|
51. **Presentation only** (CR-005). The overhaul changes no game rules, API calls, copy or accessible names.
|
||||||
|
52. **Boards fit the fleet** (CR-006). Location boards are generated with the player's fleet (ship models and pilot skills) as input. Bands the fleet can't carry shrink to a preview; in bands it can, all but a configured number of contracts are ones some ship-and-pilot pair can take.
|
||||||
|
53. **Weight warning** (CR-006). The board highlights a contract's mass when none of the player's ships at that location (or in the fleet, when none are there) can carry it. Display only.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -532,6 +534,12 @@ Steps 10E.1–10E.9 are specified in `memory-bank/change-requests/CR-005-ui-over
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Phase 10F — Boards That Fit Your Fleet (CR-006)
|
||||||
|
|
||||||
|
Steps 10F.1–10F.5 are specified in `memory-bank/change-requests/CR-006-boards-that-fit.md` §5, with its §6 amendments to earlier steps. Done after Phase 10E and before Phase 11; stop for review at the end.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Phase 11 — PWA
|
## Phase 11 — PWA
|
||||||
|
|
||||||
### Step 11.1 — Installable PWA
|
### Step 11.1 — Installable PWA
|
||||||
|
|
|
||||||
|
|
@ -188,3 +188,9 @@ Spec: `memory-bank/change-requests/CR-005-ui-overhaul.md`. New decisions recorde
|
||||||
- [x] **Step 10E.7 — Pilots.** Header eyebrow "N on the roster". Hire panel is an amber feature panel with an icon, styled select and button. Pilot cards (two columns from `md`): initials disc (amber glow for you, cyan for hires) with a level badge, "Level N", XP text and a decorative violet XP bar (not a `progressbar`: a resting pilot's card must have none), an In flight pill or location, the ship; skills on an inset panel as "Name n/max" with pips and a glowing + raise button; skill points in amber when available; duty as an amber `Meter` that turns rose from 85 %; rest as a sky panel with a moon icon; Rest now / confirmation and assign with kit buttons and selects. Existing pilots and duty tests pass unchanged. **Manual check:** scratchpad screenshots reviewed.
|
- [x] **Step 10E.7 — Pilots.** Header eyebrow "N on the roster". Hire panel is an amber feature panel with an icon, styled select and button. Pilot cards (two columns from `md`): initials disc (amber glow for you, cyan for hires) with a level badge, "Level N", XP text and a decorative violet XP bar (not a `progressbar`: a resting pilot's card must have none), an In flight pill or location, the ship; skills on an inset panel as "Name n/max" with pips and a glowing + raise button; skill points in amber when available; duty as an amber `Meter` that turns rose from 85 %; rest as a sky panel with a moon icon; Rest now / confirmation and assign with kit buttons and selects. Existing pilots and duty tests pass unchanged. **Manual check:** scratchpad screenshots reviewed.
|
||||||
- [x] **Step 10E.8 — Locations, map panels, start screen and dialogs.** Locations ("Known space"): planet-art cards (two columns from `md`), emerald tone and an HQ pill where you have a bay, distance in cyan, specs on an inset `dl`, View contracts as a button, Lease bay as a full-width primary; undiscovered places dimmed and greyed with a padlock. Map: Reset view and the tap panels restyled (glass panel with the location's or ship's art, icon close button, View contracts as a button; a side card from `md`). Start screen is a title screen: gradient DEADHEAD wordmark, the Sun, Earth, Moon and a Courier around it, and a large Start button. "While you were away" and the deal are on `Sheet` ("Logbook" with big emerald credits and violet XP, discoveries with a star, goals panel with a success button; the deal as an amber "Incoming transmission" with the Courier's art and a large "Shake on it"). The last `slate-*` classes are gone from `apps/web/src`. Existing locations, map, onboarding, away and deal tests pass unchanged. **Manual check:** scratchpad screenshots reviewed.
|
- [x] **Step 10E.8 — Locations, map panels, start screen and dialogs.** Locations ("Known space"): planet-art cards (two columns from `md`), emerald tone and an HQ pill where you have a bay, distance in cyan, specs on an inset `dl`, View contracts as a button, Lease bay as a full-width primary; undiscovered places dimmed and greyed with a padlock. Map: Reset view and the tap panels restyled (glass panel with the location's or ship's art, icon close button, View contracts as a button; a side card from `md`). Start screen is a title screen: gradient DEADHEAD wordmark, the Sun, Earth, Moon and a Courier around it, and a large Start button. "While you were away" and the deal are on `Sheet` ("Logbook" with big emerald credits and violet XP, discoveries with a star, goals panel with a success button; the deal as an amber "Incoming transmission" with the Courier's art and a large "Shake on it"). The last `slate-*` classes are gone from `apps/web/src`. Existing locations, map, onboarding, away and deal tests pass unchanged. **Manual check:** scratchpad screenshots reviewed.
|
||||||
- [x] **Step 10E.9 — Regression and review.** CR-005 §6 amendments added to plan steps 8.1 (the CR said 8.2; the layout is 8.1, corrected) and 11.1. Typecheck, lint, full suite with coverage (100 files, 680 tests) and the web build are green. The main JS chunk grew from 496 KB to 523 KB, past Vite's 500 KB warning; splitting it is left for Phase 11/13. **Review screenshots** (palette-quantized PNGs, 6 MB total): `docs/screenshots/10e9-final-{hq,map,contracts,dispatch,fleet,pilots,market,locations}-midgame-{desktop,phone}.png`, `10e9-final-{hq,contracts,map}-tutorial-{desktop,phone}.png` and `10e9-final-start-new-{desktop,phone}.png`. Phone shots are full-page captures, so the fixed bottom navigation appears partway down the image. **Manual check for the user:** the look on a real phone, and tap targets in the 7-tab bar on the narrowest phones. **Phase 10E complete — stopped for review.**
|
- [x] **Step 10E.9 — Regression and review.** CR-005 §6 amendments added to plan steps 8.1 (the CR said 8.2; the layout is 8.1, corrected) and 11.1. Typecheck, lint, full suite with coverage (100 files, 680 tests) and the web build are green. The main JS chunk grew from 496 KB to 523 KB, past Vite's 500 KB warning; splitting it is left for Phase 11/13. **Review screenshots** (palette-quantized PNGs, 6 MB total): `docs/screenshots/10e9-final-{hq,map,contracts,dispatch,fleet,pilots,market,locations}-midgame-{desktop,phone}.png`, `10e9-final-{hq,contracts,map}-tutorial-{desktop,phone}.png` and `10e9-final-start-new-{desktop,phone}.png`. Phone shots are full-page captures, so the fixed bottom navigation appears partway down the image. **Manual check for the user:** the look on a real phone, and tap targets in the 7-tab bar on the narrowest phones. **Phase 10E complete — stopped for review.**
|
||||||
|
|
||||||
|
## Phase 10F — Boards That Fit Your Fleet (CR-006)
|
||||||
|
|
||||||
|
Spec: `memory-bank/change-requests/CR-006-boards-that-fit.md`. New decisions recorded as plan §0.5.52–0.5.53.
|
||||||
|
|
||||||
|
- [x] **Step 10F.1 — Client: weight warning.** `carryLimit(state, locationId)` (`LocationBoards.tsx`): the largest capacity among the player's ships at the location, else the whole fleet, else null. Contract cards show the mass in rose and a rose "Too heavy" pill when it is over that limit. Test: `weight-warning.test.tsx`.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue