Fantasy-Map-Generator/wiki/Data-Model.md
Claude 779e3d03b3
Add comprehensive wiki documentation
Created extensive wiki documentation covering all aspects of the Fantasy Map Generator:

- Home.md: Wiki homepage with overview and navigation
- Getting-Started.md: Complete beginner's guide for users and developers
- Architecture.md: System architecture, design patterns, and technology stack
- Data-Model.md: Detailed data structures and relationships
- Generation-Process.md: Step-by-step map generation pipeline
- Modules-Reference.md: Documentation for all 20+ modules
- Features-and-UI.md: Complete feature list and UI guide
- README.md: Wiki directory overview

The documentation includes:
- High-level architecture and design patterns
- Detailed data structures with typed arrays
- 17-stage generation pipeline with algorithms
- All core modules (generators, renderers, UI, I/O)
- 41+ UI editors and features
- Code examples and usage patterns
- Developer setup and contribution guidelines
- User tutorials and quick start guides

This wiki provides comprehensive documentation for both users wanting to create maps and developers wanting to understand or contribute to the codebase.
2025-11-04 21:37:18 +00:00

17 KiB

Data Model

This document describes the data structures used by the Fantasy Map Generator. Understanding these structures is essential for contributing to the project or building extensions.

Overview

The generator maintains two primary data structures:

  1. grid - The initial Voronoi diagram with terrain and climate data
  2. pack - A packed/filtered version with civilizations and derived features

Both are global objects accessible throughout the application. All map data can be serialized to/from these structures for save/load functionality.

Grid Object

The grid object represents the initial Voronoi diagram created from ~10,000 jittered points. It contains the raw terrain and climate data.

Structure

grid = {
  // Core Voronoi data
  points: [[x1, y1], [x2, y2], ...],  // Array of [x, y] coordinates
  cells: {
    i: Uint32Array,      // Cell indices [0, 1, 2, ...]
    v: Array,            // Vertices indices for each cell
    c: Array,            // Adjacent cell indices
    b: Uint8Array,       // Border cell (1) or not (0)
    f: Uint16Array,      // Feature ID (island/ocean/lake)
    t: Int8Array,        // Cell type: -1=ocean, -2=lake, 1=land
    h: Uint8Array,       // Height (0-100, where 20 is sea level)
    temp: Int8Array,     // Temperature (-128 to 127)
    prec: Uint8Array,    // Precipitation (0-255)
    area: Float32Array,  // Cell area in square pixels
  },

  // Vertices
  vertices: {
    p: [[x, y], ...],    // Vertex coordinates
    v: Array,            // Voronoi vertices
    c: Array             // Adjacent cells to each vertex
  },

  // Seeds (feature centers)
  seeds: {
    i: Uint16Array,      // Seed cell indices
  }
}

Key Properties

cells.i (Index)

  • Unique identifier for each cell
  • Values: 0 to n-1 where n is cell count
  • Used to reference cells throughout the application

cells.h (Height)

  • Elevation value for the cell
  • Range: 0-100 (typically)
  • Convention: 0-20 = water, 20+ = land
  • Higher values = higher elevation

cells.temp (Temperature)

  • Temperature in relative units
  • Range: -128 to 127 (signed 8-bit)
  • Calculated based on latitude and other factors
  • Affects biome assignment

cells.prec (Precipitation)

  • Rainfall/moisture level
  • Range: 0-255 (unsigned 8-bit)
  • Affects river generation and biomes
  • Higher near coasts and prevailing winds

cells.f (Feature ID)

  • Identifies which landmass/ocean/lake the cell belongs to
  • Each contiguous land area gets a unique ID
  • Used for island detection and feature management

cells.t (Type)

  • Quick type classification
  • Values: -2 = lake, -1 = ocean, 0 = coast, 1 = land
  • Used for filtering and quick checks

Grid Methods

The grid doesn't expose many methods directly. Most operations are performed by utility functions in main.js:

// Generate initial grid
generateGrid();

// Get neighboring cells
const neighbors = grid.cells.c[cellId];

// Check if cell is land
const isLand = grid.cells.h[cellId] >= 20;

Pack Object

The pack object is derived from grid after initial generation. It contains only land cells and adds civilization data.

Structure

