Fantasy-Map-Generator/_bmad-output/project-context.md
Azgaar 70e3eea4d1 feat: Introduce TextureAtlasLayer for efficient texture management and rendering
refactor: Streamline WebGL2LayerClass methods and remove unused code
refactor: Consolidate relief icon rendering logic and remove benchmarks
2026-03-12 20:17:17 +01:00

10 KiB
Raw Permalink 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)

Code Style Principles (enforced on all generated code)

Concise — no noise:

  • No comments unless the why is genuinely non-obvious from reading the code. Never narrate what the code does.
  • No JSDoc/TSDoc on internal functions. Exported public API only if callers are outside src/.
  • Good names eliminate comments. If you feel a comment is needed, improve the name first.

Clean abstractions — no leaks:

  • Each abstraction fully owns its concern. Callers must not need internal implementation details to use it correctly.
  • No thin wrapper classes that just re-expose another class's internals.
  • Keep call depth shallow: if tracing a feature requires more than two levels of indirection, refactor toward directness.

No academic over-engineering:

  • No design patterns (Factory, Strategy, Observer, etc.) unless the problem concretely requires the flexibility they provide.
  • No wrapper objects or extra indirection layers that add zero behavior.
  • Functions take ≤ 3 positional parameters. Use a plain options object only when fields are genuinely optional and vary by caller.

Minimal artifacts:

  • Only create files directly required by the story's acceptance criteria.
  • No per-domain helper/utils files unless there are ≥ 3 reusable functions with multiple callers.
  • No barrel re-export files unless there is an actual cross-module consumer today.

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