Step 3.4: add route distance

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
fireshaper 2026-10-02 18:42:02 -04:00
parent 301c0934e1
commit ccd571fdd0
5 changed files with 49 additions and 1 deletions

View File

@ -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. - **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. - **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. - **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.

View File

@ -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.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.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. - [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.5 — Travel time
- [ ] Step 3.6 — Fuel cost - [ ] Step 3.6 — Fuel cost
- [ ] Step 3.7 — Range and landing eligibility - [ ] Step 3.7 — Range and landing eligibility

View File

@ -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);
}

View File

@ -1,3 +1,4 @@
export * from "./clock"; export * from "./clock";
export * from "./rng"; export * from "./rng";
export * from "./positions"; export * from "./positions";
export * from "./distance";

View File

@ -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));
}
}
});
});