8.8 KiB
| project_name | user_name | date | sections_completed | |||||||
|---|---|---|---|---|---|---|---|---|---|---|
| Fantasy-Map-Generator | Azgaar | 2026-03-12 |
|
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:
-
Define type and declare global at the top of the file:
declare global { var ModuleName: ModuleClass; } -
Implement as a class:
class ModuleClass { // methods } -
Register on
windowat the bottom of the file (last line):window.ModuleName = new ModuleClass(); -
Import the module in
src/modules/index.ts(side-effect import):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 datagrid— raw grid data before packinggraphWidth,graphHeight— map canvas dimensionssvgWidth,svgHeight— SVG element dimensionsTIME/WARN/ERROR/DEBUG— logging flagsseed— 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:
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:
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,noFallthroughCasesInSwitchall enabled. noEmit: true— TypeScript is typechecking only; Vite handles transpilation.isolatedModules: true— each file must be independently compilable; avoid type-only exports withouttypekeyword.- Module resolution:
bundlermode withallowImportingTsExtensions— use.tsextensions in imports withinsrc/. noExplicitAnyis disabled —anyis permitted where needed (legacy interop).noNonNullAssertionis disabled —!non-null assertions are allowed.- Always use
Number.isNaN()— neverisNaN()(BiomenoGlobalIsNanrule is an error). - Always provide radix to
parseInt()—parseInt(str, 10)(BiomeuseParseIntRadixrule). - Use template literals over string concatenation (Biome
useTemplatewarning). - Import
rnfrom"../utils"for rounding —rn(value, decimals).
Code Organization
src/modules/— generator classes (one domain per file, kebab-case filename)src/renderers/— SVG draw functions (prefixeddraw-, registered aswindow.drawX)src/utils/— pure utility functions exported as named exportssrc/types/— TypeScript type declarations (PackedGraph.ts,global.ts)src/config/— static configuration datapublic/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.tsfiles alongside source files insrc/utils/ - Use
describe/it/expectfrom"vitest" - Default
vitestcommand runs these (no browser needed) vitest --config=vitest.browser.config.tsfor browser-context unit tests
E2E Tests (Playwright)
- Files go in
tests/e2e/with.spec.tsextension - Always clear cookies and storage in
beforeEach: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:
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— notpublic/legacy JS - Formatter: spaces (not tabs), double quotes for JS strings
- Organize imports is auto-applied on save
- Run
npm run lintto check+fix,npm run formatto format only - Rules to always follow:
Number.isNaN()notisNaN()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:
baseURL switches to/whenNETLIFYenv var is set - No vitest config file at root — default Vitest config is inlined in
package.jsonscripts; browser config is invitest.browser.config.ts
Common Anti-Patterns to Avoid
- Do NOT use plain
Arrayfor cell data inpack.cells— use typed arrays - Do NOT define
varin modules withoutdeclare global— all globals must be typed insrc/types/global.ts - Do NOT add new modules to
public/modules/— new code goes insrc/modules/as TypeScript - Do NOT call
isNaN()— useNumber.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.tswith anything other than a bare side-effect import (import "./module-name") - Do NOT hardcode the land height threshold — use
>= 20and reference the convention