CR-004: record change request and decisions

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
fireshaper 2026-10-03 21:40:16 -04:00
parent 99aebe1dcb
commit 0aecda66d7
4 changed files with 165 additions and 0 deletions

View File

@ -754,3 +754,10 @@ Implementation decisions not spelled out in `memory-bank/implementation-plan.md`
- **Step 11.1's amendment names the generated folder explicitly** (`public/assets/map-gen/`), since the source art is dropped from the build and must not be precached. - **Step 11.1's amendment names the generated folder explicitly** (`public/assets/map-gen/`), since the source art is dropped from the build and must not be precached.
- **The "map with undiscovered locations" review shot is the tutorial map**, which shows Mars, Ceres and Jupiter locked; the mid-game shot has Mars discovered and Ceres and Jupiter still locked. - **The "map with undiscovered locations" review shot is the tutorial map**, which shows Mars, Ceres and Jupiter locked; the mid-game shot has Mars discovered and Ceres and Jupiter still locked.
- **Screenshot size:** phone shots are full-resolution (DPR 3) PNGs, about 2 MB each; `docs/screenshots/` is about 24 MB. They are kept as-is for the review; trimming or moving them out of the repository is a later cleanup. - **Screenshot size:** phone shots are full-resolution (DPR 3) PNGs, about 2 MB each; `docs/screenshots/` is about 24 MB. They are kept as-is for the review; trimming or moving them out of the repository is a later cleanup.
## CR-004 — Pilot Duty and Rest (Phase 10D)
- **Change request CR-004 is stored in `memory-bank/change-requests/`** and inserted as Phase 10D, after Phase 10C and before Phase 11.
- **Decision numbering:** the CR's 0.5.34–0.5.38 are recorded as **0.5.42 Duty clock, 0.5.43 Duty limit, 0.5.44 Finish the job, 0.5.45 Rest, 0.5.46 Rest covers a board refresh**, since 34–41 were already in use.
- **Error code:** the CR's `pilot_resting` is `pilot-resting`, matching the API's kebab-case codes.
- **Rest length vs. the CR's example:** the CR's rule sets the default rest to one board period (60 real minutes = 60 game hours at the current compression), but its worked example says a pilot "rests about 15 real minutes". The rule is explicit and test-enforced (decision 0.5.46), so it wins: default rest is 60 game hours (one real hour). Shortening rest would mean shortening the board period, which is a balance change for Phase 12.

View File