pack = {
  // Cell data (filtered from grid, only land cells)
  cells: {
    i: Uint32Array,       // Cell indices
    p: Array,             // [x, y] coordinates
    v: Array,             // Vertex indices
    c: Array,             // Adjacent cells
    area: Float32Array,   // Cell area

    // Terrain data (from grid)
    h: Uint8Array,        // Height
    temp: Int8Array,      // Temperature
    prec: Uint8Array,     // Precipitation

    // Water features
    r: Uint16Array,       // River ID (0 = no river)
    fl: Uint16Array,      // Water flux (amount of water flowing)
    conf: Uint8Array,     // River confluence count

    // Biomes & terrain
    biome: Uint8Array,    // Biome type ID

    // Civilization
    s: Uint16Array,       // State ID (0 = neutral)
    culture: Uint16Array, // Culture ID
    religion: Uint16Array, // Religion ID (0 = no religion)
    province: Uint16Array, // Province ID
    burg: Uint16Array,    // Burg ID (0 = no settlement)

    // Infrastructure
    road: Uint16Array,    // Road power (0 = no road)
    crossroad: Uint16Array, // Crossroad value

    // Derived properties
    pop: Float32Array,    // Population density
    harbor: Uint8Array,   // Harbor/port status
  },

  // Vertices
  vertices: {
    p: Array,            // [x, y] coordinates
    c: Array,            // Adjacent cells
    v: Array             // Voronoi data
  },

  // Burgs (settlements)
  burgs: [
    {
      i: Number,         // Unique ID
      cell: Number,      // Cell index where burg is located
      x: Number,         // X coordinate
      y: Number,         // Y coordinate
      name: String,      // Settlement name
      feature: Number,   // Feature (island) ID

      // Political
      state: Number,     // State ID
      capital: Boolean,  // Is state capital

      // Cultural
      culture: Number,   // Culture ID

      // Population
      population: Number,     // Total population
      type: String,          // Settlement type (city, town, etc.)

      // Other
      port: Number,      // Port/harbor value
      citadel: Boolean,  // Has citadel/castle
    }
  ],

  // States (political entities)
  states: [
    {
      i: Number,         // Unique ID (0 = neutral)
      name: String,      // State name
      color: String,     // CSS color code
      capital: Number,   // Capital burg ID

      // Cultural
      culture: Number,   // Dominant culture ID
      religion: Number,  // State religion ID

      // Political
      type: String,      // Government type (Kingdom, Empire, etc.)
      expansionism: Number, // Expansion aggressiveness (0-1)
      form: String,      // "Monarchy", "Republic", etc.

      // Geographic
      area: Number,      // Total area in cells
      cells: Number,     // Number of cells

      // Population
      rural: Number,     // Rural population
      urban: Number,     // Urban population

      // Military
      military: Array,   // Military units

      // Diplomacy
      diplomacy: Array,  // Relations with other states

      // Other
      pole: [x, y],      // Pole of inaccessibility (label position)
      alert: Number,     // Alert level
      alive: Number,     // Is state alive (1) or removed (0)
    }
  ],

  // Cultures
  cultures: [
    {
      i: Number,         // Unique ID
      name: String,      // Culture name
      base: Number,      // Base name generation set
      type: String,      // Culture type (Generic, River, etc.)

      // Geographic
      center: Number,    // Origin cell
      color: String,     // CSS color code

      // Area & population
      area: Number,      // Total area
      cells: Number,     // Number of cells
      rural: Number,     // Rural population
      urban: Number,     // Urban population

      // Cultural traits
      expansionism: Number, // Expansion rate
      shield: String,    // Shield shape for CoA
      code: String,      // Two-letter code
    }
  ],

  // Religions
  religions: [
    {
      i: Number,         // Unique ID (0 = no religion)
      name: String,      // Religion name
      color: String,     // CSS color code
      type: String,      // Religion type (Folk, Organized, etc.)
      form: String,      // Form (Cult, Church, etc.)

      // Origins
      culture: Number,   // Origin culture ID
      center: Number,    // Origin cell

      // Geographic
      area: Number,      // Total area
      cells: Number,     // Number of cells
      rural: Number,     // Rural population
      urban: Number,     // Urban population

      // Deities & beliefs
      deity: String,     // Deity name (if applicable)
      expansion: String, // Expansion strategy
      expansionism: Number, // Expansion rate
      code: String,      // Two-letter code
    }
  ],

  // Rivers
  rivers: [
    {
      i: Number,         // Unique ID
      source: Number,    // Source cell
      mouth: Number,     // Mouth cell
      cells: Array,      // Array of cell indices along river
      length: Number,    // River length
      width: Number,     // River width
      name: String,      // River name
      type: String,      // River type
      parent: Number,    // Parent river (for tributaries)
    }
  ],

  // Features (landmasses, oceans, lakes)
  features: [
    {
      i: Number,         // Unique ID
      land: Boolean,     // Is land (true) or water (false)
      border: Boolean,   // Touches map border
      type: String,      // "island", "ocean", "lake"
      cells: Number,     // Number of cells
      firstCell: Number, // First cell of feature
      group: String,     // Group name (for islands)
      area: Number,      // Total area
      height: Number,    // Average height
    }
  ],

  // Provinces
  provinces: [
    {
      i: Number,         // Unique ID
      state: Number,     // State ID
      name: String,      // Province name
      formName: String,  // Form name (e.g., "Duchy of X")
      color: String,     // CSS color code

      // Capital
      burg: Number,      // Capital burg ID
      center: Number,    // Center cell

      // Geography
      area: Number,      // Total area
      cells: Number,     // Number of cells

      // Population
      rural: Number,     // Rural population
      urban: Number,     // Urban population

      // Other
      pole: [x, y],      // Label position
    }
  ],

  // Markers (map annotations)
  markers: [
    {
      i: Number,         // Unique ID
      type: String,      // Marker type (volcano, monument, etc.)
      x: Number,         // X coordinate
      y: Number,         // Y coordinate
      cell: Number,      // Cell index
      icon: String,      // Icon identifier
      size: Number,      // Icon size
      note: String,      // Associated note text
    }
  ]
}

