Step 24.2: engine: manual decision flag
CI / check (push) Has been cancelled
Details
CI / check (push) Has been cancelled
Details
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
bf55eb8d9f
commit
587f504ae1
|
|
@ -1812,3 +1812,11 @@ Implementation decisions not spelled out in `memory-bank/implementation-plan.md`
|
||||||
- **"Set to the migration time" without a clock in the migration.** Migrations are pure functions of the save, so the 8 → 9 step leaves `lastManualDecisionAt` null and the engine's catch-up sets a null one to the time it runs, before anything else. That is the first time the engine opens the save (load or import), so the effect is the CR's: nobody's fleet is asleep the moment they update.
|
- **"Set to the migration time" without a clock in the migration.** Migrations are pure functions of the save, so the 8 → 9 step leaves `lastManualDecisionAt` null and the engine's catch-up sets a null one to the time it runs, before anything else. That is the first time the engine opens the save (load or import), so the effect is the CR's: nobody's fleet is asleep the moment they update.
|
||||||
- **The sleep settings are their own config block** (`fleetSleep`), not part of `independentPilots`, whose values are all checked as positive settings of a different kind.
|
- **The sleep settings are their own config block** (`fleetSleep`), not part of `independentPilots`, whose values are all checked as positive settings of a different kind.
|
||||||
- **The threshold lives on the company**, beside the Dispatch Office and spending floor: it travels with the save, unlike device preferences.
|
- **The threshold lives on the company**, beside the Dispatch Office and spending floor: it travels with the save, unlike device preferences.
|
||||||
|
|
||||||
|
## Step 24.2
|
||||||
|
|
||||||
|
- **The catalog is `routes`** (`packages/schema/src/routes.ts`): `manualDecision` is a required field of `RouteDef`, so a new command fails the build until it says.
|
||||||
|
- **Counted beyond the CR's list:** unassigning and resting a pilot, refuel policy and emergency fuel (refuelling decisions), the spending floor (an order to the pilots) and renaming the company. They're all game commands the player makes; the CR counts "any game command".
|
||||||
|
- **Not counted:** `markWelcomeSeen` (like dismissing), new company and the dev tools. A new company starts with its creation as the last decision anyway.
|
||||||
|
- **Display preferences aren't commands** (they live on the device, CR-011), so they can't stamp it; the test checks the engine-side things the player can do without deciding: reading, boards, dismissing, the welcome and export.
|
||||||
|
- **A refused command isn't a decision:** the stamp happens after the handler succeeds, in the same write.
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ Every refusal carries `{ code, message }`: the engine's `CommandError`, which th
|
||||||
|
|
||||||
## API routes (`packages/schema/src/routes.ts`)
|
## API routes (`packages/schema/src/routes.ts`)
|
||||||
|
|
||||||
The `routes` catalog is the list of engine commands (CR-014). Each entry has `request` (path parameters merged with the body), `response` and `auth` (whether it needs a company). `method` and `path` are kept from the server's HTTP API: ported tests route by them.
|
The `routes` catalog is the list of engine commands (CR-014). Each entry has `request` (path parameters merged with the body), `response`, `auth` (whether it needs a company) and `manualDecision` (CR-024: whether it is one of the player's decisions; required, so every new command declares it). After a successful `manualDecision` command the engine sets `company.lastManualDecisionAt` to now; the fleet sleeps a threshold after it. `method` and `path` are kept from the server's HTTP API: ported tests route by them.
|
||||||
|
|
||||||
| Name | Method & path | Request | Response | Auth |
|
| Name | Method & path | Request | Response | Auth |
|
||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
|
|
|
||||||
|
|
@ -522,7 +522,7 @@ Spec: `memory-bank/change-requests/CR-023-shop-time-rest.md` (received as "CR-02
|
||||||
Spec: `memory-bank/change-requests/CR-024-fleet-sleep.md` (received as "CR-022 … Phase 22"). New decisions recorded as plan §0.5.136–0.5.139.
|
Spec: `memory-bank/change-requests/CR-024-fleet-sleep.md` (received as "CR-022 … Phase 22"). New decisions recorded as plan §0.5.136–0.5.139.
|
||||||
|
|
||||||
- [x] **Step 24.1 — Content and save.** Config `fleetSleep` (default 24 h; options 12, 24, 48 and 72 h, rising, the default among them). Save version 9: `company.lastManualDecisionAt` and `company.sleepAfterHours`; a new company starts with its creation time and the default. The 8 → 9 migration sets the default and leaves the last decision null; the engine's catch-up sets a null one to the time it first runs on the save, which is the migration time in effect. Tests: `packages/content/test/config.test.ts`, `packages/schema/test/save.test.ts` (8 → 9), `packages/engine/test/fleet-sleep.test.ts` (a new company; an imported version-8 save is stamped with the import time).
|
- [x] **Step 24.1 — Content and save.** Config `fleetSleep` (default 24 h; options 12, 24, 48 and 72 h, rising, the default among them). Save version 9: `company.lastManualDecisionAt` and `company.sleepAfterHours`; a new company starts with its creation time and the default. The 8 → 9 migration sets the default and leaves the last decision null; the engine's catch-up sets a null one to the time it first runs on the save, which is the migration time in effect. Tests: `packages/content/test/config.test.ts`, `packages/schema/test/save.test.ts` (8 → 9), `packages/engine/test/fleet-sleep.test.ts` (a new company; an imported version-8 save is stamped with the import time).
|
||||||
- [ ] Step 24.2 — Engine: manual decision flag
|
- [x] **Step 24.2 — Engine: manual decision flag.** `RouteDef.manualDecision` is required on every command in the catalog. Manual: dispatch and deadhead (`createDispatch`), buying ships, upgrades, refuelling, refuel policy, emergency fuel, leasing, hiring, assigning and unassigning, resting a pilot, skill points, the Dispatch Office, pilot mode and orders, the spending floor, the deal, renaming the company, the loan and goals. Not: reads, `acknowledgeSummaries`, `markWelcomeSeen`, export and import, new company, dev tools. The engine stamps `lastManualDecisionAt` after a flagged command succeeds; Independent pilots' trips go through the commands' functions, not the catalog, so they never do. Tests: `packages/schema/test/routes.test.ts` (every command declares it; the manual list); `packages/engine/test/fleet-sleep.test.ts` (a dispatch stamps it; viewing, a board, dismissing a summary, the welcome and an export don't; a refused command doesn't; Independent pilots' trips don't).
|
||||||
- [ ] Step 24.3 — Engine: sleep in live play and catch-up
|
- [ ] Step 24.3 — Engine: sleep in live play and catch-up
|
||||||
- [ ] Step 24.4 — Engine: waking and "Back to work"
|
- [ ] Step 24.4 — Engine: waking and "Back to work"
|
||||||
- [ ] Step 24.5 — Client: return banner, status, and setting
|
- [ ] Step 24.5 — Client: return banner, status, and setting
|
||||||
|
|
|
||||||
|
|
@ -255,6 +255,8 @@ export class GameEngine {
|
||||||
viewedContractId: this.#viewedContractId,
|
viewedContractId: this.#viewedContractId,
|
||||||
});
|
});
|
||||||
const result = handler({ save, now, random }, input);
|
const result = handler({ save, now, random }, input);
|
||||||
|
// The catalog says which commands are the player's decisions (decision 0.5.136).
|
||||||
|
if (routes[name].manualDecision) save.company.lastManualDecisionAt = now;
|
||||||
pruneSave(save, now);
|
pruneSave(save, now);
|
||||||
return { save, result, unchanged: JSON.stringify(save) === before };
|
return { save, result, unchanged: JSON.stringify(save) === before };
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,16 @@
|
||||||
import { content } from "@deadhead/content";
|
import { content } from "@deadhead/content";
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import { encodeSave } from "../src/index";
|
import { encodeSave } from "../src/index";
|
||||||
import { newTestApp, readSave, signUp, TEST_NOW } from "./support";
|
import {
|
||||||
|
addPilot,
|
||||||
|
addShip,
|
||||||
|
newTestApp,
|
||||||
|
readSave,
|
||||||
|
setCredits,
|
||||||
|
signUp,
|
||||||
|
signUpOwner,
|
||||||
|
TEST_NOW,
|
||||||
|
} from "./support";
|
||||||
|
|
||||||
const HOUR = 60 * 60_000;
|
const HOUR = 60 * 60_000;
|
||||||
|
|
||||||
|
|
@ -30,3 +39,54 @@ describe("fleet sleep in the save (Step 24.1, decisions 0.5.136, 0.5.137)", () =
|
||||||
expect(imported.sleepAfterHours).toBe(24);
|
expect(imported.sleepAfterHours).toBe(24);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("manual decisions (Step 24.2, decision 0.5.136)", () => {
|
||||||
|
it("stamps the time on a dispatch, not on viewing, dismissing or exporting", async () => {
|
||||||
|
const test = newTestApp();
|
||||||
|
const owner = await signUpOwner(test.app);
|
||||||
|
const last = async () => (await readSave(test.app)).company.lastManualDecisionAt;
|
||||||
|
const { dispatch } = await test.app.call("createDispatch", {
|
||||||
|
kind: "reposition",
|
||||||
|
shipId: owner.shipId,
|
||||||
|
pilotId: (await readSave(test.app)).pilots.find((p) => p.isAvatar)!.id,
|
||||||
|
destinationId: "moon",
|
||||||
|
});
|
||||||
|
const dispatchedAt = test.clock.now;
|
||||||
|
expect(await last()).toBe(dispatchedAt);
|
||||||
|
|
||||||
|
// Later: the trip lands; viewing, dismissing its summary and exporting change nothing.
|
||||||
|
test.clock.now = dispatch.arrivalAt + HOUR;
|
||||||
|
await test.app.call("getPlayerState", {});
|
||||||
|
await test.app.call("getContractBoard", { locationId: "earth" });
|
||||||
|
await test.app.call("acknowledgeSummaries", { dispatchIds: [dispatch.id] });
|
||||||
|
await test.app.call("markWelcomeSeen", {});
|
||||||
|
await test.app.call("exportSave", {});
|
||||||
|
expect(await last()).toBe(dispatchedAt);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("doesn't count a refused command", async () => {
|
||||||
|
const test = newTestApp();
|
||||||
|
await signUpOwner(test.app);
|
||||||
|
const start = (await readSave(test.app)).company.lastManualDecisionAt;
|
||||||
|
test.clock.now += HOUR;
|
||||||
|
await expect(test.app.call("buyDispatchOffice", {})).rejects.toThrow();
|
||||||
|
expect((await readSave(test.app)).company.lastManualDecisionAt).toBe(start);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("never counts what Independent pilots do", async () => {
|
||||||
|
const test = newTestApp();
|
||||||
|
await signUpOwner(test.app);
|
||||||
|
await setCredits(test.app, content.config.independentPilots.dispatchOfficePrice + 50_000);
|
||||||
|
await test.app.call("buyDispatchOffice", {});
|
||||||
|
await addShip(test.app, { id: "s1", modelId: "courier", name: "Wren" });
|
||||||
|
await addPilot(test.app, { id: "p1", name: "Ada", assignedShipId: "s1" });
|
||||||
|
await test.app.call("setPilotMode", { pilotId: "p1", mode: "independent" });
|
||||||
|
const decided = (await readSave(test.app)).company.lastManualDecisionAt;
|
||||||
|
|
||||||
|
test.clock.now += 4 * HOUR;
|
||||||
|
await test.app.call("getPlayerState", {});
|
||||||
|
const save = await readSave(test.app);
|
||||||
|
expect(save.dispatches.some((d) => d.independent)).toBe(true);
|
||||||
|
expect(save.company.lastManualDecisionAt).toBe(decided);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,12 @@ export interface RouteDef {
|
||||||
response: z.ZodType;
|
response: z.ZodType;
|
||||||
/** Whether it needs a company (was: a session, before CR-014). */
|
/** Whether it needs a company (was: a session, before CR-014). */
|
||||||
auth: boolean;
|
auth: boolean;
|
||||||
|
/**
|
||||||
|
* Whether running it is one of the player's manual decisions (decision
|
||||||
|
* 0.5.136): the fleet's sleep time counts from the last one. Required, so
|
||||||
|
* every new command has to say.
|
||||||
|
*/
|
||||||
|
manualDecision: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Every endpoint of the base game: the contract between server and client. */
|
/** Every endpoint of the base game: the contract between server and client. */
|
||||||
|
|
@ -60,6 +66,7 @@ export const routes = {
|
||||||
request: emptyRequestSchema,
|
request: emptyRequestSchema,
|
||||||
response: serverTimeSchema,
|
response: serverTimeSchema,
|
||||||
auth: false,
|
auth: false,
|
||||||
|
manualDecision: false,
|
||||||
},
|
},
|
||||||
createGuest: {
|
createGuest: {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
@ -67,6 +74,7 @@ export const routes = {
|
||||||
request: emptyRequestSchema,
|
request: emptyRequestSchema,
|
||||||
response: createGuestResponseSchema,
|
response: createGuestResponseSchema,
|
||||||
auth: false,
|
auth: false,
|
||||||
|
manualDecision: false,
|
||||||
},
|
},
|
||||||
getPlayerState: {
|
getPlayerState: {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
|
@ -74,6 +82,7 @@ export const routes = {
|
||||||
request: emptyRequestSchema,
|
request: emptyRequestSchema,
|
||||||
response: playerStateSchema,
|
response: playerStateSchema,
|
||||||
auth: true,
|
auth: true,
|
||||||
|
manualDecision: false,
|
||||||
},
|
},
|
||||||
getContractBoard: {
|
getContractBoard: {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
|
@ -81,6 +90,7 @@ export const routes = {
|
||||||
request: contractBoardRequestSchema,
|
request: contractBoardRequestSchema,
|
||||||
response: contractBoardResponseSchema,
|
response: contractBoardResponseSchema,
|
||||||
auth: true,
|
auth: true,
|
||||||
|
manualDecision: false,
|
||||||
},
|
},
|
||||||
getTutorialBoard: {
|
getTutorialBoard: {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
|
@ -88,6 +98,7 @@ export const routes = {
|
||||||
request: emptyRequestSchema,
|
request: emptyRequestSchema,
|
||||||
response: contractBoardResponseSchema,
|
response: contractBoardResponseSchema,
|
||||||
auth: true,
|
auth: true,
|
||||||
|
manualDecision: false,
|
||||||
},
|
},
|
||||||
createDispatch: {
|
createDispatch: {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
@ -95,6 +106,7 @@ export const routes = {
|
||||||
request: createDispatchRequestSchema,
|
request: createDispatchRequestSchema,
|
||||||
response: dispatchResponseSchema,
|
response: dispatchResponseSchema,
|
||||||
auth: true,
|
auth: true,
|
||||||
|
manualDecision: true,
|
||||||
},
|
},
|
||||||
buyShip: {
|
buyShip: {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
@ -102,6 +114,7 @@ export const routes = {
|
||||||
request: buyShipRequestSchema,
|
request: buyShipRequestSchema,
|
||||||
response: shipResponseSchema,
|
response: shipResponseSchema,
|
||||||
auth: true,
|
auth: true,
|
||||||
|
manualDecision: true,
|
||||||
},
|
},
|
||||||
startUpgrade: {
|
startUpgrade: {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
@ -109,6 +122,7 @@ export const routes = {
|
||||||
request: startUpgradeRequestSchema,
|
request: startUpgradeRequestSchema,
|
||||||
response: shipResponseSchema,
|
response: shipResponseSchema,
|
||||||
auth: true,
|
auth: true,
|
||||||
|
manualDecision: true,
|
||||||
},
|
},
|
||||||
/** CR-015: fill the tank (or buy `amountFu`) at the ship's location. */
|
/** CR-015: fill the tank (or buy `amountFu`) at the ship's location. */
|
||||||
refuelShip: {
|
refuelShip: {
|
||||||
|
|
@ -117,6 +131,7 @@ export const routes = {
|
||||||
request: refuelRequestSchema,
|
request: refuelRequestSchema,
|
||||||
response: shipResponseSchema,
|
response: shipResponseSchema,
|
||||||
auth: true,
|
auth: true,
|
||||||
|
manualDecision: true,
|
||||||
},
|
},
|
||||||
setRefuelPolicy: {
|
setRefuelPolicy: {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
@ -124,6 +139,7 @@ export const routes = {
|
||||||
request: refuelPolicyRequestSchema,
|
request: refuelPolicyRequestSchema,
|
||||||
response: shipResponseSchema,
|
response: shipResponseSchema,
|
||||||
auth: true,
|
auth: true,
|
||||||
|
manualDecision: true,
|
||||||
},
|
},
|
||||||
/** CR-015: fuel for the cheapest way out, added to the loan (decision 0.5.99). */
|
/** CR-015: fuel for the cheapest way out, added to the loan (decision 0.5.99). */
|
||||||
orderEmergencyFuel: {
|
orderEmergencyFuel: {
|
||||||
|
|
@ -132,6 +148,7 @@ export const routes = {
|
||||||
request: emergencyFuelRequestSchema,
|
request: emergencyFuelRequestSchema,
|
||||||
response: shipResponseSchema,
|
response: shipResponseSchema,
|
||||||
auth: true,
|
auth: true,
|
||||||
|
manualDecision: true,
|
||||||
},
|
},
|
||||||
leaseBay: {
|
leaseBay: {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
@ -139,6 +156,7 @@ export const routes = {
|
||||||
request: leaseBayRequestSchema,
|
request: leaseBayRequestSchema,
|
||||||
response: bayResponseSchema,
|
response: bayResponseSchema,
|
||||||
auth: true,
|
auth: true,
|
||||||
|
manualDecision: true,
|
||||||
},
|
},
|
||||||
hirePilot: {
|
hirePilot: {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
@ -146,6 +164,7 @@ export const routes = {
|
||||||
request: hirePilotRequestSchema,
|
request: hirePilotRequestSchema,
|
||||||
response: pilotResponseSchema,
|
response: pilotResponseSchema,
|
||||||
auth: true,
|
auth: true,
|
||||||
|
manualDecision: true,
|
||||||
},
|
},
|
||||||
assignPilot: {
|
assignPilot: {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
@ -153,6 +172,7 @@ export const routes = {
|
||||||
request: assignPilotRequestSchema,
|
request: assignPilotRequestSchema,
|
||||||
response: pilotResponseSchema,
|
response: pilotResponseSchema,
|
||||||
auth: true,
|
auth: true,
|
||||||
|
manualDecision: true,
|
||||||
},
|
},
|
||||||
unassignPilot: {
|
unassignPilot: {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
@ -160,6 +180,7 @@ export const routes = {
|
||||||
request: unassignPilotRequestSchema,
|
request: unassignPilotRequestSchema,
|
||||||
response: pilotResponseSchema,
|
response: pilotResponseSchema,
|
||||||
auth: true,
|
auth: true,
|
||||||
|
manualDecision: true,
|
||||||
},
|
},
|
||||||
restPilot: {
|
restPilot: {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
@ -167,6 +188,7 @@ export const routes = {
|
||||||
request: restPilotRequestSchema,
|
request: restPilotRequestSchema,
|
||||||
response: pilotResponseSchema,
|
response: pilotResponseSchema,
|
||||||
auth: true,
|
auth: true,
|
||||||
|
manualDecision: true,
|
||||||
},
|
},
|
||||||
spendSkillPoint: {
|
spendSkillPoint: {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
@ -174,6 +196,7 @@ export const routes = {
|
||||||
request: spendSkillPointRequestSchema,
|
request: spendSkillPointRequestSchema,
|
||||||
response: pilotResponseSchema,
|
response: pilotResponseSchema,
|
||||||
auth: true,
|
auth: true,
|
||||||
|
manualDecision: true,
|
||||||
},
|
},
|
||||||
/** CR-021: Independent pilots (decisions 0.5.123–0.5.127). */
|
/** CR-021: Independent pilots (decisions 0.5.123–0.5.127). */
|
||||||
buyDispatchOffice: {
|
buyDispatchOffice: {
|
||||||
|
|
@ -182,6 +205,7 @@ export const routes = {
|
||||||
request: emptyRequestSchema,
|
request: emptyRequestSchema,
|
||||||
response: dispatchOfficeResponseSchema,
|
response: dispatchOfficeResponseSchema,
|
||||||
auth: true,
|
auth: true,
|
||||||
|
manualDecision: true,
|
||||||
},
|
},
|
||||||
setPilotMode: {
|
setPilotMode: {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
@ -189,6 +213,7 @@ export const routes = {
|
||||||
request: pilotModeRequestSchema,
|
request: pilotModeRequestSchema,
|
||||||
response: pilotResponseSchema,
|
response: pilotResponseSchema,
|
||||||
auth: true,
|
auth: true,
|
||||||
|
manualDecision: true,
|
||||||
},
|
},
|
||||||
setPilotOrders: {
|
setPilotOrders: {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
@ -196,6 +221,7 @@ export const routes = {
|
||||||
request: pilotOrdersRequestSchema,
|
request: pilotOrdersRequestSchema,
|
||||||
response: pilotResponseSchema,
|
response: pilotResponseSchema,
|
||||||
auth: true,
|
auth: true,
|
||||||
|
manualDecision: true,
|
||||||
},
|
},
|
||||||
setSpendingFloor: {
|
setSpendingFloor: {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
@ -203,6 +229,7 @@ export const routes = {
|
||||||
request: spendingFloorRequestSchema,
|
request: spendingFloorRequestSchema,
|
||||||
response: dispatchOfficeResponseSchema,
|
response: dispatchOfficeResponseSchema,
|
||||||
auth: true,
|
auth: true,
|
||||||
|
manualDecision: true,
|
||||||
},
|
},
|
||||||
acknowledgeSummaries: {
|
acknowledgeSummaries: {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
@ -210,6 +237,7 @@ export const routes = {
|
||||||
request: acknowledgeSummariesRequestSchema,
|
request: acknowledgeSummariesRequestSchema,
|
||||||
response: acknowledgeSummariesResponseSchema,
|
response: acknowledgeSummariesResponseSchema,
|
||||||
auth: true,
|
auth: true,
|
||||||
|
manualDecision: false,
|
||||||
},
|
},
|
||||||
/** CR-022: the welcome cards are closed for this company (decision 0.5.131). */
|
/** CR-022: the welcome cards are closed for this company (decision 0.5.131). */
|
||||||
markWelcomeSeen: {
|
markWelcomeSeen: {
|
||||||
|
|
@ -218,6 +246,7 @@ export const routes = {
|
||||||
request: emptyRequestSchema,
|
request: emptyRequestSchema,
|
||||||
response: playerStateSchema,
|
response: playerStateSchema,
|
||||||
auth: true,
|
auth: true,
|
||||||
|
manualDecision: false,
|
||||||
},
|
},
|
||||||
acceptDeal: {
|
acceptDeal: {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
@ -225,6 +254,7 @@ export const routes = {
|
||||||
request: emptyRequestSchema,
|
request: emptyRequestSchema,
|
||||||
response: playerStateSchema,
|
response: playerStateSchema,
|
||||||
auth: true,
|
auth: true,
|
||||||
|
manualDecision: true,
|
||||||
},
|
},
|
||||||
/** On the device only (CR-014): the save as an export code. */
|
/** On the device only (CR-014): the save as an export code. */
|
||||||
exportSave: {
|
exportSave: {
|
||||||
|
|
@ -233,6 +263,7 @@ export const routes = {
|
||||||
request: emptyRequestSchema,
|
request: emptyRequestSchema,
|
||||||
response: exportSaveResponseSchema,
|
response: exportSaveResponseSchema,
|
||||||
auth: true,
|
auth: true,
|
||||||
|
manualDecision: false,
|
||||||
},
|
},
|
||||||
/** On the device only (CR-014): replaces this device's company with an exported one. */
|
/** On the device only (CR-014): replaces this device's company with an exported one. */
|
||||||
importSave: {
|
importSave: {
|
||||||
|
|
@ -241,6 +272,7 @@ export const routes = {
|
||||||
request: importSaveRequestSchema,
|
request: importSaveRequestSchema,
|
||||||
response: createGuestResponseSchema,
|
response: createGuestResponseSchema,
|
||||||
auth: false,
|
auth: false,
|
||||||
|
manualDecision: false,
|
||||||
},
|
},
|
||||||
/** Non-production only (CR-011): not registered when NODE_ENV is production. */
|
/** Non-production only (CR-011): not registered when NODE_ENV is production. */
|
||||||
devAdvanceClock: {
|
devAdvanceClock: {
|
||||||
|
|
@ -249,6 +281,7 @@ export const routes = {
|
||||||
request: devAdvanceClockRequestSchema,
|
request: devAdvanceClockRequestSchema,
|
||||||
response: devAdvanceClockResponseSchema,
|
response: devAdvanceClockResponseSchema,
|
||||||
auth: true,
|
auth: true,
|
||||||
|
manualDecision: false,
|
||||||
},
|
},
|
||||||
devAddCredits: {
|
devAddCredits: {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
@ -256,6 +289,7 @@ export const routes = {
|
||||||
request: devAddCreditsRequestSchema,
|
request: devAddCreditsRequestSchema,
|
||||||
response: companyResponseSchema,
|
response: companyResponseSchema,
|
||||||
auth: true,
|
auth: true,
|
||||||
|
manualDecision: false,
|
||||||
},
|
},
|
||||||
devPilot: {
|
devPilot: {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
@ -263,6 +297,7 @@ export const routes = {
|
||||||
request: devPilotRequestSchema,
|
request: devPilotRequestSchema,
|
||||||
response: playerStateSchema,
|
response: playerStateSchema,
|
||||||
auth: true,
|
auth: true,
|
||||||
|
manualDecision: false,
|
||||||
},
|
},
|
||||||
devShip: {
|
devShip: {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
@ -270,6 +305,7 @@ export const routes = {
|
||||||
request: devShipRequestSchema,
|
request: devShipRequestSchema,
|
||||||
response: playerStateSchema,
|
response: playerStateSchema,
|
||||||
auth: true,
|
auth: true,
|
||||||
|
manualDecision: false,
|
||||||
},
|
},
|
||||||
devDiscoverAll: {
|
devDiscoverAll: {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
@ -277,6 +313,7 @@ export const routes = {
|
||||||
request: emptyRequestSchema,
|
request: emptyRequestSchema,
|
||||||
response: playerStateSchema,
|
response: playerStateSchema,
|
||||||
auth: true,
|
auth: true,
|
||||||
|
manualDecision: false,
|
||||||
},
|
},
|
||||||
newCompany: {
|
newCompany: {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
@ -284,6 +321,7 @@ export const routes = {
|
||||||
request: emptyRequestSchema,
|
request: emptyRequestSchema,
|
||||||
response: playerStateSchema,
|
response: playerStateSchema,
|
||||||
auth: true,
|
auth: true,
|
||||||
|
manualDecision: false,
|
||||||
},
|
},
|
||||||
renameCompany: {
|
renameCompany: {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
@ -291,6 +329,7 @@ export const routes = {
|
||||||
request: renameCompanyRequestSchema,
|
request: renameCompanyRequestSchema,
|
||||||
response: companyResponseSchema,
|
response: companyResponseSchema,
|
||||||
auth: true,
|
auth: true,
|
||||||
|
manualDecision: true,
|
||||||
},
|
},
|
||||||
repayLoan: {
|
repayLoan: {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
@ -298,6 +337,7 @@ export const routes = {
|
||||||
request: repayLoanRequestSchema,
|
request: repayLoanRequestSchema,
|
||||||
response: companyResponseSchema,
|
response: companyResponseSchema,
|
||||||
auth: true,
|
auth: true,
|
||||||
|
manualDecision: true,
|
||||||
},
|
},
|
||||||
claimGoal: {
|
claimGoal: {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
@ -305,6 +345,7 @@ export const routes = {
|
||||||
request: claimGoalRequestSchema,
|
request: claimGoalRequestSchema,
|
||||||
response: companyResponseSchema,
|
response: companyResponseSchema,
|
||||||
auth: true,
|
auth: true,
|
||||||
|
manualDecision: true,
|
||||||
},
|
},
|
||||||
} as const satisfies Record<string, RouteDef>;
|
} as const satisfies Record<string, RouteDef>;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -79,3 +79,42 @@ describe("route catalog", () => {
|
||||||
expect(open.sort()).toEqual(["createGuest", "getServerTime", "importSave"]);
|
expect(open.sort()).toEqual(["createGuest", "getServerTime", "importSave"]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("manual decisions in the catalog (Step 24.2, decision 0.5.136)", () => {
|
||||||
|
it("has every command declare whether it is a manual decision", () => {
|
||||||
|
for (const [name, route] of Object.entries(routes) as [string, RouteDef][]) {
|
||||||
|
expect(typeof route.manualDecision, name).toBe("boolean");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("counts the player's game commands, not viewing, dismissing, saves or dev tools", () => {
|
||||||
|
const manual = Object.entries(routes)
|
||||||
|
.filter(([, route]) => route.manualDecision)
|
||||||
|
.map(([name]) => name)
|
||||||
|
.sort();
|
||||||
|
expect(manual).toEqual(
|
||||||
|
[
|
||||||
|
"acceptDeal",
|
||||||
|
"assignPilot",
|
||||||
|
"buyDispatchOffice",
|
||||||
|
"buyShip",
|
||||||
|
"claimGoal",
|
||||||
|
"createDispatch",
|
||||||
|
"hirePilot",
|
||||||
|
"leaseBay",
|
||||||
|
"orderEmergencyFuel",
|
||||||
|
"refuelShip",
|
||||||
|
"renameCompany",
|
||||||
|
"repayLoan",
|
||||||
|
"restPilot",
|
||||||
|
"setPilotMode",
|
||||||
|
"setPilotOrders",
|
||||||
|
"setRefuelPolicy",
|
||||||
|
"setSpendingFloor",
|
||||||
|
"spendSkillPoint",
|
||||||
|
"startUpgrade",
|
||||||
|
"unassignPilot",
|
||||||
|
].sort(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue