11 KiB
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_restingispilot-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/simstays pure, all numbers live inpackages/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.mdfor schema or API changes andprogress.mdafter 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_restingreason, 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_restingerror 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_restingfor 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.