Biomes Data

The biomesData object defines biome properties:

biomesData = {
  i: [id0, id1, ...],        // Biome IDs
  name: [...],               // Human-readable names
  color: [...],              // Display colors
  habitability: [...],       // How suitable for settlements (0-100)
  iconsDensity: [...],       // Density of relief icons
  icons: [...],              // Icon sets to use
  cost: [...],               // Movement cost multiplier
  biomesMartix: [...]        // Temperature/precipitation mapping
}

Standard Biomes

ID Name Description
1 Marine Ocean biome
2 Hot desert Arid, hot regions
3 Cold desert Arid, cold regions
4 Savanna Grasslands with scattered trees
5 Grassland Temperate grasslands
6 Tropical seasonal forest Wet/dry tropical forest
7 Temperate deciduous forest Moderate climate forests
8 Tropical rainforest Dense, wet jungle
9 Temperate rainforest Wet coastal forests
10 Taiga Boreal forest
11 Tundra Treeless cold regions
12 Glacier Ice and snow
13 Wetland Marshes and swamps

Notes Data

User annotations stored separately:

notes = [
  {
    id: String,          // Unique identifier
    name: String,        // Note title
    legend: String,      // Legend text
  }
]

Map History

Undo/redo system stores state snapshots:

mapHistory = [
  {
    json: String,        // Serialized map state
    options: Object,     // Generation options at time
    version: String      // Generator version
  }
]

Data Relationships

Cell → Civilization Hierarchy

Cell (pack.cells.i[cellId])
  ├─ Burg (pack.cells.burg[cellId] → pack.burgs[burgId])
  ├─ State (pack.cells.s[cellId] → pack.states[stateId])
  ├─ Culture (pack.cells.culture[cellId] → pack.cultures[cultureId])
  ├─ Religion (pack.cells.religion[cellId] → pack.religions[religionId])
  └─ Province (pack.cells.province[cellId] → pack.provinces[provinceId])

State Hierarchy

State (pack.states[stateId])
  ├─ Capital Burg (pack.states[stateId].capital → pack.burgs[burgId])
  ├─ Culture (pack.states[stateId].culture → pack.cultures[cultureId])
  ├─ Religion (pack.states[stateId].religion → pack.religions[religionId])
  ├─ Provinces (pack.provinces.filter(p => p.state === stateId))
  └─ Burgs (pack.burgs.filter(b => b.state === stateId))

River Network

River (pack.rivers[riverId])
  ├─ Source Cell (pack.rivers[riverId].source)
  ├─ Mouth Cell (pack.rivers[riverId].mouth)
  ├─ Path Cells (pack.rivers[riverId].cells[])
  └─ Parent River (pack.rivers[riverId].parent for tributaries)

Data Access Patterns

Finding data for a cell

// Given a cell index
const cellId = 1234;

