Step 2.4: add cargo types and skills
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
10ded07ea6
commit
6804a7120c
|
|
@ -57,3 +57,9 @@ Implementation decisions not spelled out in `memory-bank/implementation-plan.md`
|
|||
- **Ranges** (million km): Courier 5 (Moon only; Mars is ~55 at its closest), Freighter 650 (covers Ceres at its farthest, ~600, and Jupiter near its closest, ~590, per decision 0.5.25), Bulk Hauler 1,000 (Jupiter at its farthest, ~970). Step 3.7 checks these against real positions.
|
||||
- **Speeds and overheads** aim at GDD §6 durations at compression 60: Courier 3M km/h with 2 h overhead (Earth→Moon ≈ 2 real minutes, mostly overhead), Freighter 2M km/h with 4 h (Earth→Mars ≈ 1–2 real hours), Bulk Hauler 1.2M km/h with 8 h.
|
||||
- **Prices** (30,000 / 150,000 / 600,000), **capacities** (20 / 100 / 400 t), **fuel costs** (20 / 40 / 80 per million km) and **hull ratings** (1 / 2 / 3) are first guesses; Step 2.5 tunes the Courier price.
|
||||
|
||||
## Step 2.4
|
||||
|
||||
- **Ids:** cargo `standard`, `heavy`, `high-value`, `dangerous`; skills `long-haul`, `heavy-cargo`, `expensive-cargo`, `dangerous-cargo`.
|
||||
- **Base pay** (credits per tonne per million km): Standard 10, Heavy 14, High-Value 18, Dangerous 20. **Tier multipliers:** 1, 1.2, 1.25, 1.3. First guesses, to be tuned in Phase 12.
|
||||
- **Cargo and skills share one data file** (`src/data/cargo.ts`), since every cargo skill exists to gate a cargo type.
|
||||
|
|
|
|||
|
|
@ -13,6 +13,6 @@
|
|||
- [x] **Step 2.1 — Content schemas.** Zod 4 schemas in `packages/content/src/schemas.ts` for Location, ShipModel, CargoType, Skill and GameConfig, with inferred types. Tests check that each schema accepts a valid example and rejects it with any one field missing.
|
||||
- [x] **Step 2.2 — The five locations.** `src/data/locations.ts`: Earth, Moon, Mars, Ceres Station, Jupiter Cloud Platform. Earth and Moon start discovered; Earth has the highest launch multiplier.
|
||||
- [x] **Step 2.3 — The three ship models.** `src/data/ships.ts`: Courier, Freighter, Bulk Hauler, all atmospheric. Speed, capacity and price orderings tested; range rules are checked in Step 3.7.
|
||||
- [ ] Step 2.4 — Cargo types and skills
|
||||
- [x] **Step 2.4 — Cargo types and skills.** `src/data/cargo.ts`: Standard, Heavy, High-Value, Dangerous; skills Long Haul, Heavy Cargo, Expensive Cargo, Dangerous Cargo (max level 5). The location test now checks cargo ids against the real data.
|
||||
- [ ] Step 2.5 — Default game config
|
||||
- [ ] Step 2.6 — Single content bundle
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
import type { CargoType, Skill } from "../schemas";
|
||||
|
||||
/** Standard is always tier 0; other types carry a tier from 1 to 5 (decision 0.5.6). */
|
||||
export const cargoTypes: CargoType[] = [
|
||||
{
|
||||
id: "standard",
|
||||
name: "Standard",
|
||||
basePayPerTonnePerMillionKm: 10,
|
||||
tierPayMultiplier: 1,
|
||||
requiredSkillId: null,
|
||||
},
|
||||
{
|
||||
id: "heavy",
|
||||
name: "Heavy",
|
||||
basePayPerTonnePerMillionKm: 14,
|
||||
tierPayMultiplier: 1.2,
|
||||
requiredSkillId: "heavy-cargo",
|
||||
},
|
||||
{
|
||||
id: "high-value",
|
||||
name: "High-Value",
|
||||
basePayPerTonnePerMillionKm: 18,
|
||||
tierPayMultiplier: 1.25,
|
||||
requiredSkillId: "expensive-cargo",
|
||||
},
|
||||
{
|
||||
id: "dangerous",
|
||||
name: "Dangerous",
|
||||
basePayPerTonnePerMillionKm: 20,
|
||||
tierPayMultiplier: 1.3,
|
||||
requiredSkillId: "dangerous-cargo",
|
||||
},
|
||||
];
|
||||
|
||||
export const skills: Skill[] = [
|
||||
{
|
||||
id: "long-haul",
|
||||
name: "Long Haul",
|
||||
maxLevel: 5,
|
||||
description: "Access to longer routes.",
|
||||
},
|
||||
{
|
||||
id: "heavy-cargo",
|
||||
name: "Heavy Cargo",
|
||||
maxLevel: 5,
|
||||
description: "Access to heavier loads, one tier per level.",
|
||||
},
|
||||
{
|
||||
id: "expensive-cargo",
|
||||
name: "Expensive Cargo",
|
||||
maxLevel: 5,
|
||||
description: "Access to high-value cargo, one tier per level.",
|
||||
},
|
||||
{
|
||||
id: "dangerous-cargo",
|
||||
name: "Dangerous Cargo",
|
||||
maxLevel: 5,
|
||||
description: "Access to hazardous cargo, one hazard class per level.",
|
||||
},
|
||||
];
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
export * from "./schemas";
|
||||
export * from "./data/locations";
|
||||
export * from "./data/ships";
|
||||
export * from "./data/cargo";
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { cargoTypeSchema, cargoTypes, skillSchema, skills } from "../src/index";
|
||||
|
||||
describe("cargo types", () => {
|
||||
it("all pass the schema", () => {
|
||||
for (const cargo of cargoTypes) {
|
||||
expect(() => cargoTypeSchema.parse(cargo)).not.toThrow();
|
||||
}
|
||||
});
|
||||
|
||||
it("has Standard plus three skill-gated types", () => {
|
||||
expect(cargoTypes.map((c) => c.id)).toEqual(["standard", "heavy", "high-value", "dangerous"]);
|
||||
expect(cargoTypes.find((c) => c.id === "standard")!.requiredSkillId).toBeNull();
|
||||
});
|
||||
|
||||
it("requires only skills that exist", () => {
|
||||
const skillIds = skills.map((s) => s.id);
|
||||
for (const cargo of cargoTypes) {
|
||||
if (cargo.requiredSkillId !== null) {
|
||||
expect(skillIds).toContain(cargo.requiredSkillId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("pays more than Standard for every non-standard type", () => {
|
||||
const standard = cargoTypes.find((c) => c.id === "standard")!;
|
||||
for (const cargo of cargoTypes) {
|
||||
if (cargo !== standard) {
|
||||
expect(cargo.basePayPerTonnePerMillionKm).toBeGreaterThan(
|
||||
standard.basePayPerTonnePerMillionKm,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("skills", () => {
|
||||
it("all pass the schema", () => {
|
||||
for (const skill of skills) {
|
||||
expect(() => skillSchema.parse(skill)).not.toThrow();
|
||||
}
|
||||
});
|
||||
|
||||
it("has the four base skills, each max level 5", () => {
|
||||
expect(skills.map((s) => s.id)).toEqual([
|
||||
"long-haul",
|
||||
"heavy-cargo",
|
||||
"expensive-cargo",
|
||||
"dangerous-cargo",
|
||||
]);
|
||||
for (const skill of skills) {
|
||||
expect(skill.maxLevel).toBe(5);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { locationSchema, locations } from "../src/index";
|
||||
import { cargoTypes, locationSchema, locations } from "../src/index";
|
||||
|
||||
// The cargo types planned for Step 2.4; replaced by the real data once it exists.
|
||||
const cargoTypeIds = ["standard", "heavy", "high-value", "dangerous"];
|
||||
const cargoTypeIds = cargoTypes.map((c) => c.id);
|
||||
|
||||
describe("locations", () => {
|
||||
it("all pass the schema", () => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue