deadhead-space-freight-tycoon/DECISIONS.md

85 lines
8.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Decisions
Implementation decisions not spelled out in `memory-bank/implementation-plan.md`. Design decisions from plan §0.5 are logged here as they are implemented.
## Step 1.1
- **Repo lives on an NTFS drive (`C:\Projects\...`).** The original location (G:) is exFAT, which cannot hold symlinks, so pnpm could not link workspace packages. Do not move the repo back to an exFAT drive.
- **TypeScript is pinned to `~6.0`.** TypeScript 7 is the latest release, but `typescript-eslint` (needed in Step 1.2) only supports `<6.1`. Upgrade once typescript-eslint supports 7.
- **Packages export TypeScript source directly** (`main`/`types` point at `src/index.ts`). There is no build step for internal packages; Vite, Vitest and the server's TS runner consume the source. The base config uses `moduleResolution: "Bundler"` and `noEmit`.
- **Node 24** is the required runtime (`engines` + `.nvmrc`).
- **Line endings are LF** everywhere (`.gitattributes`), overriding the machine's global `core.autocrlf`, to match Prettier and the Linux CI runner.
## Step 1.2
- **ESLint 10 flat config** with `typescript-eslint` recommended (not type-aware, to keep lint fast) and `eslint-config-prettier`. `pnpm lint` runs ESLint then `prettier --check`.
- **Purity rule** for `packages/sim` and `packages/content`: `no-restricted-properties` bans `Math.random` and `Date.now`; `no-restricted-syntax` bans `new Date()` with no arguments (`new Date(x)` is allowed).
- **Prettier skips `memory-bank/`** so tooling never rewrites the design docs.
## Step 1.3
- **Vitest projects**: the root `vitest.config.ts` treats each `apps/*` and `packages/*` folder as a project, so `pnpm test` runs every package's tests. Packages can add their own `vitest.config.ts` when they need a different environment.
- **Tests live in each package's `test/` folder** and are included in that package's tsconfig, so `pnpm typecheck` covers them.
## Step 1.4
- **CI runs on Gitea Actions** (`.gitea/workflows/ci.yml`): install with a frozen lockfile, then typecheck, lint and test.
- **`.pnpm-store/` is ignored** by Prettier, ESLint and git. On the Gitea runner, pnpm puts its store inside the workspace, and `prettier --check` failed on the store's files (CI run #1).
## Step 1.5
- **One Postgres container, two databases.** `docker-compose.yml` runs a single `postgres:18` server on port 5432 (user/password `deadhead`). The default database `deadhead` is for development; `docker/postgres/init/01-create-test-db.sql` creates `deadhead_test` the first time the data volume is created. To recreate both from scratch, run `docker compose down -v`.
- **Env var names** in `.env.example`: `DATABASE_URL`, `TEST_DATABASE_URL`, `BETTER_AUTH_SECRET` and `BETTER_AUTH_URL` (Better Auth's own names), `PORT`, and `GAME_EPOCH_REAL_START` plus `GAME_EPOCH_EPHEMERIS_DATE` (decision 0.5.9). The game-clock speed is not an env var; it belongs in GameConfig (rule 0.1.5).
- **Epoch values in `.env.example` are placeholders.** The design docs do not fix an ephemeris date yet.
## Step 2.1
- **Ids are lowercase kebab-case strings** (`earth`, `bulk-hauler`, `long-haul`).
- **One landing-class enum** (`vacuum`, `atmospheric`, `hardened`) serves both a location's landing requirement and a ship's landing class. `hardened` is not used in the base game.
- **Location `body`** is an enum of the five astronomy bodies (`earth`, `moon`, `mars`, `ceres`, `jupiter`) so the sim can map each to a position source in Step 3.3.
- **Epoch fields are ISO 8601 UTC strings** (`epoch.realStart`, `epoch.ephemerisDate`), not `Date` objects, so content stays plain data.
- **`tutorialBoardSize`** is a GameConfig field in addition to `contractBoardSize`, because decision 0.5.21 sets the tutorial board's size separately.
- **XP per delivery inputs** are `base`, `perMillionKm` and `perCargoTier`. Step 3.16 decides how they combine.
- **`pilotLevelXpThresholds`** lists the _total_ XP needed for level 1, 2, 3, ...; it and the Long Haul cut-offs must be strictly increasing.
- **`tierPayMultiplier`** is one number per cargo type. Step 3.8 decides how it scales with tier.
- **Imports omit the `.ts` extension** (`moduleResolution: "Bundler"`).
## Step 2.2
- **Location ids:** `earth`, `moon`, `mars`, `ceres-station`, `jupiter-cloud-platform`.
- **Cargo flows** follow GDD §9: Earth and Mars produce standard and high-value goods and demand ore (heavy) and fuel (dangerous); the Moon produces standard and heavy; Ceres produces heavy; Jupiter produces dangerous (fuel). The outposts import high-value equipment.
- **Launch multipliers:** Earth 3, Jupiter Cloud Platform 2, Mars 1.5, Moon 0.5, Ceres 0.3.
- **Bay lease prices are first guesses** (Earth 5,000 up to Jupiter 15,000). Step 2.5 tunes the Earth price against the tutorial.
- **The cargo-id check uses a placeholder list** of the four cargo ids planned in Step 2.4 until the cargo data exists.
## Step 2.3
- **Ranges** (million km): Courier 5 (Moon only; Mars is ~55 at its closest), Freighter 650 (covers Ceres at its farthest, ~600, and Jupiter near its closest, ~590, per decision 0.5.25), Bulk Hauler 1,000 (Jupiter at its farthest, ~970). Step 3.7 checks these against real positions.
- **Speeds and overheads** aim at GDD §6 durations at compression 60: Courier 3M km/h with 2 h overhead (Earth→Moon ≈ 2 real minutes, mostly overhead), Freighter 2M km/h with 4 h (Earth→Mars ≈ 1–2 real hours), Bulk Hauler 1.2M km/h with 8 h.
- **Prices** (30,000 / 150,000 / 600,000), **capacities** (20 / 100 / 400 t), **fuel costs** (20 / 40 / 80 per million km) and **hull ratings** (1 / 2 / 3) are first guesses; Step 2.5 tunes the Courier price.
## Step 2.4
- **Ids:** cargo `standard`, `heavy`, `high-value`, `dangerous`; skills `long-haul`, `heavy-cargo`, `expensive-cargo`, `dangerous-cargo`.
- **Base pay** (credits per tonne per million km): Standard 10, Heavy 14, High-Value 18, Dangerous 20. **Tier multipliers:** 1, 1.2, 1.25, 1.3. First guesses, to be tuned in Phase 12.
- **Cargo and skills share one data file** (`src/data/cargo.ts`), since every cargo skill exists to gate a cargo type.
## Step 2.5
- **The game epoch lives in GameConfig, not in env vars.** Step 2.1 puts it in GameConfig, and rule 0.1.5 keeps game numbers in `packages/content`. The `GAME_EPOCH_*` variables added to `.env.example` in Step 1.5 are removed; the placeholder values (real start 2026-01-01, in-game 2100-01-01) are unchanged.
- **Money values scaled up.** With the Step 2.2–2.4 rates a tutorial run paid ~24 credits. Pay rates and fuel costs are ×10, and prices are set so a Courier (4,000) plus an Earth bay (1,000) takes ~21 runs at ~240 credits each. Freighter 100,000, Bulk Hauler 400,000; leases Moon 800, Mars 20,000, Ceres 40,000, Jupiter 80,000.
- **Placeholder tutorial payout in the test:** Standard pay × 0.384M km × average mass (midpoint of the contract minimum and Courier capacity, 12.5 t) × tutorial share (0.5).
- **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.
- **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.
## 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.