Step 10B.1: generate contract boards for any location
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
ea37ce8ced
commit
dff2c576cf
|
|
@ -617,3 +617,7 @@ Implementation decisions not spelled out in `memory-bank/implementation-plan.md`
|
||||||
- **Change request CR-002 is stored in `memory-bank/change-requests/`** and inserted as Phase 10B, before Phase 11.
|
- **Change request CR-002 is stored in `memory-bank/change-requests/`** and inserted as Phase 10B, before Phase 11.
|
||||||
- **Decision numbering:** the CR's new 0.5.27 (Ship delivery) and 0.5.28 (Undiscovered locations) are recorded as **0.5.35** and **0.5.36**, since 27–34 were already in use. Decision 0.5.5 is replaced and the glossary gains "Contract board" and "Deadhead".
|
- **Decision numbering:** the CR's new 0.5.27 (Ship delivery) and 0.5.28 (Undiscovered locations) are recorded as **0.5.35** and **0.5.36**, since 27–34 were already in use. Decision 0.5.5 is replaced and the glossary gains "Contract board" and "Deadhead".
|
||||||
- **Error code:** the CR's `location_undiscovered` is the existing `location-undiscovered`.
|
- **Error code:** the CR's `location_undiscovered` is the existing `location-undiscovered`.
|
||||||
|
|
||||||
|
## Step 10B.1
|
||||||
|
|
||||||
|
- **The board generator's input is `location`** (the freight market's location). Sim never knew about bays for boards; the old field name `origin` was the only "bay-board" naming, and comments now say "location board". Generation rules and seeds (`board:<location>:<period>`) are unchanged.
|
||||||
|
|
|
||||||
|
|
@ -148,7 +148,7 @@ export async function bayBoard(
|
||||||
.where(eq(discoveredLocations.playerId, playerId));
|
.where(eq(discoveredLocations.playerId, playerId));
|
||||||
return generateContractBoard(
|
return generateContractBoard(
|
||||||
{
|
{
|
||||||
origin,
|
location: origin,
|
||||||
discoveredIds: new Set(discovered.map((d) => d.locationId)),
|
discoveredIds: new Set(discovered.map((d) => d.locationId)),
|
||||||
periodIndex,
|
periodIndex,
|
||||||
worldSeed: player!.worldSeed,
|
worldSeed: player!.worldSeed,
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,7 @@ Spec: `memory-bank/change-requests/CR-001-faster-start.md`. Decisions recorded a
|
||||||
|
|
||||||
Spec: `memory-bank/change-requests/CR-002-open-markets.md`. New decisions recorded as plan §0.5.35–0.5.36.
|
Spec: `memory-bank/change-requests/CR-002-open-markets.md`. New decisions recorded as plan §0.5.35–0.5.36.
|
||||||
|
|
||||||
- [ ] Step 10B.1 — Sim: boards at any location
|
- [x] **Step 10B.1 — Sim: boards at any location.** `BoardInput.location` (was `origin`) is the board's location; no bay notion in sim board generation. Moon board test added.
|
||||||
- [ ] Step 10B.2 — Sim: deadhead validation
|
- [ ] Step 10B.2 — Sim: deadhead validation
|
||||||
- [ ] Step 10B.3 — Database: boards keyed by location
|
- [ ] Step 10B.3 — Database: boards keyed by location
|
||||||
- [ ] Step 10B.4 — Server: board endpoint for any discovered location
|
- [ ] Step 10B.4 — Server: board endpoint for any discovered location
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,8 @@ export function standardCargo(content: SimContent): CargoType {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BoardInput {
|
export interface BoardInput {
|
||||||
origin: Location;
|
/** The location whose freight market this is; every contract starts here (CR-002). */
|
||||||
|
location: Location;
|
||||||
discoveredIds: ReadonlySet<string>;
|
discoveredIds: ReadonlySet<string>;
|
||||||
periodIndex: number;
|
periodIndex: number;
|
||||||
worldSeed: Seed;
|
worldSeed: Seed;
|
||||||
|
|
@ -109,12 +110,13 @@ function pickTier(rng: Rng, cargoType: CargoType, content: SimContent): number {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a bay's contract board for one period: `contractMassBands` decide how
|
* Generates a location's contract board for one period (any discovered
|
||||||
|
* location, bay or not, CR-002): `contractMassBands` decide how
|
||||||
* many contracts fall in each ship-size mass band. Same inputs always give the
|
* many contracts fall in each ship-size mass band. Same inputs always give the
|
||||||
* same board.
|
* same board.
|
||||||
*/
|
*/
|
||||||
export function generateContractBoard(input: BoardInput, content: SimContent): Contract[] {
|
export function generateContractBoard(input: BoardInput, content: SimContent): Contract[] {
|
||||||
const { origin, discoveredIds, periodIndex, worldSeed } = input;
|
const { location: origin, discoveredIds, periodIndex, worldSeed } = input;
|
||||||
const { config } = content;
|
const { config } = content;
|
||||||
const rng = createRng(deriveSeed(worldSeed, `board:${origin.id}:${periodIndex}`));
|
const rng = createRng(deriveSeed(worldSeed, `board:${origin.id}:${periodIndex}`));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ export function resolveDispatch(input: ResolveInput, content: SimContent): Resol
|
||||||
const { contract } = record;
|
const { contract } = record;
|
||||||
const base = { shipLocationId: record.destinationId };
|
const base = { shipLocationId: record.destinationId };
|
||||||
if (contract === null) {
|
if (contract === null) {
|
||||||
// Repositions earn nothing and only fly to bays, which are already discovered.
|
// Deadheads earn nothing and only fly to discovered locations.
|
||||||
return {
|
return {
|
||||||
status: "resolved",
|
status: "resolved",
|
||||||
resolution: {
|
resolution: {
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import {
|
||||||
const earth = content.location("earth")!;
|
const earth = content.location("earth")!;
|
||||||
const discoveredIds = new Set(["earth", "moon"]);
|
const discoveredIds = new Set(["earth", "moon"]);
|
||||||
const periodIndex = boardPeriodIndex(Date.parse("2100-01-01T00:00:00Z"), content.config);
|
const periodIndex = boardPeriodIndex(Date.parse("2100-01-01T00:00:00Z"), content.config);
|
||||||
const input: BoardInput = { origin: earth, discoveredIds, periodIndex, worldSeed: 12345 };
|
const input: BoardInput = { location: earth, discoveredIds, periodIndex, worldSeed: 12345 };
|
||||||
|
|
||||||
describe("contract board generation", () => {
|
describe("contract board generation", () => {
|
||||||
it("produces an identical board from the same inputs", () => {
|
it("produces an identical board from the same inputs", () => {
|
||||||
|
|
@ -42,7 +42,10 @@ describe("contract board generation", () => {
|
||||||
it("ties tier to skill: Standard is tier 0 with no skill, others 1–5 matching the skill level", () => {
|
it("ties tier to skill: Standard is tier 0 with no skill, others 1–5 matching the skill level", () => {
|
||||||
const moon = content.location("moon")!;
|
const moon = content.location("moon")!;
|
||||||
for (let p = 0; p < 50; p++) {
|
for (let p = 0; p < 50; p++) {
|
||||||
for (const c of generateContractBoard({ ...input, origin: moon, periodIndex: p }, content)) {
|
for (const c of generateContractBoard(
|
||||||
|
{ ...input, location: moon, periodIndex: p },
|
||||||
|
content,
|
||||||
|
)) {
|
||||||
if (c.cargoTypeId === "standard") {
|
if (c.cargoTypeId === "standard") {
|
||||||
expect(c.tier).toBe(0);
|
expect(c.tier).toBe(0);
|
||||||
expect(c.requiredSkill).toBeNull();
|
expect(c.requiredSkill).toBeNull();
|
||||||
|
|
@ -106,6 +109,20 @@ describe("contract board generation", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("location boards (CR-002)", () => {
|
||||||
|
it("generates a deterministic Moon board from the location alone, with no bay data", () => {
|
||||||
|
const moon = content.location("moon")!;
|
||||||
|
const moonInput: BoardInput = { location: moon, discoveredIds, periodIndex, worldSeed: 12345 };
|
||||||
|
const board = generateContractBoard(moonInput, content);
|
||||||
|
expect(board).toHaveLength(content.config.contractBoardSize);
|
||||||
|
for (const c of board) {
|
||||||
|
expect(c.originId).toBe("moon");
|
||||||
|
expect(c.destinationId).not.toBe("moon");
|
||||||
|
}
|
||||||
|
expect(generateContractBoard(moonInput, content)).toEqual(board);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("required Long Haul level", () => {
|
describe("required Long Haul level", () => {
|
||||||
it("counts the cut-offs a distance exceeds", () => {
|
it("counts the cut-offs a distance exceeds", () => {
|
||||||
expect(requiredLongHaulLevel(0.4, content.config)).toBe(0);
|
expect(requiredLongHaulLevel(0.4, content.config)).toBe(0);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue