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