Fantasy-Map-Generator/_bmad-output/project-context.md
2026-03-12 01:05:55 +01:00

214 lines
8.8 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.

---
project_name: "Fantasy-Map-Generator"
user_name: "Azgaar"
date: "2026-03-12"
sections_completed:
["technology_stack", "architecture", "language_rules", "framework_rules", "testing_rules", "code_quality", "workflow"]
---
# Project Context for AI Agents
_Critical rules and patterns that AI agents must follow when implementing code in this project. Focuses on unobvious details that agents might otherwise miss._
---
## Technology Stack & Versions
| Technology | Version | Role |
| ---------- | -------- | ----------------------------------------------- |
| TypeScript | ^5.9.3 | Source language for `src/` |
| Vite | ^7.3.1 | Build tool & dev server |
| Biome | 2.3.13 | Linter & formatter (replaces ESLint + Prettier) |
| Vitest | ^4.0.18 | Unit & browser unit tests |
| Playwright | ^1.57.0 | E2E tests |
| D3 | ^7.9.0 | SVG rendering & data manipulation |
| Delaunator | ^5.0.1 | Voronoi/Delaunay triangulation |
| Three.js | ^0.183.2 | 3D globe view |
| Polylabel | ^2.0.1 | Polygon label placement |
| Node.js | >=24.0.0 | Runtime requirement |
---
## Architecture Overview
**Hybrid codebase**: New code lives in `src/` (TypeScript, bundled by Vite). Legacy code lives in `public/modules/` (plain JavaScript, loaded as-is). The two halves communicate through `window` globals.
**Vite config quirk**: `root` is `./src`, `publicDir` is `../public`. All paths in config files must be relative to `src/`.
---
## Critical Implementation Rules
### Global Module Pattern (MOST IMPORTANT)
Every TypeScript generator module follows this mandatory pattern:
1. **Define type and declare global** at the top of the file:
```ts
declare global {
var ModuleName: ModuleClass;
}
```
2. **Implement as a class**:
```ts
class ModuleClass {
// methods
}
```
3. **Register on `window` at the bottom of the file** (last line):
```ts
window.ModuleName = new ModuleClass();
```
4. **Import the module** in `src/modules/index.ts` (side-effect import):
```ts
import "./module-name";
```
**Utility functions used by legacy JS** must also be attached to `window` via `src/utils/index.ts`.
### Global Variables
Key globals declared in `src/types/global.ts` — always use these directly, never redeclare:
- `pack` (`PackedGraph`) — main data structure with all cell/feature data
- `grid` — raw grid data before packing
- `graphWidth`, `graphHeight` — map canvas dimensions
- `svgWidth`, `svgHeight` — SVG element dimensions
- `TIME` / `WARN` / `ERROR` / `DEBUG` — logging flags
- `seed` — current map seed string
D3 selection globals (for SVG manipulation): `svg`, `viewbox`, `rivers`, `labels`, `burgLabels`, `burgIcons`, `markers`, `defs`, `coastline`, `lakes`, `terrs`, `routes`, etc.
### Data Structures & Typed Arrays
The `PackedGraph.cells` object stores most data in **typed arrays** for performance. Always use the utility functions:
```ts
import {createTypedArray, getTypedArray} from "../utils";
// Create typed array (auto-selects Uint8/Uint16/Uint32 based on maxValue)
createTypedArray({maxValue: cells.i.length, length: n});
// Get constructor only
getTypedArray(maxValue);
```
**Never use plain JS arrays for numeric cell data** — always use typed arrays.
### Land Height Threshold
Land cells have height `>= 20`. Water/ocean cells have height `< 20`. This threshold is a project-wide constant:
```ts
const isLand = (cellId: number) => cells.h[cellId] >= 20;
```
Use exactly `>= 20` — no magic numbers, no alternative thresholds.
### Language-Specific Rules
- **TypeScript strict mode** is on: `strict`, `noUnusedLocals`, `noUnusedParameters`, `noFallthroughCasesInSwitch` all enabled.
- **`noEmit: true`** — TypeScript is typechecking only; Vite handles transpilation.
- **`isolatedModules: true`** — each file must be independently compilable; avoid type-only exports without `type` keyword.
- **Module resolution**: `bundler` mode with `allowImportingTsExtensions` — use `.ts` extensions in imports within `src/`.
- **`noExplicitAny`** is disabled — `any` is permitted where needed (legacy interop).
- **`noNonNullAssertion`** is disabled — `!` non-null assertions are allowed.
- **Always use `Number.isNaN()`** — never `isNaN()` (Biome `noGlobalIsNan` rule is an error).
- **Always provide radix to `parseInt()`** — `parseInt(str, 10)` (Biome `useParseIntRadix` rule).
- **Use template literals** over string concatenation (Biome `useTemplate` warning).
- Import `rn` from `"../utils"` for rounding — `rn(value, decimals)`.
### Code Organization
- `src/modules/` — generator classes (one domain per file, kebab-case filename)
- `src/renderers/` — SVG draw functions (prefixed `draw-`, registered as `window.drawX`)
- `src/utils/` — pure utility functions exported as named exports
- `src/types/` — TypeScript type declarations (`PackedGraph.ts`, `global.ts`)
- `src/config/` — static configuration data
- `public/modules/` — legacy JavaScript (do not add TypeScript here)
### Naming Conventions
| Item | Convention | Example |
| ----------------- | ---------------------------------- | --------------------------------------- |
| Files | kebab-case | `burgs-generator.ts`, `draw-borders.ts` |
| Classes | PascalCase + domain suffix | `BurgModule`, `BiomesModule` |
| Window globals | PascalCase | `window.Burgs`, `window.Biomes` |
| Utility functions | camelCase | `rn`, `minmax`, `createTypedArray` |
| Constants | SCREAMING_SNAKE_CASE | `TYPED_ARRAY_MAX_VALUES` |
| Unit test files | `*.test.ts` (co-located in `src/`) | `commonUtils.test.ts` |
| E2E test files | `*.spec.ts` (in `tests/e2e/`) | `burgs.spec.ts` |
---
## Testing Rules
### Unit Tests (Vitest)
- Co-locate `*.test.ts` files alongside source files in `src/utils/`
- Use `describe` / `it` / `expect` from `"vitest"`
- Default `vitest` command runs these (no browser needed)
- `vitest --config=vitest.browser.config.ts` for browser-context unit tests
### E2E Tests (Playwright)
- Files go in `tests/e2e/` with `.spec.ts` extension
- Always clear cookies and storage in `beforeEach`:
```ts
await context.clearCookies();
await page.evaluate(() => {
localStorage.clear();
sessionStorage.clear();
});
```
- Use seed parameter for deterministic maps: `page.goto("/?seed=test-NAME&width=1280&height=720")`
- **Wait for map generation** before asserting:
```ts
await page.waitForFunction(() => (window as any).mapId !== undefined, {timeout: 60000});
```
- Fixed viewport for consistent rendering: 1280×720 (set in `playwright.config.ts`)
- Access global state via `page.evaluate(() => (window as any).pack)`
- Only Chromium is tested (single browser project in CI)
---
## Code Quality & Style (Biome)
- **Scope**: Biome only lints/formats `src/**/*.ts` — not `public/` legacy JS
- **Formatter**: spaces (not tabs), double quotes for JS strings
- **Organize imports** is auto-applied on save
- Run `npm run lint` to check+fix, `npm run format` to format only
- Rules to always follow:
- `Number.isNaN()` not `isNaN()`
- `parseInt(x, 10)` always with radix
- Template literals over concatenation
- No unused variables or imports (error level)
---
## Development Workflow
- **Dev server**: `npm run dev` (Vite, port 5173)
- **Build**: `npm run build` (tsc typecheck + Vite bundle → `dist/`)
- **E2E in dev**: requires dev server running; CI builds first then previews on port 4173
- **Netlify deploy**: `base` URL switches to `/` when `NETLIFY` env var is set
- **No vitest config file at root** — default Vitest config is inlined in `package.json` scripts; browser config is in `vitest.browser.config.ts`
---
## Common Anti-Patterns to Avoid
- **Do NOT** use plain `Array` for cell data in `pack.cells` — use typed arrays
- **Do NOT** define `var` in modules without `declare global` — all globals must be typed in `src/types/global.ts`
- **Do NOT** add new modules to `public/modules/` — new code goes in `src/modules/` as TypeScript
- **Do NOT** call `isNaN()` — use `Number.isNaN()`
- **Do NOT** call `parseInt()` without a radix
- **Do NOT** skip the `window.ModuleName = new ModuleClass()` registration at the bottom of module files
- **Do NOT** import modules in `src/modules/index.ts` with anything other than a bare side-effect import (`import "./module-name"`)
- **Do NOT** hardcode the land height threshold — use `>= 20` and reference the convention