// Get basic terrain
const height = pack.cells.h[cellId];
const temperature = pack.cells.temp[cellId];
const biome = pack.cells.biome[cellId];

// Get civilization
const stateId = pack.cells.s[cellId];
const cultureId = pack.cells.culture[cellId];
const burgId = pack.cells.burg[cellId];

// Get full objects
const state = pack.states[stateId];
const culture = pack.cultures[cultureId];
const burg = pack.burgs[burgId];

Finding all cells for an entity

// All cells belonging to a state
const stateCells = pack.cells.i.filter(i => pack.cells.s[i] === stateId);

// All cells with a specific biome
const biomeCells = pack.cells.i.filter(i => pack.cells.biome[i] === biomeId);

// All cells with rivers
const riverCells = pack.cells.i.filter(i => pack.cells.r[i] > 0);

Iterating efficiently

// Using typed arrays directly (fastest)
for (let i = 0; i < pack.cells.i.length; i++) {
  const cellId = pack.cells.i[i];
  const height = pack.cells.h[i];
  // Process cell...
}

// Using filter + map (more readable)
const mountainCells = pack.cells.i
  .filter(i => pack.cells.h[i] > 70)
  .map(i => ({
    id: i,
    x: pack.cells.p[i][0],
    y: pack.cells.p[i][1]
  }));

Serialization

Save Format

Maps are saved as JSON with the following structure:

{
  info: {
    version: String,      // Generator version
    description: String,  // Map description
    exportedAt: String,   // Timestamp
    mapName: String,      // Map name
    width: Number,        // Map width
    height: Number,       // Map height
    seed: String          // Random seed
  },
  settings: {},           // Generation options
  mapCoordinates: {},     // Coordinate system
  grid: {},              // Grid data
  pack: {},              // Pack data
  biomesData: {},        // Biome definitions
  notes: [],             // User notes
  nameBases: []          // Name generation data
}

Load Process

When loading a map:

  1. Parse JSON
  2. Restore typed arrays from regular arrays
  3. Run version migration if needed (via versioning.js)
  4. Restore global state
  5. Regenerate derived data if necessary
  6. Render to SVG

Performance Considerations

Memory Usage

Typed arrays provide significant memory savings:

  • Uint8Array: 1 byte per element (0-255)
  • Uint16Array: 2 bytes per element (0-65,535)
  • Int8Array: 1 byte per element (-128-127)
  • Float32Array: 4 bytes per element

For 10,000 cells:

  • Regular array: ~80 KB per property
  • Uint8Array: ~10 KB per property
  • 80-90% memory reduction

Access Speed

Typed arrays provide:

  • Faster iteration (predictable memory layout)
  • Better cache utilization
  • Optimized by JavaScript engines

Trade-offs

Pros:

  • Excellent memory efficiency
  • Fast array operations
  • Type safety for numeric data

Cons:

  • Less flexible than objects
  • Parallel arrays can be confusing
  • Requires index synchronization

Extending the Data Model

When adding new data:

  1. Choose the right location

    • Cell-level: Add to pack.cells.*
    • Entity-level: Add new array like pack.newEntities[]
  2. Use appropriate types

    • IDs: Uint16Array or Uint32Array
    • Small numbers: Uint8Array or Int8Array
    • Decimals: Float32Array
    • Strings/objects: Regular arrays
  3. Update serialization

    • Add to save format
    • Add to load process
    • Handle versioning in versioning.js
  4. Consider relationships

    • How does it relate to existing data?
    • What indices/lookups are needed?
    • How will it be queried?

Example: Adding a new cell property

// 1. Add to pack.cells
pack.cells.myProperty = new Uint8Array(pack.cells.i.length);

// 2. Initialize during generation
function generateMyProperty() {
  for (let i = 0; i < pack.cells.i.length; i++) {
    pack.cells.myProperty[i] = calculateValue(i);
  }
}

// 3. Update save/load
function saveMap() {
  const data = {
    // ... existing data
    myProperty: Array.from(pack.cells.myProperty)
  };
}

function loadMap(data) {
  // ... load other data
  pack.cells.myProperty = new Uint8Array(data.myProperty);
}

// 4. Use in rendering/editing
function renderMyProperty() {
  d3.select('#myLayer').selectAll('path')
    .data(pack.cells.i)
    .attr('fill', i => getColor(pack.cells.myProperty[i]));
}

Reference Documentation

For more details on specific aspects: