Step 10D.1: duty limit, Long Haul duty bonus and rest config

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
fireshaper 2026-10-03 21:43:09 -04:00
parent 0aecda66d7
commit 935d9b43f0
6 changed files with 31 additions and 1 deletions

View File

@ -761,3 +761,8 @@ Implementation decisions not spelled out in `memory-bank/implementation-plan.md`
- **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. - **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. - **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. - **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.
## Step 10D.1
- **Field names:** `dutyLimitGameHours`, `longHaulDutyBonusGameHours`, `restGameHours`. All in game hours, like other trip times.
- **The "rest ≥ board period" rule is a content test, not a schema refinement**, as the CR asks. The schema only requires the three numbers to be positive.

View File

@ -164,7 +164,7 @@ Spec: `memory-bank/change-requests/CR-003-map-art.md`. New decisions recorded as
Spec: `memory-bank/change-requests/CR-004-pilot-duty-rest.md`. New decisions recorded as plan §0.5.42–0.5.46. 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 - [x] **Step 10D.1 — Content config.** GameConfig gains `dutyLimitGameHours` (12), `longHaulDutyBonusGameHours` (2) and `restGameHours` (60 = one 60-minute board period at 60× compression). Tests: rest in real time ≥ board period; duty numbers positive.
- [ ] Step 10D.2 — Sim: duty and rest functions - [ ] Step 10D.2 — Sim: duty and rest functions
- [ ] Step 10D.3 — Sim: validation and resolution - [ ] Step 10D.3 — Sim: validation and resolution
- [ ] Step 10D.4 — Schema and database - [ ] Step 10D.4 — Schema and database

View File

@ -24,6 +24,12 @@ export const gameConfig: GameConfig = {
// Courier price + Earth bay lease. // Courier price + Earth bay lease.
dealLoanAmount: 5000, dealLoanAmount: 5000,
contractBoardPeriodRealMinutes: 60, contractBoardPeriodRealMinutes: 60,
// Duty and rest (CR-004): about three Moon round trips, then rest.
dutyLimitGameHours: 12,
longHaulDutyBonusGameHours: 2,
// One board period (60 real minutes at 60× compression), so a rested pilot
// always wakes to a fresh board (decision 0.5.46).
restGameHours: 60,
firstContactChance: 0.1, firstContactChance: 0.1,
firstContactPayoutMultiplier: 2, firstContactPayoutMultiplier: 2,
// One band per ship size, so every board has work for every ship (playtest fix): // One band per ship size, so every board has work for every ship (playtest fix):

View File

@ -134,6 +134,12 @@ export const gameConfigSchema = z
/** Loan taken on with the deal, in credits (decision 0.5.30). */ /** Loan taken on with the deal, in credits (decision 0.5.30). */
dealLoanAmount: positiveInt, dealLoanAmount: positiveInt,
contractBoardPeriodRealMinutes: z.number().positive(), contractBoardPeriodRealMinutes: z.number().positive(),
/** Game hours a pilot may fly before mandatory rest, at Long Haul 0 (decision 0.5.43). */
dutyLimitGameHours: z.number().positive(),
/** Extra duty game hours per Long Haul level (decision 0.5.43). */
longHaulDutyBonusGameHours: z.number().positive(),
/** How long a rest lasts, in game hours; at least one board period (decision 0.5.46). */
restGameHours: z.number().positive(),
firstContactChance: fraction, firstContactChance: fraction,
firstContactPayoutMultiplier: z.number().positive(), firstContactPayoutMultiplier: z.number().positive(),
/** /**

View File

@ -67,4 +67,14 @@ describe("game config", () => {
const total = gameConfig.contractMassBands.reduce((sum, b) => sum + b.count, 0); const total = gameConfig.contractMassBands.reduce((sum, b) => sum + b.count, 0);
expect(total).toBe(gameConfig.contractBoardSize); expect(total).toBe(gameConfig.contractBoardSize);
}); });
it("rests a pilot for at least one contract board period of real time (CR-004)", () => {
const restRealMinutes = (gameConfig.restGameHours * 60) / gameConfig.timeCompression;
expect(restRealMinutes).toBeGreaterThanOrEqual(gameConfig.contractBoardPeriodRealMinutes);
});
it("has positive duty numbers (CR-004)", () => {
expect(gameConfig.dutyLimitGameHours).toBeGreaterThan(0);
expect(gameConfig.longHaulDutyBonusGameHours).toBeGreaterThan(0);
});
}); });

View File

@ -63,6 +63,9 @@ const validGameConfig = {
contractBoardSize: 8, contractBoardSize: 8,
tutorialBoardSize: 50, tutorialBoardSize: 50,
contractBoardPeriodRealMinutes: 60, contractBoardPeriodRealMinutes: 60,
dutyLimitGameHours: 12,
longHaulDutyBonusGameHours: 2,
restGameHours: 60,
firstContactChance: 0.1, firstContactChance: 0.1,
firstContactPayoutMultiplier: 2, firstContactPayoutMultiplier: 2,
contractMassBands: [ contractMassBands: [