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.
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:
grid- The initial Voronoi diagram with terrain and climate datapack- 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:
0ton-1wherenis 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:
-128to127(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:
- Parse JSON
- Restore typed arrays from regular arrays
- Run version migration if needed (via
versioning.js) - Restore global state
- Regenerate derived data if necessary
- 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:
-
Choose the right location
- Cell-level: Add to
pack.cells.* - Entity-level: Add new array like
pack.newEntities[]
- Cell-level: Add to
-
Use appropriate types
- IDs: Uint16Array or Uint32Array
- Small numbers: Uint8Array or Int8Array
- Decimals: Float32Array
- Strings/objects: Regular arrays
-
Update serialization
- Add to save format
- Add to load process
- Handle versioning in
versioning.js
-
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:
- Architecture - System design and patterns
- Generation Process - How data is created
- Modules Reference - Module APIs