Step 10J.1: pilot profit share config and the veteran pilot
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
727b0c02da
commit
e0742fff76
|
|
@ -994,3 +994,9 @@ Implementation decisions not spelled out in `memory-bank/implementation-plan.md`
|
|||
- **Renumbered on arrival:** titled "CR-007 … Phase 10G"; CR-007 to CR-009 and Phases 10G–10I already exist, so it is **CR-010**, done as **Phase 10J** (its 10G.n are 10J.n).
|
||||
- **Decisions:** 0.5.12 Pilot pay is replaced in place (profit share). The CR's 0.5.51 and 0.5.52 are **0.5.69 Deduction order** and **0.5.70 Veteran pilot**. Its "CR-001 decision 0.5.24" (the loan) is this plan's §0.5.32.
|
||||
- **Goal and timing target changes** to CR-001's goal 4 and the "hire first pilot" target are applied in their steps (10J.3, 10J.7).
|
||||
|
||||
## Step 10J.1
|
||||
|
||||
- **The flat fee is removed one step later**, in 10J.2 together with the formula that replaces it, so every commit builds. `pilotProfitShare` is added here.
|
||||
- **The veteran lives with the onboarding content** (`content.veteranPilot`: name, bio, level, skills) since it belongs to the deal. Name and bio are from the CR's example.
|
||||
- **Skill points are checked against the level:** a level-2 pilot has 2 × 2 = 4 points; the veteran spends 1 (Heavy Cargo 1) and has 3 unspent.
|
||||
|
|
|
|||
|
|
@ -240,7 +240,7 @@ Spec: `memory-bank/change-requests/CR-009-ship-upgrades.md` (received as "CR-006
|
|||
|
||||
Spec: `memory-bank/change-requests/CR-010-veteran-pilot-profit-share.md` (received as "CR-007 … Phase 10G"). Decision 0.5.12 replaced; new decisions recorded as plan §0.5.69–0.5.70.
|
||||
|
||||
- [ ] Step 10J.1 — Content: config and veteran
|
||||
- [x] **Step 10J.1 — Content: config and veteran.** Config `pilotProfitShare` 0.25 (a fraction 0–1); the flat `pilotPayPerDelivery` is removed in 10J.2 when the sim switches to the share. `veteranPilotSchema` and `content.veteranPilot`: Rosa Kell, "Halvorsen's pilot for twelve years.", level 2, Heavy Cargo 1. Test: `content/test/veteran.test.ts`.
|
||||
- [ ] Step 10J.2 — Sim: profit-share pay
|
||||
- [ ] Step 10J.3 — Sim: goal change
|
||||
- [ ] Step 10J.4 — Server: veteran on the deal
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { cargoTypes, skills } from "./data/cargo";
|
|||
import { gameConfig } from "./data/config";
|
||||
import { goods } from "./data/goods";
|
||||
import { locations } from "./data/locations";
|
||||
import { onboardingGoals, scriptedTutorialContracts } from "./data/onboarding";
|
||||
import { onboardingGoals, scriptedTutorialContracts, veteranPilot } from "./data/onboarding";
|
||||
import { shipModels } from "./data/ships";
|
||||
import { upgrades } from "./data/upgrades";
|
||||
|
||||
|
|
@ -24,6 +24,7 @@ export const content = {
|
|||
config: gameConfig,
|
||||
scriptedTutorialContracts,
|
||||
onboardingGoals,
|
||||
veteranPilot,
|
||||
upgrades,
|
||||
location: byId(locations),
|
||||
shipModel: byId(shipModels),
|
||||
|
|
|
|||
|
|
@ -65,4 +65,6 @@ export const gameConfig: GameConfig = {
|
|||
skillPointsPerLevel: 2,
|
||||
pilotHireCost: 2000,
|
||||
pilotPayPerDelivery: 100,
|
||||
// A quarter of each contract's profit (payout − fuel) goes to a hired pilot (CR-010).
|
||||
pilotProfitShare: 0.25,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { OnboardingGoal, ScriptedTutorialContract } from "../schemas";
|
||||
import type { OnboardingGoal, ScriptedTutorialContract, VeteranPilot } from "../schemas";
|
||||
|
||||
/** Special tutorial contracts by run (decision 0.5.33). */
|
||||
export const scriptedTutorialContracts: ScriptedTutorialContract[] = [
|
||||
|
|
@ -52,3 +52,14 @@ export const onboardingGoals: OnboardingGoal[] = [
|
|||
reward: 2500,
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Halvorsen's last hire, who comes with the deal (decision 0.5.70): level 2,
|
||||
* one point already in Heavy Cargo, the rest unspent.
|
||||
*/
|
||||
export const veteranPilot: VeteranPilot = {
|
||||
name: "Rosa Kell",
|
||||
bio: "Halvorsen's pilot for twelve years.",
|
||||
level: 2,
|
||||
skills: { "heavy-cargo": 1 },
|
||||
};
|
||||
|
|
|
|||
|
|
@ -265,6 +265,8 @@ export const gameConfigSchema = z
|
|||
pilotHireCost: nonNegativeInt,
|
||||
/** Credits per delivery; the avatar is exempt (decision 0.5.12). */
|
||||
pilotPayPerDelivery: nonNegativeInt,
|
||||
/** Hired pilots' share of profit (payout − fuel) per contract delivery (decision 0.5.12, CR-010). */
|
||||
pilotProfitShare: fraction,
|
||||
})
|
||||
.refine(
|
||||
(config) =>
|
||||
|
|
@ -315,3 +317,14 @@ export const onboardingGoalSchema = z.object({
|
|||
reward: positiveInt,
|
||||
});
|
||||
export type OnboardingGoal = z.infer<typeof onboardingGoalSchema>;
|
||||
|
||||
/** The veteran pilot who comes with the deal (decision 0.5.70, CR-010). */
|
||||
export const veteranPilotSchema = z.object({
|
||||
name: z.string().min(1),
|
||||
/** One line shown on the deal and the pilot card. */
|
||||
bio: z.string().min(1),
|
||||
level: positiveInt,
|
||||
/** Skill levels already bought, by skill id. */
|
||||
skills: z.record(idSchema, positiveInt),
|
||||
});
|
||||
export type VeteranPilot = z.infer<typeof veteranPilotSchema>;
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@ const validGameConfig = {
|
|||
skillPointsPerLevel: 1,
|
||||
pilotHireCost: 1000,
|
||||
pilotPayPerDelivery: 100,
|
||||
pilotProfitShare: 0.25,
|
||||
tutorialLoanerShipModelId: "company-courier",
|
||||
tutorialRunsBeforeDeal: 4,
|
||||
tutorialMassTonnes: { light: 5, medium: 12, full: 20 },
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
content,
|
||||
gameConfig,
|
||||
gameConfigSchema,
|
||||
veteranPilot,
|
||||
veteranPilotSchema,
|
||||
} from "../src/index";
|
||||
|
||||
describe("veteran pilot and profit share (CR-010)", () => {
|
||||
it("has a valid profit share between 0 and 1", () => {
|
||||
expect(gameConfigSchema.safeParse(gameConfig).success).toBe(true);
|
||||
expect(gameConfig.pilotProfitShare).toBeGreaterThan(0);
|
||||
expect(gameConfig.pilotProfitShare).toBeLessThan(1);
|
||||
expect(gameConfigSchema.safeParse({ ...gameConfig, pilotProfitShare: 1.2 }).success).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it("gives the veteran a valid level and existing skills within their skill points", () => {
|
||||
expect(veteranPilotSchema.safeParse(veteranPilot).success).toBe(true);
|
||||
expect(veteranPilot.level).toBe(2);
|
||||
expect(veteranPilot.level).toBeLessThanOrEqual(gameConfig.pilotLevelXpThresholds.length);
|
||||
const spent = Object.values(veteranPilot.skills).reduce((a, b) => a + b, 0);
|
||||
expect(spent).toBeLessThanOrEqual(veteranPilot.level * gameConfig.skillPointsPerLevel);
|
||||
for (const [skillId, level] of Object.entries(veteranPilot.skills)) {
|
||||
const skill = content.skill(skillId);
|
||||
expect(skill, skillId).toBeDefined();
|
||||
expect(level).toBeLessThanOrEqual(skill!.maxLevel);
|
||||
}
|
||||
expect(veteranPilot.skills).toEqual({ "heavy-cargo": 1 });
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue