From f44789878e7edd9a78cf0f553341520a1f52bd36 Mon Sep 17 00:00:00 2001 From: fireshaper Date: Fri, 2 Oct 2026 18:40:19 -0400 Subject: [PATCH] Step 3.1: add game clock conversion Co-Authored-By: Claude Opus 5.5 --- DECISIONS.md | 5 +++++ memory-bank/progress.md | 21 +++++++++++++++++++++ packages/sim/package.json | 3 +++ packages/sim/src/clock.ts | 26 ++++++++++++++++++++++++++ packages/sim/src/index.ts | 2 +- packages/sim/test/clock.test.ts | 23 +++++++++++++++++++++++ pnpm-lock.yaml | 6 +++++- 7 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 packages/sim/src/clock.ts create mode 100644 packages/sim/test/clock.test.ts diff --git a/DECISIONS.md b/DECISIONS.md index f16a57c..7f4e0ae 100644 --- a/DECISIONS.md +++ b/DECISIONS.md @@ -77,3 +77,8 @@ Implementation decisions not spelled out in `memory-bank/implementation-plan.md` - **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. + +## Step 3.1 + +- **Times are Unix-millisecond numbers.** A real time is a wall-clock instant; a game time is the instant of the in-game date, so `new Date(gameTime)` gives the in-game date and can be passed straight to `astronomy-engine`. +- **Every sim function takes the content it needs as arguments** (here the `GameConfig`), rather than importing the default bundle, so tests can pass custom fixtures. diff --git a/memory-bank/progress.md b/memory-bank/progress.md index f51c789..6485c39 100644 --- a/memory-bank/progress.md +++ b/memory-bank/progress.md @@ -16,3 +16,24 @@ - [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.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. + +## Phase 3 — Simulation Package + +- [x] **Step 3.1 — Game clock conversion.** `src/clock.ts`: `realToGame` / `gameToReal` using the config epoch and compression; times are Unix-ms numbers. +- [ ] Step 3.2 — Seeded random number generator +- [ ] Step 3.3 — Location positions +- [ ] Step 3.4 — Route distance +- [ ] Step 3.5 — Travel time +- [ ] Step 3.6 — Fuel cost +- [ ] Step 3.7 — Range and landing eligibility +- [ ] Step 3.8 — Contract model and payout +- [ ] Step 3.9 — Contract board generation +- [ ] Step 3.10 — Tutorial contract board +- [ ] Step 3.11 — Pilot eligibility +- [ ] Step 3.12 — Dispatch validation +- [ ] Step 3.13 — Dispatch record creation +- [ ] Step 3.14 — Reposition dispatch +- [ ] Step 3.15 — In-flight position and progress +- [ ] Step 3.16 — XP and leveling +- [ ] Step 3.17 — Dispatch resolution +- [ ] Step 3.18 — Sim package public API and coverage diff --git a/packages/sim/package.json b/packages/sim/package.json index 7a8fc7a..6027bf4 100644 --- a/packages/sim/package.json +++ b/packages/sim/package.json @@ -7,5 +7,8 @@ "types": "./src/index.ts", "scripts": { "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@deadhead/content": "workspace:*" } } diff --git a/packages/sim/src/clock.ts b/packages/sim/src/clock.ts new file mode 100644 index 0000000..144b58b --- /dev/null +++ b/packages/sim/src/clock.ts @@ -0,0 +1,26 @@ +import type { GameConfig } from "@deadhead/content"; + +/** + * Times are milliseconds since the Unix epoch. A real time is a wall-clock + * instant; a game time is the instant of the in-game (ephemeris) date, so + * `new Date(gameTime)` is the in-game date. + */ +export type RealTime = number; +export type GameTime = number; + +export const MS_PER_MINUTE = 60_000; +export const MS_PER_HOUR = 3_600_000; + +/** Converts a real instant to the in-game instant it maps to. */ +export function realToGame(real: RealTime, config: GameConfig): GameTime { + const realStart = Date.parse(config.epoch.realStart); + const gameStart = Date.parse(config.epoch.ephemerisDate); + return gameStart + (real - realStart) * config.timeCompression; +} + +/** Converts an in-game instant back to the real instant it maps to. */ +export function gameToReal(game: GameTime, config: GameConfig): RealTime { + const realStart = Date.parse(config.epoch.realStart); + const gameStart = Date.parse(config.epoch.ephemerisDate); + return realStart + (game - gameStart) / config.timeCompression; +} diff --git a/packages/sim/src/index.ts b/packages/sim/src/index.ts index cb0ff5c..83adf15 100644 --- a/packages/sim/src/index.ts +++ b/packages/sim/src/index.ts @@ -1 +1 @@ -export {}; +export * from "./clock"; diff --git a/packages/sim/test/clock.test.ts b/packages/sim/test/clock.test.ts new file mode 100644 index 0000000..f098346 --- /dev/null +++ b/packages/sim/test/clock.test.ts @@ -0,0 +1,23 @@ +import { gameConfig } from "@deadhead/content"; +import { describe, expect, it } from "vitest"; +import { MS_PER_HOUR, MS_PER_MINUTE, gameToReal, realToGame } from "../src/index"; + +const realStart = Date.parse(gameConfig.epoch.realStart); +const gameStart = Date.parse(gameConfig.epoch.ephemerisDate); + +describe("game clock", () => { + it("maps the real epoch to the game epoch", () => { + expect(realToGame(realStart, gameConfig)).toBe(gameStart); + }); + + it("round-trips real → game → real", () => { + for (const real of [realStart, realStart + 12_345_678, Date.parse("2027-06-15T08:30:00Z")]) { + expect(gameToReal(realToGame(real, gameConfig), gameConfig)).toBe(real); + } + }); + + it("advances one game hour per real minute at compression 60", () => { + expect(gameConfig.timeCompression).toBe(60); + expect(realToGame(realStart + MS_PER_MINUTE, gameConfig)).toBe(gameStart + MS_PER_HOUR); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 57bf141..2bb74b2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -48,7 +48,11 @@ importers: packages/schema: {} - packages/sim: {} + packages/sim: + dependencies: + '@deadhead/content': + specifier: workspace:* + version: link:../content packages: