From ccd571fdd0d9ed9c1d5413e0b25592f869ac0ab1 Mon Sep 17 00:00:00 2001 From: fireshaper Date: Fri, 2 Oct 2026 18:42:02 -0400 Subject: [PATCH] Step 3.4: add route distance Co-Authored-By: Claude Opus 5.5 --- DECISIONS.md | 4 ++++ memory-bank/progress.md | 2 +- packages/sim/src/distance.ts | 10 +++++++++ packages/sim/src/index.ts | 1 + packages/sim/test/distance.test.ts | 33 ++++++++++++++++++++++++++++++ 5 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 packages/sim/src/distance.ts create mode 100644 packages/sim/test/distance.test.ts diff --git a/DECISIONS.md b/DECISIONS.md index 8cef03f..a1b50ae 100644 --- a/DECISIONS.md +++ b/DECISIONS.md @@ -94,3 +94,7 @@ Implementation decisions not spelled out in `memory-bank/implementation-plan.md` - **The Moon is Earth plus 0.384M km on the +x axis**, as the plan specifies; its real orbit is ignored. - **Ceres uses a circular orbit** (astronomy-engine has no Ceres): radius 2.7675 AU (~414M km), period 1,680.5 days, mean longitude ~160° at J2000. The angle is approximate, which is fine for gameplay. - **Test date is 2100-03-01**, close to the placeholder game epoch. + +## Step 3.4 + +- **Route distance is the straight line between the two positions at one game time.** Orbital transfer paths are not modelled. diff --git a/memory-bank/progress.md b/memory-bank/progress.md index b01614b..dacbf7e 100644 --- a/memory-bank/progress.md +++ b/memory-bank/progress.md @@ -22,7 +22,7 @@ - [x] **Step 3.1 — Game clock conversion.** `src/clock.ts`: `realToGame` / `gameToReal` using the config epoch and compression; times are Unix-ms numbers. - [x] **Step 3.2 — Seeded RNG.** `src/rng.ts`: `createRng(seed)` (mulberry32) with `next`, `int`, `pick`; `deriveSeed(parent, label)` for child seeds. - [x] **Step 3.3 — Location positions.** `src/positions.ts`: heliocentric ecliptic positions (million km) from `astronomy-engine` for Earth, Mars and Jupiter; Moon = Earth + fixed offset; Ceres on a circular orbit. -- [ ] Step 3.4 — Route distance +- [x] **Step 3.4 — Route distance.** `src/distance.ts`: `routeDistance(from, to, gameTime)`, straight-line million km. - [ ] Step 3.5 — Travel time - [ ] Step 3.6 — Fuel cost - [ ] Step 3.7 — Range and landing eligibility diff --git a/packages/sim/src/distance.ts b/packages/sim/src/distance.ts new file mode 100644 index 0000000..cf00295 --- /dev/null +++ b/packages/sim/src/distance.ts @@ -0,0 +1,10 @@ +import type { Location } from "@deadhead/content"; +import type { GameTime } from "./clock"; +import { locationPosition } from "./positions"; + +/** Straight-line distance between two locations at a game time, in million km. */ +export function routeDistance(from: Location, to: Location, gameTime: GameTime): number { + const a = locationPosition(from, gameTime); + const b = locationPosition(to, gameTime); + return Math.hypot(b.x - a.x, b.y - a.y); +} diff --git a/packages/sim/src/index.ts b/packages/sim/src/index.ts index 1dc91a0..cc8060d 100644 --- a/packages/sim/src/index.ts +++ b/packages/sim/src/index.ts @@ -1,3 +1,4 @@ export * from "./clock"; export * from "./rng"; export * from "./positions"; +export * from "./distance"; diff --git a/packages/sim/test/distance.test.ts b/packages/sim/test/distance.test.ts new file mode 100644 index 0000000..5e33c53 --- /dev/null +++ b/packages/sim/test/distance.test.ts @@ -0,0 +1,33 @@ +import { content } from "@deadhead/content"; +import { describe, expect, it } from "vitest"; +import { routeDistance } from "../src/index"; + +const earth = content.location("earth")!; +const moon = content.location("moon")!; +const mars = content.location("mars")!; + +const start = Date.parse("2100-01-01T00:00:00Z"); +const MS_PER_DAY = 86_400_000; +/** Game times sampled every ~30 days across two years. */ +const monthly = Array.from({ length: 25 }, (_, i) => start + i * 30 * MS_PER_DAY); + +describe("route distance", () => { + it("keeps Earth→Moon under 1 million km at any time", () => { + for (const t of monthly) { + expect(routeDistance(earth, moon, t)).toBeLessThan(1); + } + }); + + it("varies Earth→Mars by at least 200 million km over two years", () => { + const distances = monthly.map((t) => routeDistance(earth, mars, t)); + expect(Math.max(...distances) - Math.min(...distances)).toBeGreaterThanOrEqual(200); + }); + + it("is symmetric", () => { + for (const a of content.locations) { + for (const b of content.locations) { + expect(routeDistance(a, b, start)).toBe(routeDistance(b, a, start)); + } + } + }); +});