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).
|
- **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.
|
- **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).
|
- **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.
|
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.2 — Sim: profit-share pay
|
||||||
- [ ] Step 10J.3 — Sim: goal change
|
- [ ] Step 10J.3 — Sim: goal change
|
||||||
- [ ] Step 10J.4 — Server: veteran on the deal
|
- [ ] Step 10J.4 — Server: veteran on the deal
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { cargoTypes, skills } from "./data/cargo";
|
||||||
import { gameConfig } from "./data/config";
|
import { gameConfig } from "./data/config";
|
||||||
import { goods } from "./data/goods";
|
import { goods } from "./data/goods";
|
||||||
import { locations } from "./data/locations";
|
import { locations } from "./data/locations";
|
||||||
import { onboardingGoals, scriptedTutorialContracts } from "./data/onboarding";
|
import { onboardingGoals, scriptedTutorialContracts, veteranPilot } from "./data/onboarding";
|
||||||
import { shipModels } from "./data/ships";
|
import { shipModels } from "./data/ships";
|
||||||
import { upgrades } from "./data/upgrades";
|
import { upgrades } from "./data/upgrades";
|
||||||
|
|
||||||
|
|
@ -24,6 +24,7 @@ export const content = {
|
||||||
config: gameConfig,
|
config: gameConfig,
|
||||||
scriptedTutorialContracts,
|
scriptedTutorialContracts,
|
||||||
onboardingGoals,
|
onboardingGoals,
|
||||||
|
veteranPilot,
|
||||||
upgrades,
|
upgrades,
|
||||||
location: byId(locations),
|
location: byId(locations),
|
||||||
shipModel: byId(shipModels),
|
shipModel: byId(shipModels),
|
||||||
|
|
|
||||||
|
|
@ -65,4 +65,6 @@ export const gameConfig: GameConfig = {
|
||||||
skillPointsPerLevel: 2,
|
skillPointsPerLevel: 2,
|
||||||
pilotHireCost: 2000,
|
pilotHireCost: 2000,
|
||||||
pilotPayPerDelivery: 100,
|
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). */
|
/** Special tutorial contracts by run (decision 0.5.33). */
|
||||||
export const scriptedTutorialContracts: ScriptedTutorialContract[] = [
|
export const scriptedTutorialContracts: ScriptedTutorialContract[] = [
|
||||||
|
|
@ -52,3 +52,14 @@ export const onboardingGoals: OnboardingGoal[] = [
|
||||||
reward: 2500,
|
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,
|
pilotHireCost: nonNegativeInt,
|
||||||
/** Credits per delivery; the avatar is exempt (decision 0.5.12). */
|
/** Credits per delivery; the avatar is exempt (decision 0.5.12). */
|
||||||
pilotPayPerDelivery: nonNegativeInt,
|
pilotPayPerDelivery: nonNegativeInt,
|
||||||
|
/** Hired pilots' share of profit (payout − fuel) per contract delivery (decision 0.5.12, CR-010). */
|
||||||
|
pilotProfitShare: fraction,
|
||||||
})
|
})
|
||||||
.refine(
|
.refine(
|
||||||
(config) =>
|
(config) =>
|
||||||
|
|
@ -315,3 +317,14 @@ export const onboardingGoalSchema = z.object({
|
||||||
reward: positiveInt,
|
reward: positiveInt,
|
||||||
});
|
});
|
||||||
export type OnboardingGoal = z.infer<typeof onboardingGoalSchema>;
|
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,
|
skillPointsPerLevel: 1,
|
||||||
pilotHireCost: 1000,
|
pilotHireCost: 1000,
|
||||||
pilotPayPerDelivery: 100,
|
pilotPayPerDelivery: 100,
|
||||||
|
pilotProfitShare: 0.25,
|
||||||
tutorialLoanerShipModelId: "company-courier",
|
tutorialLoanerShipModelId: "company-courier",
|
||||||
tutorialRunsBeforeDeal: 4,
|
tutorialRunsBeforeDeal: 4,
|
||||||
tutorialMassTonnes: { light: 5, medium: 12, full: 20 },
|
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