@ -0,0 +1,132 @@
# Change Request CR-004 — Pilot Duty and Rest
**Project:** Deadhead: Space Freight Tycoon
**Applies to:** a build with Phases 1–10, CR-001, CR-002, and CR-003 complete
**Do this:** as **Phase 10D**, after Phase 10C and before Phase 11
**Related docs:** `implementation-plan.md`, `change-request-faster-start.md`, `change-request-open-markets.md`, `change-request-map-art.md`, `DECISIONS.md`
> **Numbering note (added when implemented):** the plan's §0.5 already uses 34–41. This CR's decisions 0.5.34–0.5.38 are recorded as **0.5.42 Duty clock**, **0.5.43 Duty limit**, **0.5.44 Finish the job**, **0.5.45 Rest** and **0.5.46 Rest covers a board refresh**. The error code `pilot_resting` is `pilot-resting` (the API's kebab-case convention).
---
## 1. Why
Pilots can currently fly back-to-back forever, so there is no rhythm and no reason to run more than one pilot per ship. When a location's board has nothing useful, the only options are to wait with nothing to show for it or to deadhead.
This adds an ATS-style hours-of-service rule:
- Each pilot has a **duty clock** that fills up as they fly.
- When it's full, the pilot must **rest** for a fixed time on arrival. The ship stays put unless another pilot takes it.
- The player can also **rest a pilot early**. Rest always lasts at least one contract board period, so **a rested pilot always wakes up to a fresh board.**
- The **Long Haul** skill extends a pilot's duty limit, so leveling it changes the math, not just which jobs unlock.
## 2. How it works
| Rule | Detail |
|---|---|
| Duty clock | Each pilot tracks duty hours (game hours flown since their last rest). |
| What counts | Every contract and deadhead trip adds its full travel time. Tutorial runs do not count. |
| Duty limit | Base limit from config, plus a bonus per Long Haul level. |
| Starting a trip | Allowed whenever the pilot is not resting, even if the trip will take them over the limit. Pilots always finish the job they're on. |
| Mandatory rest | If duty hours are at or over the limit when a trip resolves, rest starts automatically at the arrival time. |
| Voluntary rest | The player can rest any idle pilot who isn't in flight, at any duty level. |
| Rest | Lasts the configured rest duration. When it starts, duty hours reset to zero; the pilot can't fly until it ends. Rest happens wherever the pilot is; no bay is needed. |
| Crew swap | A resting pilot can be unassigned (at a bay, per decision 0.5.2), and a rested pilot can be assigned to the same ship. Rest blocks the pilot, not the ship. |
| Fresh board | The rest duration in real time is at least the contract board period, so any rest crosses at least one board refresh. |
Example with defaults (12-hour limit, 1 real minute = 1 game hour): a pilot flies three ~4-hour Moon runs, rests about 15 real minutes, and wakes to a new board. A Mars run (60+ game hours) always ends in a rest, which is negligible next to the trip itself.
## 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`.
- Rest is derived from stored timestamps, like dispatches. **No background job is needed to end a rest**: a pilot is resting if the current time is before their rest-ends-at time.
- 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 10D.
## 4. Decisions
Record these in `DECISIONS.md`.
- **0.5.34 Duty clock.** Pilots accrue duty hours equal to each trip's travel time in game hours, for contracts and deadheads. Tutorial runs accrue nothing.
- **0.5.35 Duty limit.** Base duty limit plus a per-level Long Haul bonus, both in config.
- **0.5.36 Finish the job.** Dispatch is blocked only while a pilot is resting. A trip that will exceed the limit is allowed; mandatory rest starts on arrival.
- **0.5.37 Rest.** Starting rest sets duty hours to zero and sets rest-ends-at to start time plus the rest duration. Mandatory rest starts at the trip's arrival time, so it is correct even if resolution happens later (offline).
- **0.5.38 Rest covers a board refresh.** The rest duration converted to real time must be at least the contract board period. This is enforced by a content test.
## 5. Steps — Phase 10D
### Step 10D.1 — Content config
- Add GameConfig fields: base duty limit (game hours, default 12), Long Haul duty bonus per level (game hours, default 2), rest duration (game hours; default equal to the board period converted to game hours).
- **Test:** Config validates. A test converts the rest duration to real time with the current compression and asserts it is at least the board period. Both duty numbers are positive.
### Step 10D.2 — Sim: duty and rest functions
- Add pure functions:
- Duty limit for a pilot, from their Long Haul level.
- Whether a pilot is resting at a given real time.
- Duty hours after a trip (current duty plus trip travel time, unless it's a tutorial run).
- Whether a pilot needs mandatory rest after a trip.
- Rest window from a start time (rest-ends-at).
- **Test:** Duty limit rises by the bonus for each Long Haul level. A pilot is resting one second before rest-ends-at and not resting at it. A tutorial trip leaves duty unchanged. A pilot at 10 of 12 hours who flies a 4-hour trip needs rest; one at 4 of 12 who flies a 4-hour trip does not. For many random start times, a rest window always contains at least one contract board period boundary.
### Step 10D.3 — Sim: validation and resolution
- Dispatch validation gains a `pilot_resting` reason, checked before the other pilot checks.
- Dispatch resolution returns the pilot's new duty hours and, if mandatory rest applies, a rest-ends-at time computed from the arrival time.
- **Test:** A resting pilot fails validation with `pilot_resting`. A pilot whose duty is over the limit but not resting can still be dispatched. Resolving a trip that pushes duty over the limit returns duty zero and rest-ends-at equal to arrival time plus rest duration, even when resolved hours after arrival. Deadheads accrue duty; tutorial runs don't.
### Step 10D.4 — Schema and database
- Add to pilots: duty hours and rest-ends-at (nullable).
- Add the `pilot_resting` error code and a "rest pilot" endpoint to the route catalog.
- Player state shows, per pilot: duty hours, duty limit, resting (yes/no), and rest-ends-at.
- Dispatch summaries include a "rest started" note when a trip ended in mandatory rest.
- 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 10D.5 — Server: apply duty and rest
- Resolution (lazy and job-based) writes the pilot's new duty hours and rest-ends-at.
- The rest endpoint starts rest now for an idle pilot who is not in flight and not already resting. It works at any duty level.
- Dispatch returns `pilot_resting` for a resting pilot.
- Assigning and unassigning ignore rest (crew swaps are allowed); only flying is blocked.
- **Test:** A pilot who chains Moon runs until over the limit is resting after the last one resolves, and dispatch with them fails with `pilot_resting`. After the rest window passes, the same dispatch succeeds. Resting a pilot by hand sets duty zero and the correct rest-ends-at; resting an in-flight or already-resting pilot fails. A resting pilot can be unassigned at a bay, and a rested pilot assigned to that ship can dispatch immediately.
### Step 10D.6 — Client: pilot duty on the Pilots screen
- Each pilot shows a duty bar (hours used of limit). Resting pilots show "Resting" with a countdown instead.
- Idle, non-resting pilots have a "Rest now" action with a confirmation that says rest lasts at least one board refresh.
- **Test:** Component test: the duty bar matches state; a resting pilot shows the countdown and no Rest now action; Rest now calls the endpoint.
### Step 10D.7 — Client: duty in the dispatch flow
- In the dispatch sheet, pairs with a resting pilot are disabled with "Resting — mm:ss".
- For other pairs, show the pilot's duty after the trip. If it reaches the limit, show "Will need rest on arrival".
- **Test:** Component test: a resting pilot's pair is disabled with the countdown; a trip that takes a pilot over the limit shows the warning; one that doesn't, doesn't. Numbers match the sim.
### Step 10D.8 — Client: board refresh and the "nothing here" case
- Every contract board shows "New contracts in mm:ss" until the next board period.
- If a location has an idle ship but no contract that ship and its pilot are eligible for, show a short prompt with both options: "Rest [pilot] — wakes to a new board" and "Deadhead (fly empty)".
- **Test:** Component test: the countdown matches the next period boundary for the mocked time; with no eligible contracts, the prompt shows both actions and each calls the right endpoint or opens the deadhead picker.
### Step 10D.9 — Client: rest in summaries and on HQ
- The "while you were away" modal shows "Pilot X is resting until …" for trips that ended in mandatory rest.
- HQ shows the count of resting pilots and the next pilot to wake up, with a countdown.
- **Test:** Component test with seeded state: the modal line appears for a summary with rest started; HQ shows the correct count and the soonest wake time.
### Step 10D.10 — Plan amendments and regression
- Apply the amendments in §6 to `implementation-plan.md`.
- Run the full suite.
- **Test:** CI is green. Manual check: on a fresh account after the CR-001 deal, chain Moon runs with the avatar until mandatory rest starts; confirm the ship's location board shows a new set of contracts when the avatar wakes.
## 6. Amendments to `implementation-plan.md`
| Step | Change |
|---|---|
| 0.5 | Add decisions 0.5.34–0.5.38. |
| 2.1 | GameConfig gains duty limit, Long Haul duty bonus, and rest duration (10D.1). |
| 3.12 | Validation gains `pilot_resting` (10D.3). |
| 3.17 | Resolution returns duty hours and rest-ends-at (10D.3). |
| 5.3 | Pilots table gains duty hours and rest-ends-at (10D.4). |
| 9.5, 9.7 | Duty and rest in the dispatch sheet and Pilots screen (10D.6, 10D.7). |
| 12.1 | The simulated player follows rest rules: it rests a pilot when no eligible contract exists at its location and deadheading isn't better. Re-check all CR-001 timing targets with rest in place; adjust content values, not thresholds. Add a check that no pilot is ever dispatched while resting. |
| 12.6 | After leveling Long Haul, confirm the pilot's duty limit increased. |
## 7. Out of scope
Fatigue penalties, accidents, or slower flying when tired; rest facilities or bay amenities that shorten rest; pilot morale; and anything else in `implementation-plan.md` §0.3.

View File

@ -97,6 +97,11 @@ These answer the pre-build review. Log each one in `DECISIONS.md` as it is imple
39. **Child bodies** (CR-003). The Moon (and any future location whose position is "parent plus small offset") is drawn at a fixed screen-space offset from its parent, not at its scaled real position, so it never overlaps the parent at any zoom. 39. **Child bodies** (CR-003). The Moon (and any future location whose position is "parent plus small offset") is drawn at a fixed screen-space offset from its parent, not at its scaled real position, so it never overlaps the parent at any zoom.
40. **Background** (CR-003). The background is not tiled. It is scaled to cover the viewport with a margin and moves with gentle parallax, clamped so its edges never show. 40. **Background** (CR-003). The background is not tiled. It is scaled to cover the viewport with a margin and moves with gentle parallax, clamped so its edges never show.
41. **Fallback** (CR-003). If any asset is missing or fails to load, the map draws the old placeholder for that item and logs one warning. The map never fails to render because of art. 41. **Fallback** (CR-003). If any asset is missing or fails to load, the map draws the old placeholder for that item and logs one warning. The map never fails to render because of art.
42. **Duty clock** (CR-004). Pilots accrue duty hours equal to each trip's travel time in game hours, for contracts and deadheads. Tutorial runs accrue nothing.
43. **Duty limit** (CR-004). Base duty limit plus a per-level Long Haul bonus, both in config.
44. **Finish the job** (CR-004). Dispatch is blocked only while a pilot is resting. A trip that will exceed the limit is allowed; mandatory rest starts on arrival.
45. **Rest** (CR-004). Starting rest sets duty hours to zero and sets rest-ends-at to start time plus the rest duration. Mandatory rest starts at the trip's arrival time, so it is correct even if resolution happens later (offline).
46. **Rest covers a board refresh** (CR-004). The rest duration converted to real time must be at least the contract board period. This is enforced by a content test.
--- ---
@ -503,6 +508,12 @@ Steps 10C.1–10C.10 are specified in `memory-bank/change-requests/CR-003-map-ar
--- ---
## Phase 10D — Pilot Duty and Rest (CR-004)
Steps 10D.1–10D.10 are specified in `memory-bank/change-requests/CR-004-pilot-duty-rest.md` §5, with its §6 amendments to earlier steps. Done after Phase 10C 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

View File

@ -159,3 +159,18 @@ Spec: `memory-bank/change-requests/CR-003-map-art.md`. New decisions recorded as
- [x] **Step 10C.8 — Ships in flight.** Each flight draws its model's sprite rotated by `shipRotation` (art points up: right = 90°, down = 180°), positioned by sim progress every frame. Routes are faint end to end and brighter ahead of the ship. `separateShips` spreads ships closer than 44 px apart, perpendicular to their route and 44 px apart; hit-testing uses the same positions. The selected ship or location gets an amber ring (`MapCanvas` `selected` prop). Test: `map-ships.test.ts`. **Manual check:** `docs/screenshots/10c8-ships-midgame-{desktop,desktop-zoomed,phone}.png`. - [x] **Step 10C.8 — Ships in flight.** Each flight draws its model's sprite rotated by `shipRotation` (art points up: right = 90°, down = 180°), positioned by sim progress every frame. Routes are faint end to end and brighter ahead of the ship. `separateShips` spreads ships closer than 44 px apart, perpendicular to their route and 44 px apart; hit-testing uses the same positions. The selected ship or location gets an amber ring (`MapCanvas` `selected` prop). Test: `map-ships.test.ts`. **Manual check:** `docs/screenshots/10c8-ships-midgame-{desktop,desktop-zoomed,phone}.png`.
- [x] **Step 10C.9 — Performance.** `map/ticker.ts` `runWhileVisible`: the PixiJS ticker stops when the page is hidden, restarts when visible, and stops on unmount. Canvas resolution capped at 2×. Labels restyle only on change. Big ship crowds (> 3) pack on a hexagonal grid (`hexSpiral`) instead of a long line. Tests: `map-ticker.test.ts`, crowd test in `map-ships.test.ts`. **Measured** (headless Chrome, software WebGL, 10 ships in flight, panning): 58.5 fps at DPR 1 even with CPU throttled 4×; 11 fps at DPR 3 before the 2× cap, 22 fps after — fill-rate bound in software rendering, not JavaScript. **Manual check for the user:** pan and zoom on a real mid-range phone (GPU rendering). Screenshots: `docs/screenshots/10c9-busy-busy-{desktop,desktop-zoomed,phone}.png`. - [x] **Step 10C.9 — Performance.** `map/ticker.ts` `runWhileVisible`: the PixiJS ticker stops when the page is hidden, restarts when visible, and stops on unmount. Canvas resolution capped at 2×. Labels restyle only on change. Big ship crowds (> 3) pack on a hexagonal grid (`hexSpiral`) instead of a long line. Tests: `map-ticker.test.ts`, crowd test in `map-ships.test.ts`. **Measured** (headless Chrome, software WebGL, 10 ships in flight, panning): 58.5 fps at DPR 1 even with CPU throttled 4×; 11 fps at DPR 3 before the 2× cap, 22 fps after — fill-rate bound in software rendering, not JavaScript. **Manual check for the user:** pan and zoom on a real mid-range phone (GPU rendering). Screenshots: `docs/screenshots/10c9-busy-busy-{desktop,desktop-zoomed,phone}.png`.
- [x] **Step 10C.10 — Plan amendments and regression.** CR-003 §6 amendments added to plan steps 10.1, 10.2, 10.3 and 11.1, and to CR-001 Step 10A.16 (§0.5.37–41 were added with the CR). Full suite (629 tests), coverage, lint and web build green. **Review screenshots:** `docs/screenshots/10c10-final-{tutorial,midgame}-{desktop,phone}.png` (the tutorial map also shows the undiscovered locations), plus the per-step shots from 10C.5–10C.9. **Phase 10C complete — stopped for review.** - [x] **Step 10C.10 — Plan amendments and regression.** CR-003 §6 amendments added to plan steps 10.1, 10.2, 10.3 and 11.1, and to CR-001 Step 10A.16 (§0.5.37–41 were added with the CR). Full suite (629 tests), coverage, lint and web build green. **Review screenshots:** `docs/screenshots/10c10-final-{tutorial,midgame}-{desktop,phone}.png` (the tutorial map also shows the undiscovered locations), plus the per-step shots from 10C.5–10C.9. **Phase 10C complete — stopped for review.**
## Phase 10D — Pilot Duty and Rest (CR-004)
Spec: `memory-bank/change-requests/CR-004-pilot-duty-rest.md`. New decisions recorded as plan §0.5.42–0.5.46.
- [ ] Step 10D.1 — Content config
- [ ] Step 10D.2 — Sim: duty and rest functions
- [ ] Step 10D.3 — Sim: validation and resolution
- [ ] Step 10D.4 — Schema and database
- [ ] Step 10D.5 — Server: apply duty and rest
- [ ] Step 10D.6 — Client: pilot duty on the Pilots screen
- [ ] Step 10D.7 — Client: duty in the dispatch flow
- [ ] Step 10D.8 — Client: board refresh and the "nothing here" case
- [ ] Step 10D.9 — Client: rest in summaries and on HQ
- [ ] Step 10D.10 — Plan amendments and regression