deadhead-space-freight-tycoon/DECISIONS.md

4.1 KiB

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").