Step 2.6: export a single content bundle
CI / check (push) Successful in 24s Details

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
fireshaper 2026-10-02 17:47:44 -04:00
parent 2c211524d3
commit 241e2613f4
5 changed files with 57 additions and 1 deletions

View File

@ -72,3 +72,8 @@ Implementation decisions not spelled out in `memory-bank/implementation-plan.md`
- **Starting credits are 0.** The company pays fuel during the tutorial, and the ~21-run target already assumes the player starts with nothing. - **Starting credits are 0.** The company pays fuel during the tutorial, and the ~21-run target already assumes the player starts with nothing.
- **Other defaults:** board size 8, tutorial board 40 (more than the ~30 runs that fit in a 60-minute period), first contact 10% at ×2 pay, contract mass 5–400 t, Long Haul cut-offs 100/250/400/600/800M km, ten pilot levels at 2 skill points each (enough to max all four skills), hire cost 2,000, pilot pay 100 per delivery. - **Other defaults:** board size 8, tutorial board 40 (more than the ~30 runs that fit in a 60-minute period), first contact 10% at ×2 pay, contract mass 5–400 t, Long Haul cut-offs 100/250/400/600/800M km, ten pilot levels at 2 skill points each (enough to max all four skills), hire cost 2,000, pilot pay 100 per delivery.
- **Known balance issue for Phase 12:** pay is linear in distance, but every trip pays a fixed overhead, so short Earth–Moon runs earn far less per real hour than long runs (a Freighter on Mars earns roughly 50× a Courier on the Moon). - **Known balance issue for Phase 12:** pay is linear in distance, but every trip pays a fixed overhead, so short Earth–Moon runs earn far less per real hour than long runs (a Freighter on Mars earns roughly 50× a Courier on the Moon).
## Step 2.6
- **Lookups return `undefined`** for an unknown id ("not found" without throwing). Callers decide whether a missing id is an error.
- **The package also exports each data list and schema individually**, alongside the `content` bundle, so tests and the server can validate data with the schemas.

View File

@ -15,4 +15,4 @@
- [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. - [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.
- [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. - [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.
- [x] **Step 2.5 — Default game config.** `src/data/config.ts`. Courier + Earth bay ≈ 21 tutorial runs with the placeholder average payout. Money values rescaled ×10 so tutorial runs pay meaningful amounts; epoch moved from `.env.example` into GameConfig. - [x] **Step 2.5 — Default game config.** `src/data/config.ts`. Courier + Earth bay ≈ 21 tutorial runs with the placeholder average payout. Money values rescaled ×10 so tutorial runs pay meaningful amounts; epoch moved from `.env.example` into GameConfig.
- [ ] Step 2.6 — Single content bundle - [x] **Step 2.6 — Single content bundle.** `content` in `src/bundle.ts` holds all data plus `location`, `shipModel`, `cargoType` and `skill` lookups that return `undefined` for unknown ids.

View File

@ -0,0 +1,24 @@
import { cargoTypes, skills } from "./data/cargo";
import { gameConfig } from "./data/config";
import { locations } from "./data/locations";
import { shipModels } from "./data/ships";
/** Returns a lookup that gives `undefined` for an unknown id instead of throwing. */
function byId<T extends { id: string }>(items: readonly T[]): (id: string) => T | undefined {
const map = new Map(items.map((item) => [item.id, item]));
return (id) => map.get(id);
}
/** All static game data in one object, with lookups by id. */
export const content = {
locations,
shipModels,
cargoTypes,
skills,
config: gameConfig,
location: byId(locations),
shipModel: byId(shipModels),
cargoType: byId(cargoTypes),
skill: byId(skills),
};
export type Content = typeof content;

View File

@ -3,3 +3,4 @@ export * from "./data/locations";
export * from "./data/ships"; export * from "./data/ships";
export * from "./data/cargo"; export * from "./data/cargo";
export * from "./data/config"; export * from "./data/config";
export * from "./bundle";

View File

@ -0,0 +1,26 @@
import { describe, expect, it } from "vitest";
import { content } from "../src/index";
describe("content bundle", () => {
it("contains every kind of content", () => {
expect(content.locations).toHaveLength(5);
expect(content.shipModels).toHaveLength(3);
expect(content.cargoTypes).toHaveLength(4);
expect(content.skills).toHaveLength(4);
expect(content.config.timeCompression).toBe(60);
});
it("looks up one item of each kind by id", () => {
expect(content.location("mars")?.name).toBe("Mars");
expect(content.shipModel("freighter")?.name).toBe("Freighter");
expect(content.cargoType("dangerous")?.name).toBe("Dangerous");
expect(content.skill("long-haul")?.name).toBe("Long Haul");
});
it("returns undefined for an unknown id without throwing", () => {
expect(content.location("pluto")).toBeUndefined();
expect(content.shipModel("x-wing")).toBeUndefined();
expect(content.cargoType("cheese")).toBeUndefined();
expect(content.skill("juggling")).toBeUndefined();
});
});