deadhead-space-freight-tycoon/memory-bank/change-requests/CR-004-pilot-duty-rest.md

11 KiB
Raw Permalink Blame History

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.