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

8.8 KiB
Raw Blame History

project_name user_name date sections_completed
Fantasy-Map-Generator Azgaar 2026-03-12
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:

    declare global {
      var ModuleName: ModuleClass;
    }
    
  2. Implement as a class:

    class ModuleClass {
      // methods
    }
    
  3. Register on window at the bottom of the file (last line):

    window.ModuleName = new ModuleClass();
    
  4. 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 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:

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, 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:
    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 — 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