mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-17 17:51:24 +01:00
more porting work
This commit is contained in:
parent
37391c8e8b
commit
7f31969f50
38 changed files with 3673 additions and 463 deletions
246
procedural/docs/CALL_PATTERN.md
Normal file
246
procedural/docs/CALL_PATTERN.md
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
# Call Pattern Issues in Engine Refactoring
|
||||
|
||||
## Overview
|
||||
|
||||
During the refactoring of the Fantasy Map Generator from a browser-dependent application to a headless engine, we've encountered systematic issues with how modules are called and how they access their dependencies. This document catalogues these patterns and provides a systematic approach to identifying and fixing them.
|
||||
|
||||
## The Problem
|
||||
|
||||
The original codebase used global variables and browser-specific APIs. The refactored engine uses dependency injection, but there are mismatches between:
|
||||
|
||||
1. **Function signatures** - What parameters functions expect
|
||||
2. **Function calls** - What parameters are actually passed
|
||||
3. **Data access patterns** - How modules access configuration and utilities
|
||||
|
||||
## Common Anti-Patterns Found
|
||||
|
||||
### 1. Config Nesting Mismatch
|
||||
|
||||
**Problem**: Modules expect config properties at the root level, but they're nested under specific sections.
|
||||
|
||||
**Example**:
|
||||
```javascript
|
||||
// ❌ Module expects:
|
||||
config.culturesInput
|
||||
config.culturesInSetNumber
|
||||
|
||||
// ✅ But config actually has:
|
||||
config.cultures.culturesInput
|
||||
config.cultures.culturesInSetNumber
|
||||
```
|
||||
|
||||
**Pattern**: `config.{property}` vs `config.{section}.{property}`
|
||||
|
||||
**Files affected**: `cultures-generator.js`, `biomes.js`, `river-generator.js`
|
||||
|
||||
### 2. Missing Config Parameter
|
||||
|
||||
**Problem**: Modules expect full `config` object but are passed only a subsection.
|
||||
|
||||
**Example**:
|
||||
```javascript
|
||||
// ❌ Incorrect call:
|
||||
Biomes.define(pack, grid, config.biomes, Utils)
|
||||
|
||||
// ✅ Correct call (module needs config.debug):
|
||||
Biomes.define(pack, grid, config, Utils)
|
||||
```
|
||||
|
||||
**Pattern**: Modules need `config.debug` but receive `config.{section}`
|
||||
|
||||
**Files affected**: `biomes.js`, `river-generator.js`
|
||||
|
||||
### 3. Missing Module Dependencies
|
||||
|
||||
**Problem**: Function signature doesn't include `modules` parameter but code tries to access module dependencies.
|
||||
|
||||
**Example**:
|
||||
```javascript
|
||||
// ❌ Function signature:
|
||||
function generate(pack, grid, config, utils) {
|
||||
// Code tries to use Names module
|
||||
utils.Names.getNameBases() // ❌ Names not in utils
|
||||
}
|
||||
|
||||
// ✅ Correct signature:
|
||||
function generate(pack, grid, config, utils, modules) {
|
||||
const { Names } = modules;
|
||||
Names.getNameBases() // ✅ Correct access
|
||||
}
|
||||
```
|
||||
|
||||
**Files affected**: `cultures-generator.js`
|
||||
|
||||
### 4. Missing Parameter Propagation
|
||||
|
||||
**Problem**: Functions call other functions without passing required parameters.
|
||||
|
||||
**Example**:
|
||||
```javascript
|
||||
// ❌ Missing parameters:
|
||||
Lakes.defineClimateData(h)
|
||||
|
||||
// ✅ Should pass all required params:
|
||||
Lakes.defineClimateData(pack, grid, h, config, utils)
|
||||
```
|
||||
|
||||
**Files affected**: `river-generator.js`, `features.js`
|
||||
|
||||
### 5. Global Variable References
|
||||
|
||||
**Problem**: Functions reference global variables that don't exist in headless environment.
|
||||
|
||||
**Example**:
|
||||
```javascript
|
||||
// ❌ References undefined globals:
|
||||
function clipPoly(points, secure = 0) {
|
||||
return polygonclip(points, [0, 0, graphWidth, graphHeight], secure);
|
||||
// ^^^^^^^^^^^ ^^^^^^^^^^^^ undefined
|
||||
}
|
||||
|
||||
// ✅ Get from config:
|
||||
function clipPoly(points, config, secure = 0) {
|
||||
const graphWidth = config.graph.width || 1000;
|
||||
const graphHeight = config.graph.height || 1000;
|
||||
return polygonclip(points, [0, 0, graphWidth, graphHeight], secure);
|
||||
}
|
||||
```
|
||||
|
||||
**Files affected**: `commonUtils.js`
|
||||
|
||||
### 6. Context-Aware Wrappers
|
||||
|
||||
**Problem**: Utility functions expect parameters that aren't available in calling context.
|
||||
|
||||
**Example**:
|
||||
```javascript
|
||||
// ❌ isLand expects pack but called without it:
|
||||
neighbors[cellId].filter(isLand)
|
||||
|
||||
// ✅ Create context-aware wrapper or pass explicitly:
|
||||
neighbors[cellId].filter(i => isLand(i, pack))
|
||||
```
|
||||
|
||||
**Files affected**: `features.js`
|
||||
|
||||
## Systematic Detection Strategy
|
||||
|
||||
### 1. Function Signature Analysis
|
||||
|
||||
For each exported function, check:
|
||||
```bash
|
||||
# Find function exports
|
||||
grep -n "export.*function\|export const.*=" src/engine/modules/*.js
|
||||
|
||||
# Check what parameters they expect vs receive
|
||||
```
|
||||
|
||||
### 2. Config Access Pattern Audit
|
||||
|
||||
```bash
|
||||
# Find config property access
|
||||
grep -rn "config\." src/engine/modules/ | grep -v "config\.debug"
|
||||
|
||||
# Check if properties exist in config structure
|
||||
```
|
||||
|
||||
### 3. Module Dependency Check
|
||||
|
||||
```bash
|
||||
# Find modules object usage
|
||||
grep -rn "modules\." src/engine/modules/
|
||||
|
||||
# Find utils object access to modules
|
||||
grep -rn "utils\.[A-Z]" src/engine/modules/
|
||||
```
|
||||
|
||||
### 4. Global Reference Detection
|
||||
|
||||
```bash
|
||||
# Find potential global references
|
||||
grep -rn "\b[A-Z_][A-Z_]*\b" src/engine/ | grep -v "import\|export\|const\|let\|var"
|
||||
```
|
||||
|
||||
### 5. Function Call Parameter Mismatch
|
||||
|
||||
```bash
|
||||
# Find function calls and compare with signatures
|
||||
grep -rn "\.generate\|\.define\|\.markup" src/engine/main.js
|
||||
```
|
||||
|
||||
## Systematic Fix Pattern
|
||||
|
||||
### Step 1: Audit Function Signatures
|
||||
1. List all exported functions in modules
|
||||
2. Document expected parameters
|
||||
3. Check all call sites
|
||||
4. Identify mismatches
|
||||
|
||||
### Step 2: Config Structure Mapping
|
||||
1. Document actual config structure from `config-builder.js`
|
||||
2. Find all `config.{property}` accesses in modules
|
||||
3. Map correct paths (`config.section.property`)
|
||||
|
||||
### Step 3: Dependency Injection Fix
|
||||
1. Ensure all functions receive required parameters
|
||||
2. Add `modules` parameter where needed
|
||||
3. Update all call sites to pass correct parameters
|
||||
|
||||
### Step 4: Global Reference Elimination
|
||||
1. Find all global variable references
|
||||
2. Determine correct source (config, utils, passed parameters)
|
||||
3. Update function signatures if needed
|
||||
|
||||
## Files Requiring Systematic Review
|
||||
|
||||
### High Priority (Core Generation Flow)
|
||||
- `src/engine/main.js` - All module calls
|
||||
- `src/engine/modules/biomes.js`
|
||||
- `src/engine/modules/cultures-generator.js`
|
||||
- `src/engine/modules/river-generator.js`
|
||||
- `src/engine/modules/burgs-and-states.js`
|
||||
- `src/engine/modules/features.js`
|
||||
|
||||
### Medium Priority (Utilities)
|
||||
- `src/engine/utils/commonUtils.js`
|
||||
- `src/engine/utils/cell.js`
|
||||
- `src/engine/modules/lakes.js`
|
||||
|
||||
### Low Priority (Supporting Modules)
|
||||
- `src/engine/modules/provinces-generator.js`
|
||||
- `src/engine/modules/religions-generator.js`
|
||||
- `src/engine/modules/military-generator.js`
|
||||
- All other utility modules
|
||||
|
||||
## Verification Checklist
|
||||
|
||||
For each module function:
|
||||
- [ ] Function signature matches all call sites
|
||||
- [ ] All required parameters are passed
|
||||
- [ ] Config properties accessed via correct path
|
||||
- [ ] No global variable references
|
||||
- [ ] Module dependencies properly injected
|
||||
- [ ] Error handling for missing dependencies
|
||||
|
||||
## Example Systematic Fix
|
||||
|
||||
```javascript
|
||||
// 1. Document current signature
|
||||
function someModule(pack, config, utils) { ... }
|
||||
|
||||
// 2. Document all call sites
|
||||
someModule(pack, config.section, utils) // ❌ Wrong config
|
||||
someModule(pack, grid, config, utils) // ❌ Missing grid param
|
||||
|
||||
// 3. Determine correct signature
|
||||
function someModule(pack, grid, config, utils, modules) { ... }
|
||||
|
||||
// 4. Update all call sites
|
||||
someModule(pack, grid, config, utils, modules) // ✅ Correct
|
||||
|
||||
// 5. Update internal property access
|
||||
// config.property → config.section.property
|
||||
// utils.Module → modules.Module
|
||||
```
|
||||
|
||||
This systematic approach will help identify and fix all parameter passing issues before they cause runtime errors.
|
||||
242
procedural/docs/Data_Mismatch_Task_Activity.md
Normal file
242
procedural/docs/Data_Mismatch_Task_Activity.md
Normal file
|
|
@ -0,0 +1,242 @@
|
|||
# Data Mismatch Task Activity Log
|
||||
|
||||
This file logs all completed activities for fixing data mismatches in the Fantasy Map Generator.
|
||||
|
||||
## Task 1: Add Property Checks to All Modules
|
||||
|
||||
**Status**: Completed
|
||||
**Date**: 2025-08-05
|
||||
|
||||
### Files Modified:
|
||||
|
||||
#### 1. src/engine/modules/heightmap-generator.js
|
||||
**Changes made**: Added property validation checks at the start of the `generate` function
|
||||
- Check for `graph` object and `graph.cells` structure
|
||||
- Check for `config.heightmap.templateId`
|
||||
- Check for `config.debug` section
|
||||
|
||||
#### 2. src/engine/modules/lakes.js
|
||||
**Changes made**: Added property validation checks at the start of the `detectCloseLakes` function
|
||||
- Check for `pack.cells` and `pack.features` structures
|
||||
- Check for `pack.cells.c` (neighbors) and `pack.cells.f` (features)
|
||||
- Check for `heights` array
|
||||
|
||||
#### 3. src/engine/modules/burgs-and-states.js
|
||||
**Changes made**: Added property validation checks at the start of the `generate` function
|
||||
- Check for `pack.cells.culture` from Cultures module
|
||||
- Check for `pack.cells.s` (suitability) from Cell ranking
|
||||
- Check for `pack.cultures` from Cultures module
|
||||
- Check for `config.statesNumber`
|
||||
|
||||
#### 4. src/engine/modules/cultures-generator.js
|
||||
**Changes made**: Added property validation checks at the start of the `generate` function
|
||||
- Check for `pack.cells.s` (suitability) from Cell ranking
|
||||
- Check for `config.culturesInput` and `config.culturesInSetNumber`
|
||||
- Check for `config.debug` section
|
||||
|
||||
#### 5. src/engine/modules/biomes.js
|
||||
**Changes made**: Added property validation checks at the start of the `define` function
|
||||
- Check for `pack.cells.h` (heights) from heightmap processing
|
||||
- Check for `grid.cells.temp` and `grid.cells.prec` from geography module
|
||||
- Check for `pack.cells.g` (grid reference) from pack generation
|
||||
- Check for `config.debug` section
|
||||
|
||||
#### 6. src/engine/modules/features.js
|
||||
**Changes made**: Added property validation checks to two functions:
|
||||
- `markupGrid` function: Check for `grid.cells.h` (heights), `grid.cells.c` (neighbors), and `config.debug`
|
||||
- `markupPack` function: Check for `pack.cells.h` (heights), `pack.cells.c` (neighbors), and `grid.features`
|
||||
|
||||
#### 7. src/engine/modules/river-generator.js
|
||||
**Changes made**: Added property validation checks at the start of the `generate` function
|
||||
- Check for `pack.cells.h` (heights) from heightmap processing
|
||||
- Check for `pack.cells.t` (distance field) from features module
|
||||
- Check for `pack.features` from features module
|
||||
- Check for `modules.Lakes` dependency
|
||||
- Check for `config.debug` section
|
||||
|
||||
#### 8. src/engine/modules/religions-generator.js
|
||||
**Changes made**: Added property validation checks at the start of the `generate` function
|
||||
- Check for `pack.cells.culture` from Cultures module
|
||||
- Check for `pack.cells.state` from BurgsAndStates module
|
||||
- Check for `pack.cultures` from Cultures module
|
||||
- Check for `config.religionsNumber`
|
||||
- Check for `config.debug` section
|
||||
|
||||
#### 9. src/engine/modules/provinces-generator.js
|
||||
**Changes made**: Added property validation checks at the start of the `generate` function
|
||||
- Check for `pack.cells.state` from BurgsAndStates module
|
||||
- Check for `pack.cells.burg` from BurgsAndStates module
|
||||
- Check for `pack.states` from BurgsAndStates module
|
||||
- Check for `pack.burgs` from BurgsAndStates module
|
||||
- Check for `config.debug` section
|
||||
|
||||
#### 10. src/engine/modules/routes-generator.js
|
||||
**Changes made**: Added property validation checks at the start of the `generate` function
|
||||
- Check for `pack.cells.burg` from BurgsAndStates module
|
||||
- Check for `pack.burgs` from BurgsAndStates module
|
||||
- Check for `pack.cells.h` (heights) from heightmap processing
|
||||
- Check for `pack.cells.t` (distance field) from features module
|
||||
|
||||
#### 11. src/engine/modules/military-generator.js
|
||||
**Changes made**: Added property validation checks at the start of the `generate` function
|
||||
- Check for `pack.cells.state` from BurgsAndStates module
|
||||
- Check for `pack.states` from BurgsAndStates module
|
||||
- Check for `pack.burgs` from BurgsAndStates module
|
||||
- Check for `config.debug` section
|
||||
|
||||
### Summary:
|
||||
Added property validation checks to 11 critical engine modules. Each module now validates required dependencies and configuration sections at startup, providing clear error messages when properties are missing. This implements Fix 1 from the Data_Mismatch_Tasks.md plan - adding simple property checks to fail fast with helpful error messages.
|
||||
|
||||
All checks follow the pattern:
|
||||
```javascript
|
||||
// Check required properties exist
|
||||
if (!requiredProperty) {
|
||||
throw new Error("ModuleName requires requiredProperty from DependencyModule");
|
||||
}
|
||||
```
|
||||
|
||||
This ensures clear dependency tracking and early error detection when modules are called with missing prerequisites.
|
||||
|
||||
## Task 2: Update Config Validator for Missing Fields
|
||||
|
||||
**Status**: Completed
|
||||
**Date**: 2025-08-05
|
||||
|
||||
### Files Modified:
|
||||
|
||||
#### 1. src/viewer/config-validator.js
|
||||
**Changes made**: Added simple required fields validation as specified in the task
|
||||
|
||||
**Functions added**:
|
||||
- `validateRequiredFields(config, result)` - Validates specific required fields for modules
|
||||
- `getCultureSetMax(culturesSet)` - Helper function to get maximum cultures for culture sets
|
||||
|
||||
**Required fields validated**:
|
||||
- `cultures.culturesInSetNumber` - Validates based on culturesSet maximum
|
||||
- `rivers.cellsCount` - Validates against graph.cellsDesired or defaults to 10000
|
||||
|
||||
**Implementation**: Added simple check for missing fields with warnings that show what the default values would be.
|
||||
|
||||
### Summary:
|
||||
Updated the existing config validator to implement **Fix 2** from the Data_Mismatch_Tasks.md plan by adding the specific required fields validation as shown in the task example. The validator now checks for missing `cultures.culturesInSetNumber` and `rivers.cellsCount` fields and provides warnings when they are missing.
|
||||
|
||||
## Task 3: Update Documentation with Property Timeline
|
||||
|
||||
**Status**: Completed
|
||||
**Date**: 2025-08-05
|
||||
|
||||
### Files Modified:
|
||||
|
||||
#### 1. docs/FMG Data Model.md
|
||||
**Changes made**: Added comprehensive Property Availability Timeline section as specified in the task
|
||||
|
||||
**New section added**: "Property Availability Timeline"
|
||||
- **Grid Properties section**: Documents when each grid property becomes available during generation
|
||||
- **Pack Properties section**: Documents when each pack property becomes available during generation
|
||||
- **Module Execution Flow**: Added mermaid flowchart diagram showing complete module execution sequence
|
||||
|
||||
**Properties documented**:
|
||||
- Grid properties: `cells.h`, `cells.f`, `cells.t`, `cells.temp`, `cells.prec`
|
||||
- Pack properties: `cells.h`, `cells.f`, `cells.t`, `cells.fl`, `cells.r`, `cells.biome`, `cells.s`, `cells.pop`, `cells.culture`, `cells.burg`, `cells.state`, `cells.religion`, `cells.province`
|
||||
|
||||
**Mermaid diagram**: Visual flowchart showing the complete generation pipeline from initial grid through all modules to final map data, with annotations showing what properties each module adds.
|
||||
|
||||
### Summary:
|
||||
Implemented **Fix 3** from the Data_Mismatch_Tasks.md plan by adding the Property Availability Timeline section to the existing documentation. This addresses the "Pack/Grid structure differences" issue by clearly documenting when each property becomes available during the generation process.
|
||||
|
||||
The documentation now provides:
|
||||
1. **Clear reference for developers** - Shows exactly when each property is available
|
||||
2. **Module dependency tracking** - Visual flow shows which modules depend on others
|
||||
3. **Pack vs Grid clarification** - Distinguishes between grid (coarse mesh) and pack (refined mesh) properties
|
||||
4. **Complete generation pipeline** - Mermaid diagram shows the full execution flow from main.js
|
||||
|
||||
This helps developers understand data availability and prevents undefined reference errors by showing the exact timeline of when properties are added to the data structures.
|
||||
|
||||
## Task 4: Add Requirement Comments to All Modules
|
||||
|
||||
**Status**: Completed
|
||||
**Date**: 2025-08-05
|
||||
|
||||
### Files Modified:
|
||||
|
||||
#### 1. src/engine/modules/heightmap-generator.js
|
||||
**Changes made**: Added JSDoc-style requirement comment block at the top of the `generate` function
|
||||
- **REQUIRES**: graph.cells, config.heightmap.templateId, config.debug
|
||||
- **PROVIDES**: grid.cells.h (height values)
|
||||
|
||||
#### 2. src/engine/modules/lakes.js
|
||||
**Changes made**: Added JSDoc-style requirement comment block at the top of the `detectCloseLakes` function
|
||||
- **REQUIRES**: pack.cells, pack.features, pack.cells.c, pack.cells.f, heights array
|
||||
- **PROVIDES**: Updated pack.features with closed property
|
||||
|
||||
#### 3. src/engine/modules/features.js
|
||||
**Changes made**: Added JSDoc-style requirement comment blocks to both main functions
|
||||
- **markupGrid REQUIRES**: grid.cells.h, grid.cells.c, config.debug
|
||||
- **markupGrid PROVIDES**: grid.cells.f, grid.cells.t, grid.features
|
||||
- **markupPack REQUIRES**: pack.cells.h, pack.cells.c, grid.features
|
||||
- **markupPack PROVIDES**: pack.cells.f, pack.cells.t, pack.features
|
||||
|
||||
#### 4. src/engine/modules/biomes.js
|
||||
**Changes made**: Added JSDoc-style requirement comment block at the top of the `define` function
|
||||
- **REQUIRES**: pack.cells.h, grid.cells.temp, grid.cells.prec, pack.cells.g, config.debug
|
||||
- **PROVIDES**: pack.cells.biome
|
||||
|
||||
#### 5. src/engine/modules/cultures-generator.js
|
||||
**Changes made**: Added JSDoc-style requirement comment block at the top of the `generate` function
|
||||
- **REQUIRES**: pack.cells.s, config.culturesInput, config.culturesInSetNumber
|
||||
- **PROVIDES**: pack.cells.culture, pack.cultures
|
||||
|
||||
#### 6. src/engine/modules/burgs-and-states.js
|
||||
**Changes made**: Added JSDoc-style requirement comment block at the top of the `generate` function
|
||||
- **REQUIRES**: pack.cells.culture, pack.cells.s, pack.cultures, config.statesNumber
|
||||
- **PROVIDES**: pack.burgs, pack.states, pack.cells.burg, pack.cells.state
|
||||
|
||||
#### 7. src/engine/modules/river-generator.js
|
||||
**Changes made**: Added JSDoc-style requirement comment block at the top of the `generate` function
|
||||
- **REQUIRES**: pack.cells.h, pack.cells.t, pack.features, modules.Lakes, config.debug
|
||||
- **PROVIDES**: pack.cells.fl, pack.cells.r, pack.cells.conf
|
||||
|
||||
#### 8. src/engine/modules/religions-generator.js
|
||||
**Changes made**: Added JSDoc-style requirement comment block at the top of the `generate` function
|
||||
- **REQUIRES**: pack.cells.culture, pack.cells.state, pack.cultures, config.religionsNumber, config.debug
|
||||
- **PROVIDES**: pack.cells.religion, pack.religions
|
||||
|
||||
#### 9. src/engine/modules/provinces-generator.js
|
||||
**Changes made**: Added JSDoc-style requirement comment block at the top of the `generate` function
|
||||
- **REQUIRES**: pack.cells.state, pack.cells.burg, pack.states, pack.burgs, config.debug
|
||||
- **PROVIDES**: pack.cells.province, pack.provinces
|
||||
|
||||
#### 10. src/engine/modules/routes-generator.js
|
||||
**Changes made**: Added JSDoc-style requirement comment block at the top of the `generate` function
|
||||
- **REQUIRES**: pack.cells.burg, pack.burgs, pack.cells.h, pack.cells.t
|
||||
- **PROVIDES**: pack.routes, pack.cells.routes
|
||||
|
||||
#### 11. src/engine/modules/military-generator.js
|
||||
**Changes made**: Added JSDoc-style requirement comment block at the top of the `generate` function
|
||||
- **REQUIRES**: pack.cells.state, pack.states, pack.burgs, config.debug
|
||||
- **PROVIDES**: pack.states[].military
|
||||
|
||||
### Summary:
|
||||
Implemented **Fix 4** from the Data_Mismatch_Tasks.md plan by adding requirement comments to all 11 major engine modules. Each module now has clear JSDoc-style documentation at the top of its main function showing:
|
||||
|
||||
1. **Self-documenting modules** - Each module clearly states what it requires and provides
|
||||
2. **No runtime overhead** - Comments are compile-time only and don't affect performance
|
||||
3. **Clear for developers** - Easy to understand dependencies at a glance
|
||||
4. **Dependency tracking** - Shows exact relationships between modules
|
||||
|
||||
The comment format follows the task specification:
|
||||
```javascript
|
||||
/**
|
||||
* Module description
|
||||
*
|
||||
* REQUIRES:
|
||||
* - dependency1 (from source module)
|
||||
* - dependency2 (from source module)
|
||||
*
|
||||
* PROVIDES:
|
||||
* - output1 (what this module adds)
|
||||
* - output2 (what this module adds)
|
||||
*/
|
||||
```
|
||||
|
||||
This addresses the "Module dependencies" issue by making all dependencies explicit and self-documenting, complementing the runtime property checks from Task 1.
|
||||
177
procedural/docs/Data_Mismatch_Tasks.md
Normal file
177
procedural/docs/Data_Mismatch_Tasks.md
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
|
||||
# Simplified Plan to Fix Data Mismatches in Fantasy Map Generator
|
||||
|
||||
This document outlines a practical plan to address the four common data mismatches identified in the MAP_GENERATION_TRACE.md:
|
||||
|
||||
1. **Modules expecting properties that don't exist yet**
|
||||
2. **Config sections missing expected fields**
|
||||
3. **Pack/Grid structure differences**
|
||||
4. **Module dependencies**
|
||||
|
||||
## Problem Analysis
|
||||
|
||||
### 1. Modules Expecting Properties That Don't Exist Yet
|
||||
|
||||
**Current Issue**: Modules access properties like `cells.culture` before the Cultures module has run, causing undefined references and potential crashes.
|
||||
|
||||
**Root Cause**: Modules don't check if required properties exist before accessing them.
|
||||
|
||||
### 2. Config Sections Missing Expected Fields
|
||||
|
||||
**Current Issue**: Modules expect certain configuration fields that may not be present, even after validation.
|
||||
|
||||
**Root Cause**: The existing config validator may not catch all missing fields that modules expect.
|
||||
|
||||
### 3. Pack/Grid Structure Differences
|
||||
|
||||
**Current Issue**: Pack is a refined version of grid, but modules sometimes confuse which structure they're working with.
|
||||
|
||||
**Root Cause**: Similar naming and structure between pack and grid, but different levels of detail and available properties.
|
||||
|
||||
### 4. Module Dependencies
|
||||
|
||||
**Current Issue**: Some modules require data from other modules (e.g., Rivers needs Lakes, Cultures needs Names) but these dependencies aren't formally tracked.
|
||||
|
||||
**Root Cause**: No explicit dependency management system; modules assume previous modules have run successfully.
|
||||
|
||||
## Fix Plan
|
||||
|
||||
### Fix 1: Add Simple Property Checks to Each Module
|
||||
|
||||
Instead of complex wrappers or validators, add simple checks at the start of each module:
|
||||
|
||||
**Example for burgs-and-states.js:**
|
||||
```javascript
|
||||
export const generate = (pack, grid, config, utils) => {
|
||||
// Check required properties exist
|
||||
if (!pack.cells.culture) {
|
||||
throw new Error("BurgsAndStates module requires cells.culture from Cultures module");
|
||||
}
|
||||
if (!pack.cells.s) {
|
||||
throw new Error("BurgsAndStates module requires cells.s (suitability) from Cell ranking");
|
||||
}
|
||||
|
||||
// Continue with existing code...
|
||||
}
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Clear error messages
|
||||
- No complex infrastructure
|
||||
- Easy to add to existing modules
|
||||
- Fail fast with helpful information
|
||||
|
||||
### Fix 2: Enhance Existing Config Validator
|
||||
|
||||
Update the existing `src/viewer/config-validator.js` to ensure all required fields are present:
|
||||
|
||||
```javascript
|
||||
// Add to existing validator
|
||||
const requiredFields = {
|
||||
'cultures.culturesInSetNumber': (config) => {
|
||||
// Ensure this field exists based on culturesSet
|
||||
const maxCultures = getCultureSetMax(config.cultures.culturesSet);
|
||||
return maxCultures;
|
||||
},
|
||||
'rivers.cellsCount': (config) => {
|
||||
// Ensure this matches the actual cell count
|
||||
return config.graph.cellsDesired || 10000;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Uses existing infrastructure
|
||||
- No duplication
|
||||
- Config is complete before engine runs
|
||||
|
||||
### Fix 3: Document Property Timeline in Existing Docs
|
||||
|
||||
Add a section to the existing `docs/FMG Data Model.md`:
|
||||
|
||||
```markdown
|
||||
## Property Availability Timeline
|
||||
|
||||
Properties are added to grid/pack progressively:
|
||||
|
||||
### Grid Properties (coarse mesh ~10K cells)
|
||||
- `cells.h` - Available after: heightmap module
|
||||
- `cells.t` - Available after: features module
|
||||
- `cells.temp` - Available after: geography module
|
||||
- `cells.prec` - Available after: geography module
|
||||
|
||||
### Pack Properties (refined mesh)
|
||||
- `cells.biome` - Available after: biomes module
|
||||
- `cells.s` - Available after: cell ranking
|
||||
- `cells.pop` - Available after: cell ranking
|
||||
- `cells.culture` - Available after: cultures module
|
||||
- `cells.state` - Available after: states module
|
||||
- `cells.burg` - Available after: burgs module
|
||||
- `cells.religion` - Available after: religions module
|
||||
- `cells.province` - Available after: provinces module
|
||||
```
|
||||
|
||||
+ Add mermaid flow diagram
|
||||
|
||||
**Benefits:**
|
||||
- Uses existing documentation
|
||||
- Clear reference for developers
|
||||
- No new files or folders
|
||||
|
||||
### Fix 4: Add Module Requirements as Comments
|
||||
|
||||
At the top of each module, clearly document what it requires:
|
||||
|
||||
```javascript
|
||||
// src/engine/modules/burgs-and-states.js
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Generates burgs (settlements) and states (political entities)
|
||||
*
|
||||
* REQUIRES:
|
||||
* - pack.cells.culture (from cultures module)
|
||||
* - pack.cells.s (from cell ranking)
|
||||
* - pack.cultures (from cultures module)
|
||||
*
|
||||
* PROVIDES:
|
||||
* - pack.burgs
|
||||
* - pack.states
|
||||
* - pack.cells.burg
|
||||
* - pack.cells.state
|
||||
*/
|
||||
export const generate = (pack, grid, config, utils) => {
|
||||
// ... module code
|
||||
}
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Self-documenting
|
||||
- No runtime overhead
|
||||
- Clear for developers
|
||||
|
||||
### Optional: Debug Mode for Dependency Checking
|
||||
|
||||
Add to `src/engine/main.js`:
|
||||
|
||||
```javascript
|
||||
// Only if debug flag is set
|
||||
if (config.debug.CHECK_DEPENDENCIES) {
|
||||
// Simple property existence checks before each module
|
||||
if (!pack.cells.culture && moduleNeedsCulture) {
|
||||
console.error("Missing required property: cells.culture");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Notes for tasks.
|
||||
|
||||
Please log all completed activities in docs/Data_Mismatch_Task_Activity.md.
|
||||
For each task, log the files you altered witrh the changes made in that file.
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
1. **Task 1**: Add property checks to all modules
|
||||
2. **Task 2**: Update config validator for missing fields
|
||||
3. **Task 3**: Update documentation with property timeline, including a mermaid flow diagram
|
||||
4. **Task 4**: Add requirement comments to all modules
|
||||
366
procedural/docs/FMG Data Model.md
Normal file
366
procedural/docs/FMG Data Model.md
Normal file
|
|
@ -0,0 +1,366 @@
|
|||
**FMG data model** is poorly defined, inconsistent and not documented in the codebase. This page is the first and the only attempt to document it. Once everything is documented, it can be used for building a new consistent model.
|
||||
|
||||
FMG exposes all its data into the global namespace. The global namespace is getting polluted and it can cause conflicts with 3rd party extensions. Meanwhile it simplifies debugging and allows users to run custom JS code in dev console to alter the tool behavior.
|
||||
|
||||
# Basic objects
|
||||
FMG has two meta-objects storing most of the map data:
|
||||
* `grid` contains map data before _repacking_
|
||||
* `pack` contains map data after _repacking_
|
||||
|
||||
Repacking is a process of amending an initial [voronoi diagram](https://en.wikipedia.org/wiki/Voronoi_diagram), that is based on a jittered square grid of points, into a voronoi diagram optimized for the current landmass (see [my old blog post](https://azgaar.wordpress.com/2017/10/05/templates) for the details). So the `pack` object is used for most of the data, but data optimized for square grid is available only via the `grid` object.
|
||||
|
||||
## Voronoi data
|
||||
Both `grid` and `pack` objects include data representing voronoi diagrams and their inner connections. Both initial and repacked voronoi can be build from the initial set of points, so this data is stored in memory only. It does not included into the .map file and getting calculated on map load.
|
||||
|
||||
### Grid object
|
||||
* `grid.cellsDesired`: `number` - initial count of cells/points requested for map creation. Used to define `spacing` and place points on a jittered square grid, hence the object name. Actual number of cells is defined by the number points able to place on a square grid. Default `cellsDesired` is 10 000, maximum - 100 000, minimal - 1 000
|
||||
* `grid.spacing`: `number` - spacing between points before jittering
|
||||
* `grid.cellsY`: `number` - number of cells in column
|
||||
* `grid.cellsX`: `number` - number of cells in row
|
||||
* `grid.points`: `number[][]` - coordinates `[x, y]` based on jittered square grid. Numbers rounded to 2 decimals
|
||||
* `grid.boundary`: `number[][]` - off-canvas points coordinates used to cut the diagram approximately by canvas edges. Integers
|
||||
* `grid.cells`: `{}` - cells data object, including voronoi data:
|
||||
* * `grid.cells.i`: `number[]` - cell indexes `Uint16Array` or `Uint32Array` (depending on cells number)
|
||||
* * `grid.cells.c`: `number[][]` - indexes of cells adjacent to each cell (neighboring cells)
|
||||
* * `grid.cells.v`: `number[][]` - indexes of vertices of each cell
|
||||
* * `grid.cells.b`: `number[]` - indicates if cell borders map edge, 1 if `true`, 0 if `false`. Integers, not Boolean
|
||||
|
||||
* `grid.vertices`: `{}` - vertices data object, contains only voronoi data:
|
||||
* * `grid.vertices.p`: `number[][]` - vertices coordinates `[x, y]`, integers
|
||||
* * `grid.vertices.c`: `number[][]` - indexes of cells adjacent to each vertex, each vertex has 3 adjacent cells
|
||||
* * `grid.vertices.v`: `number[][]` - indexes of vertices adjacent to each vertex. Most vertices have 3 neighboring vertices, bordering vertices has only 2, while the third is still added to the data as `-1`
|
||||
|
||||
### Pack object
|
||||
* `pack.cells`: `{}` - cells data object, including voronoi data:
|
||||
* * `pack.cells.i`: `number[]` - cell indexes `Uint16Array` or `Uint32Array` (depending on cells number)
|
||||
* * `pack.cells.p`: `number[][]` - cells coordinates `[x, y]` after repacking. Numbers rounded to 2 decimals
|
||||
* * `pack.cells.c`: `number[][]` - indexes of cells adjacent to each cell (neighboring cells)
|
||||
* * `pack.cells.v`: `number[][]` - indexes of vertices of each cell
|
||||
* * `pack.cells.b`: `number[]` - indicator whether the cell borders the map edge, 1 if `true`, 0 if `false`. Integers, not Boolean
|
||||
* * `pack.cells.g`: `number[]` - indexes of a source cell in `grid`. `Uint16Array` or `Uint32Array`. The only way to find correct `grid` cell parent for `pack` cells
|
||||
|
||||
* `pack.vertices`: `{}` - vertices data object, contains only voronoi data:
|
||||
* * `pack.vertices.p`: `number[][]` - vertices coordinates `[x, y]`, integers
|
||||
* * `pack.vertices.c`: `number[][]` - indexes of cells adjacent to each vertex, each vertex has 3 adjacent cells
|
||||
* * `pack.vertices.v`: `number[][]` - indexes of vertices adjacent to each vertex. Most vertices have 3 neighboring vertices, bordering vertices has only 2, while the third is still added to the data as `-1`
|
||||
|
||||
## Features data
|
||||
Features represent separate locked areas like islands, lakes and oceans.
|
||||
|
||||
### Grid object
|
||||
* `grid.features`: `object[]` - array containing objects for all enclosed entities of original graph: islands, lakes and oceans. Feature object structure:
|
||||
* * `i`: `number` - feature id starting from `1`
|
||||
* * `land`: `boolean` - `true` if feature is land (height >= `20`)
|
||||
* * `border`: `boolean` - `true` if feature touches map border (used to separate lakes from oceans)
|
||||
* * `type`: `string` - feature type, can be `ocean`, `island` or `lake
|
||||
|
||||
### Pack object
|
||||
* `pack.features`: `object[]` - array containing objects for all enclosed entities of repacked graph: islands, lakes and oceans. Note: element 0 has no data. Stored in .map file. Feature object structure:
|
||||
* * `i`: `number` - feature id starting from `1`
|
||||
* * `land`: `boolean` - `true` if feature is land (height >= `20`)
|
||||
* * `border`: `boolean` - `true` if feature touches map border (used to separate lakes from oceans)
|
||||
* * `type`: `string` - feature type, can be `ocean`, `island` or `lake`
|
||||
* * `group`: `string`: feature subtype, depends on type. Subtype for ocean is `ocean`; for land it is `continent`, `island`, `isle` or `lake_island`; for lake it is `freshwater`, `salt`, `dry`, `sinkhole` or `lava`
|
||||
* * `cells`: `number` - number of cells in feature
|
||||
* * `firstCell`: `number` - index of the first (top left) cell in feature
|
||||
* * `vertices`: `number[]` - indexes of vertices around the feature (perimetric vertices)
|
||||
** `name`: `string` - name, available for `lake` type only
|
||||
|
||||
## Specific cells data
|
||||
World data is mainly stored in typed arrays within `cells` object in both `grid` and `pack`.
|
||||
|
||||
### Grid object
|
||||
* `grid.cells.h`: `number[]` - cells elevation in `[0, 100]` range, where `20` is the minimal land elevation. `Uint8Array`
|
||||
* `grid.cells.f`: `number[]` - indexes of feature. `Uint16Array` or `Uint32Array` (depending on cells number)
|
||||
* `grid.cells.t`: `number[]` - [distance field](https://prideout.net/blog/distance_fields/) from water level. `1, 2, ...` - land cells, `-1, -2, ...` - water cells, `0` - unmarked cell. `Uint8Array`
|
||||
* `grid.cells.temp`: `number[]` - cells temperature in Celsius. `Uint8Array`
|
||||
* `grid.cells.prec`: `number[]` - cells precipitation in unspecified scale. `Uint8Array`
|
||||
|
||||
### Pack object
|
||||
* `pack.cells.h`: `number[]` - cells elevation in `[0, 100]` range, where `20` is the minimal land elevation. `Uint8Array`
|
||||
* `pack.cells.f`: `number[]` - indexes of feature. `Uint16Array` or `Uint32Array` (depending on cells number)
|
||||
* `pack.cells.t`: `number[]` - distance field. `1, 2, ...` - land cells, `-1, -2, ...` - water cells, `0` - unmarked cell. `Uint8Array`
|
||||
* `pack.cells.s`: `number[]` - cells score. Scoring is used to define best cells to place a burg. `Uint16Array`
|
||||
* `pack.cells.biome`: `number[]` - cells biome index. `Uint8Array`
|
||||
* `pack.cells.burg`: `number[]` - cells burg index. `Uint16Array`
|
||||
* `pack.cells.culture`: `number[]` - cells culture index. `Uint16Array`
|
||||
* `pack.cells.state`: `number[]` - cells state index. `Uint16Array`
|
||||
* `pack.cells.province`: `number[]` - cells province index. `Uint16Array`
|
||||
* `pack.cells.religion`: `number[]` - cells religion index. `Uint16Array`
|
||||
* `pack.cells.area`: `number[]` - cells area in pixels. `Uint16Array`
|
||||
* `pack.cells.pop`: `number[]` - cells population in population points (1 point = 1000 people by default). `Float32Array`, not rounded to not lose population of high population rate
|
||||
* `pack.cells.r`: `number[]` - cells river index. `Uint16Array`
|
||||
* `pack.cells.fl`: `number[]` - cells flux amount. Defines how much water flow through the cell. Use to get rivers data and score cells. `Uint16Array`
|
||||
* `pack.cells.conf`: `number[]` - cells flux amount in confluences. Confluences are cells where rivers meet each other. `Uint16Array`
|
||||
* `pack.cells.harbor`: `number[]` - cells harbor score. Shows how many water cells are adjacent to the cell. Used for scoring. `Uint8Array`
|
||||
* `pack.cells.haven`: `number[]` - cells haven cells index. Each coastal cell has haven cells defined for correct routes building. `Uint16Array` or `Uint32Array` (depending on cells number)
|
||||
* `pack.cells.routes`: `object` - cells connections via routes. E.g. `pack.cells.routes[8] = {9: 306, 10: 306}` shows that cell `8` has two route connections - with cell `9` via route `306` and with cell `10` by route `306`
|
||||
* `pack.cells.q`: `object` - quadtree used for fast closest cell detection
|
||||
|
||||
# Secondary data
|
||||
Secondary data available as a part of the `pack` object.
|
||||
|
||||
## Cultures
|
||||
Cultures (races, language zones) data is stored as an array of objects with strict element order. Element 0 is reserved by the _wildlands_ culture. If culture is removed, the element is not getting removed, but instead a `removed` attribute is added. Object structure:
|
||||
* `i`: `number` - culture id, always equal to the array index
|
||||
* `base`: `number` - _nameBase_ id, name base is used for names generation
|
||||
* `name`: `string` - culture name
|
||||
* `origins`: `number[]` - ids of origin cultures. Used to render cultures tree to show cultures evolution. The first array member is main link, other - supporting out-of-tree links
|
||||
* `shield`: `string` - shield type. Used for emblems rendering
|
||||
* `center`: `number` - cell id of culture center (initial cell)
|
||||
* `code`: `string` - culture name abbreviation. Used to render cultures tree
|
||||
* `color`: `string` - culture color in hex (e.g. `#45ff12`) or link to hatching pattern (e.g. `url(#hatch7)`)
|
||||
* `expansionism`: `number` - culture growth multiplier. Used mainly during cultures generation to spread cultures not uniformly
|
||||
* `type`: `string` - culture type, see [culture types](https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Culture-types)
|
||||
* `area`: `number` - culture area in pixels
|
||||
* `cells`: `number` - number of cells assigned to culture
|
||||
* `rural`: `number` - rural (non-burg) population of cells assigned to culture. In population points
|
||||
* `urban`: `number` - urban (burg) population of cells assigned to culture. In population points
|
||||
* `lock`: `boolean` - `true` if culture is locked (not affected by regeneration)
|
||||
* `removed`: `boolean` - `true` if culture is removed
|
||||
|
||||
## Burgs
|
||||
Burgs (settlements) data is stored as an array of objects with strict element order. Element 0 is an empty object. If burg is removed, the element is not getting removed, but instead a `removed` attribute is added. Object structure:
|
||||
* `i`: `number` - burg id, always equal to the array index
|
||||
* `name`: `string` - burg name
|
||||
* `cell`: `number` - burg cell id. One cell can have only one burg
|
||||
* `x`: `number` - x axis coordinate, rounded to two decimals
|
||||
* `y`: `number` - y axis coordinate, rounded to two decimals
|
||||
* `culture`: `number` - burg culture id
|
||||
* `state`: `number` - burg state id
|
||||
* `feature`: `number` - burg feature id (id of a landmass)
|
||||
* `population`: `number` - burg population in population points
|
||||
* `type`: `string` - burg type, see [culture types](https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Culture_types)
|
||||
* `coa`: `object` - emblem object, data model is the same as in [Armoria](https://github.com/Azgaar/Armoria) and covered in [API documentation](https://github.com/Azgaar/armoria-api#readme). The only additional fields are optional `size`: `number`, `x`: `number` and `y`: `number` that controls the emblem position on the map (if it's not default). If emblem is loaded by user, then the value is `{ custom: true }` and cannot be displayed in Armoria
|
||||
* `MFCG`: `number` - burg seed in [Medieval Fantasy City Generator](https://watabou.github.io/city-generator) (MFCG). If not provided, seed is combined from map seed and burg id
|
||||
* `link`: `string` - custom link to burg in MFCG. `MFCG` seed is not used if link is provided
|
||||
* `capital`: `number` - `1` if burg is a capital, `0` if not (each state has only 1 capital)
|
||||
* `port`: `number` - if burg is not a port, then `0`, otherwise feature id of the water body the burg stands on
|
||||
* `citadel`: `number` - `1` if burg has a castle, `0` if not. Used for MFCG
|
||||
* `plaza`: `number` - `1` if burg has a marketplace, `0` if not. Used for MFCG
|
||||
* `shanty`: `number` - `1` if burg has a shanty town, `0` if not. Used for MFCG
|
||||
* `temple`: `number` - `1` if burg has a temple, `0` if not. Used for MFCG
|
||||
* `walls`: `number` - `1` if burg has walls, `0` if not. Used for MFCG
|
||||
* `lock`: `boolean` - `true` if burg is locked (not affected by regeneration)
|
||||
* `removed`: `boolean` - `true` if burg is removed
|
||||
|
||||
## States
|
||||
States (countries) data is stored as an array of objects with strict element order. Element 0 is reserved for `neutrals`. If state is removed, the element is not getting removed, but instead a `removed` attribute is added. Object structure:
|
||||
* `i`: `number` - state id, always equal to the array index
|
||||
* `name`: `string` - short (proper) form of the state name
|
||||
* `form`: `string` - state form type. Available types are `Monarchy`, `Republic`, `Theocracy`, `Union`, and `Anarchy`
|
||||
* `formName`: `string` - string form name, used to get state `fullName`
|
||||
* `fullName`: `string` - full state name. Combination of the proper name and state `formName`
|
||||
* `color`: `string` - state color in hex (e.g. `#45ff12`) or link to hatching pattern (e.g. `url(#hatch7)`)
|
||||
* `center`: `number` - cell id of state center (initial cell)
|
||||
* `pole`: `number[]` - state pole of inaccessibility (visual center) coordinates, see [the concept description](https://blog.mapbox.com/a-new-algorithm-for-finding-a-visual-center-of-a-polygon-7c77e6492fbc?gi=6bd4fcb9ecc1)
|
||||
* `culture`: `number` - state culture id (equals to initial cell culture)
|
||||
* `type`: `string` - state type, see [culture types](https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Culture types)
|
||||
* `expansionism`: `number` - state growth multiplier. Used mainly during state generation to spread states not uniformly
|
||||
* `area`: `number` - state area in pixels
|
||||
* `burgs`: `number` - number of burgs within the state
|
||||
* `cells`: `number` - number of cells within the state
|
||||
* `rural`: `number` - rural (non-burg) population of state cells. In population points
|
||||
* `urban`: `number` - urban (burg) population of state cells. In population points
|
||||
* `neighbors`: `number[]` - ids of neighboring (bordering by land) states
|
||||
* `provinces`: `number[]` - ids of state provinces
|
||||
* `diplomacy`: `string[]` - diplomatic relations status for all states. 'x' for self and neutrals. Element 0 (neutrals) `diplomacy` is used differently and contains wars story as `string[][]`
|
||||
* `campaigns`: `object[]` - wars the state participated in. The was is defined as `start`: `number` (year), `end`: `number` (year), `name`: `string`
|
||||
* `alert`: `number` - state war alert, see [military forces page](https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Military-Forces)
|
||||
* `military`: `Regiment[]` - list of state regiments, see [military forces page](https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Military-Forces)
|
||||
* `coa`: `object` - emblem object, data model is the same as in [Armoria](https://github.com/Azgaar/Armoria) and covered in [API documentation](https://github.com/Azgaar/armoria-api#readme). The only additional fields are optional `size`: `number`, `x`: `number` and `y`: `number` that controls the emblem position on the map (if it's not default). If emblem is loaded by user, then the value is `{ custom: true }` and cannot be displayed in Armoria
|
||||
* `lock`: `boolean` - `true` if state is locked (not affected by regeneration)
|
||||
* `removed`: `boolean` - `true` if state is removed
|
||||
|
||||
### Regiment
|
||||
* `i`: `number` - regiment id, equals to the array index of regiment in the `state[x].military` array. Not unique, as unique string `regimentStateId-regimentId` is used
|
||||
* `x`: `number` - regiment x coordinate
|
||||
* `y`: `number` - regiment y coordinate
|
||||
* `bx`: `number` - regiment base x coordinate
|
||||
* `by`: `number` - regiment base y coordinate
|
||||
* `angle`: `number` - regiment rotation angle degree
|
||||
* `icon`: `number` - Unicode character to serve as an icon
|
||||
* `cell`: `number` - original regiment cell id
|
||||
* `state`: `number` - regiment state id
|
||||
* `name`: `string` - regiment name
|
||||
* `n`: `number` - `1` if regiment is a separate unit (like naval units), `0` is not
|
||||
* `u`: `Record<unitName, number>` - regiment content object
|
||||
|
||||
## Provinces
|
||||
Provinces data is stored as an array of objects with strict element order. Element 0 is not used. If religion is removed, the element is not getting removed, but instead a `removed` attribute is added. Object structure:
|
||||
* `i`: `number` - province id, always equal to the array index
|
||||
* `name`: `string` - short (proper) form of the province name
|
||||
* `formName`: `string` - string form name, used to get province `fullName`
|
||||
* `fullName`: `string` - full state name. Combination of the proper name and province `formName`
|
||||
* `color`: `string` - province color in hex (e.g. `#45ff12`) or link to hatching pattern (e.g. `url(#hatch7)`)
|
||||
* `center`: `number` - cell id of province center (initial cell)
|
||||
* `pole`: `number[]` - province pole of inaccessibility (visual center) coordinates, see [the concept description](https://blog.mapbox.com/a-new-algorithm-for-finding-a-visual-center-of-a-polygon-7c77e6492fbc?gi=6bd4fcb9ecc1)
|
||||
* `area`: `number` - province area in pixels
|
||||
* `burg`: `number` - id of province capital burg if any
|
||||
* `burgs`: `number[]` - id of burgs within the province. Optional (added when Province editor is opened)
|
||||
* `cells`: `number` - number of cells within the province
|
||||
* `rural`: `number` - rural (non-burg) population of province cells. In population points
|
||||
* `urban`: `number` - urban (burg) population of state province. In population points
|
||||
* `coa`: `object` - emblem object, data model is the same as in [Armoria](https://github.com/Azgaar/Armoria) and covered in [API documentation](https://github.com/Azgaar/armoria-api#readme). The only additional fields are optional `size`: `number`, `x`: `number` and `y`: `number` that controls the emblem position on the map (if it's not default). If emblem is loaded by user, then the value is `{ custom: true }` and cannot be displayed in Armoria
|
||||
* `lock`: `boolean` - `true` if province is locked (not affected by regeneration)
|
||||
* `removed`: `boolean` - `true` if province is removed
|
||||
|
||||
## Religions
|
||||
Religions data is stored as an array of objects with strict element order. Element 0 is reserved for "No religion". If province is removed, the element is not getting removed, but instead a `removed` attribute is added. Object structure:
|
||||
* `i`: `number` - religion id, always equal to the array index
|
||||
* `name`: `string` - religion name
|
||||
* `type`: `string` - religion type. Available types are `Folk`, `Organized`, `Heresy` and `Cult`
|
||||
* `form`: `string` - religion form
|
||||
* `deity`: `string` - religion supreme deity if any
|
||||
* `color`: `string` - religion color in hex (e.g. `#45ff12`) or link to hatching pattern (e.g. `url(#hatch7)`)
|
||||
* `code`: `string` - religion name abbreviation. Used to render religions tree
|
||||
* `origins`: `number[]` - ids of ancestor religions. `[0]` if religion doesn't have an ancestor. Used to render religions tree. The first array member is main link, other - supporting out-of-tree links
|
||||
* `center`: `number` - cell id of religion center (initial cell)
|
||||
* `culture`: `number` - religion original culture
|
||||
* `expansionism`: `number` - religion growth multiplier. Used during religion generation to define competitive size
|
||||
* `expansion`: `string` - religion expansion type. Can be `culture` so that religion grow only within its culture or `global`
|
||||
* `area`: `number` - religion area in pixels
|
||||
* `cells`: `number` - number of cells within the religion
|
||||
* `rural`: `number` - rural (non-burg) population of religion cells. In population points
|
||||
* `urban`: `number` - urban (burg) population of state religion. In population points
|
||||
* `lock`: `boolean` - `true` if religion is locked (not affected by regeneration)
|
||||
* `removed`: `boolean` - `true` if religion is removed
|
||||
|
||||
## Rivers
|
||||
Rivers data is stored as an unordered array of objects (so element id is _not_ the array index). Object structure:
|
||||
* `i`: `number` - river id
|
||||
* `name`: `string` - river name
|
||||
* `type`: `string` - river type, used to get river full name only
|
||||
* `source`: `number` - id of cell at river source
|
||||
* `mouth`: `number` - id of cell at river mouth
|
||||
* `parent`: `number` - parent river id. If river doesn't have a parent, the value is self id or `0`
|
||||
* `basin`: `number` - river basin id. Basin id is a river system main stem id. If river doesn't have a parent, the value is self id
|
||||
* `cells`: `number[]` - if of river points cells. Cells may not be unique. Cell value `-1` means the river flows off-canvas
|
||||
* `points`: `number[][]` - river points coordinates. Auto-generated rivers don't have points stored and rely on `cells` for rendering
|
||||
* `discharge`: `number` - river flux in m3/s
|
||||
* `length`: `number` - river length in km
|
||||
* `width`: `number` - river mouth width in km
|
||||
* `sourceWidth`: `number` - additional width added to river source on rendering. Used to make lake outlets start with some width depending on flux. Can be also used to manually create channels
|
||||
|
||||
## Markers
|
||||
Markers data is stored as an unordered array of objects (so element id is _not_ the array index). Object structure:
|
||||
* `i`: `number` - marker id. `'marker' + i` is used as svg element id and marker reference in `notes` object
|
||||
* `icon`: `number` - Unicode character (usually an [emoji](https://emojipedia.org/)) to serve as an icon
|
||||
* `x`: `number` - marker x coordinate
|
||||
* `y`: `number` - marker y coordinate
|
||||
* `cell`: `number` - cell id, used to prevent multiple markers generation in the same cell
|
||||
* `type`: `string` - marker type. If set, style changes will be applied to all markers of the same type. Optional
|
||||
* `size`: `number` - marker size in pixels. Optional, default value is `30` (30px)
|
||||
* `fill`: `string` - marker pin fill color. Optional, default is `#fff` (white)
|
||||
* `stroke`: `string` - marker pin stroke color. Optional, default is `#000` (black)
|
||||
* `pin`: `string`: pin element type. Optional, default is `bubble`. Pin is not rendered if value is set to `no`
|
||||
* `pinned`: `boolean`: if any marker is pinned, then only markers with `pinned = true` will be rendered. Optional
|
||||
* `dx`: `number` - icon x shift percent. Optional, default is `50` (50%, center)
|
||||
* `dy`: `number` - icon y shift percent. Optional, default s `50` (50%, center)
|
||||
* `px`: `number` - icon font-size in pixels. Optional, default is `12` (12px)
|
||||
* `lock`: `boolean` - `true` if marker is locked (not affected by regeneration). Optional
|
||||
|
||||
## Routes
|
||||
Routes data is stored as an unordered array of objects (so element id is _not_ the array index). Object structure:
|
||||
* `i`: `number` - route id. Please note the element with id `0` is a fully valid route, not a placeholder
|
||||
* `points`: `number[]` - array of control points in format `[x, y, cellId]`
|
||||
* `feature`: `number` - feature id of the route. Auto-generated routes cannot be place on multiple features
|
||||
* `group`: `string` - route group. Default groups are: 'roads', 'trails', 'searoutes'
|
||||
* `length`: `number` - route length in km. Optional
|
||||
* `name`: `string` - route name. Optional
|
||||
* `lock`: `boolean` - `true` if route is locked (not affected by regeneration). Optional
|
||||
|
||||
## Zones
|
||||
Zones data is stored as an array of objects with `i` not necessary equal to the element index, but order of element defines the rendering order and is important. Object structure:
|
||||
* `i`: `number` - zone id. Please note the element with id `0` is a fully valid zone, not a placeholder
|
||||
* `name`: `string` - zone description
|
||||
* `type`: `string` - zone type
|
||||
* `color`: `string` - link to hatching pattern (e.g. `url(#hatch7)`) or color in hex (e.g. `#45ff12`)
|
||||
* `cells`: `number[]` - array of zone cells
|
||||
* `lock`: `boolean` - `true` if zone is locked (not affected by regeneration). Optional
|
||||
* `hidden`: `boolean` - `true` if zone is hidden (not displayed). Optional
|
||||
|
||||
# Secondary global data
|
||||
Secondary data exposed to global space.
|
||||
|
||||
## Biomes
|
||||
Biomes data object is globally available as `biomesData`. It stores a few arrays, making it different from other data. Object structure:
|
||||
* `i`: `number[]` - biome id
|
||||
* `name`: `string[]` - biome names
|
||||
* `color`: `string[]` - biome colors in hex (e.g. `#45ff12`) or link to hatching pattern (e.g. `url(#hatch7)`)
|
||||
* `biomesMartix`: `number[][]` - 2d matrix used to define cell biome by temperature and moisture. Columns contain temperature data going from > `19` °C to < `-4` °C. Rows contain data for 5 moisture bands from the drier to the wettest one. Each row is a `Uint8Array`
|
||||
* `cost`: `number[]` - biome movement cost, must be `0` or positive. Extensively used during cultures, states and religions growth phase. `0` means spread to this biome costs nothing. Max value is not defined, but `5000` is the actual max used by default
|
||||
* `habitability`: `number[]` - biome habitability, must be `0` or positive. `0` means the biome is uninhabitable, max value is not defined, but `100` is the actual max used by default
|
||||
* `icons`: `string[][]` - non-weighed array of icons for each biome. Used for _relief icons_ rendering. Not-weighed means that random icons from array is selected, so the same icons can be mentioned multiple times
|
||||
* `iconsDensity`: `number[]` - defines how packed icons can be for the biome. An integer from `0` to `150`
|
||||
|
||||
## Notes
|
||||
Notes (legends) data is stored in unordered array of objects: `notes`. Object structure is as simple as:
|
||||
* `i`: `string` - note id
|
||||
* `name`: `string` - note name, visible in Legend box
|
||||
* `legend`: `string` - note text in html
|
||||
|
||||
## Name bases
|
||||
Name generator consumes training sets of real-world town names (with the exception of fantasy name bases) stored in `nameBases` array, that is available globally. Each array element represent a separate base. Base structure is:
|
||||
* `i`: `number` - base id, always equal to the array index
|
||||
* `name`: `string` - names base proper name
|
||||
* `b`: `string` - long string containing comma-separated list of names
|
||||
* `min`: `number` - recommended minimal length of generated names. Generator will adding new syllables until min length is reached
|
||||
* `max`: `number` - recommended maximal length of generated names. If max length is reached, generator will stop adding new syllables
|
||||
* `d`: `string` - letters that are allowed to be duplicated in generated names
|
||||
* `m`: `number` - if multi-word name is generated, how many of this cases should be transformed into a single word. `0` means multi-word names are not allowed, `1` - all generated multi-word names will stay as they are
|
||||
|
||||
## Property Availability Timeline
|
||||
|
||||
Properties are added to grid/pack progressively during map generation. Understanding when each property becomes available is crucial for module dependencies and avoiding undefined references.
|
||||
|
||||
### Grid Properties (coarse mesh ~10K cells)
|
||||
- `cells.h` - Available after: heightmap module
|
||||
- `cells.f` - Available after: features markupGrid module
|
||||
- `cells.t` - Available after: features markupGrid module
|
||||
- `cells.temp` - Available after: geography temperature calculation
|
||||
- `cells.prec` - Available after: geography precipitation generation
|
||||
|
||||
### Pack Properties (refined mesh)
|
||||
- `cells.h` - Available after: pack generation (reGraph utility)
|
||||
- `cells.f` - Available after: features markupPack module
|
||||
- `cells.t` - Available after: features markupPack module
|
||||
- `cells.fl` - Available after: rivers module
|
||||
- `cells.r` - Available after: rivers module
|
||||
- `cells.biome` - Available after: biomes module
|
||||
- `cells.s` - Available after: cell ranking utility
|
||||
- `cells.pop` - Available after: cell ranking utility
|
||||
- `cells.culture` - Available after: cultures module
|
||||
- `cells.burg` - Available after: burgs-and-states module
|
||||
- `cells.state` - Available after: burgs-and-states module
|
||||
- `cells.religion` - Available after: religions module
|
||||
- `cells.province` - Available after: provinces module
|
||||
|
||||
### Module Execution Flow
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Generate Grid] --> B[Heightmap Module]
|
||||
B --> |adds cells.h| C[Features markupGrid]
|
||||
C --> |adds cells.f, cells.t| D[Geography Temperature]
|
||||
D --> |adds cells.temp| E[Geography Precipitation]
|
||||
E --> |adds cells.prec| F[Pack Generation - reGraph]
|
||||
F --> |refines cells.h| G[Features markupPack]
|
||||
G --> |adds pack cells.f, cells.t| H[Rivers Module]
|
||||
H --> |adds cells.fl, cells.r| I[Biomes Module]
|
||||
I --> |adds cells.biome| J[Cell Ranking]
|
||||
J --> |adds cells.s, cells.pop| K[Cultures Module]
|
||||
K --> |adds cells.culture, pack.cultures| L[Cultures Expand]
|
||||
L --> M[BurgsAndStates Module]
|
||||
M --> |adds cells.burg, cells.state, pack.burgs, pack.states| N[Routes Module]
|
||||
N --> O[Religions Module]
|
||||
O --> |adds cells.religion, pack.religions| P[State Forms]
|
||||
P --> Q[Provinces Module]
|
||||
Q --> |adds cells.province, pack.provinces| R[Burg Features]
|
||||
R --> S[Rivers Specify]
|
||||
S --> T[Features Specify]
|
||||
T --> U[Military Module]
|
||||
U --> V[Markers Module]
|
||||
V --> W[Zones Module]
|
||||
W --> X[Complete Map Data]
|
||||
```
|
||||
226
procedural/docs/HEIGHTMAP_ASSESSMENT.md
Normal file
226
procedural/docs/HEIGHTMAP_ASSESSMENT.md
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
# Heightmap Generator Assessment
|
||||
|
||||
## Executive Summary
|
||||
|
||||
The heightmap generator module has undergone significant refactoring during the port from the browser-based version to the headless engine. While the core logic remains intact, several critical deviations have been introduced that impact functionality, architecture consistency, and maintainability. Most critically, the naming convention has become inconsistent and the state management pattern has been fundamentally altered.
|
||||
|
||||
## Key Deviations Identified
|
||||
|
||||
### 1. **Critical Naming Convention Violations**
|
||||
|
||||
**Original Pattern (../modules/heightmap-generator.js):**
|
||||
- **Input parameter:** `graph` (lines 9, 24, 40, 64, 70) - represents the incoming data structure
|
||||
- **Internal variable:** `grid` (line 4, 14, 21) - closure variable storing the graph after `setGraph(graph)` call
|
||||
- **Usage pattern:** Functions receive `graph`, call `setGraph(graph)` to store as `grid`, then use `grid` throughout
|
||||
|
||||
**Current Broken Pattern (src/engine/modules/heightmap-generator.js):**
|
||||
- **Inconsistent parameter naming:** Mix of `grid` and `graph` parameters
|
||||
- **Line 178:** `addPit(heights, graph, ...)` - should be `grid` like other functions
|
||||
- **Line 254, 255:** `findGridCell(startX, startY, graph)` - uses undefined `graph` variable
|
||||
- **Line 347:** `findGridCell(startX, startY, graph)` - uses undefined `graph` variable
|
||||
- **Line 444, 445:** `findGridCell(startX, startY, graph)` - uses undefined `graph` variable
|
||||
|
||||
**CRITICAL BUG:** These functions will fail at runtime because `graph` is undefined in their scope.
|
||||
|
||||
### 2. **setGraph/setGrid State Management Deviation**
|
||||
|
||||
**Original setGraph Pattern:**
|
||||
```javascript
|
||||
const setGraph = graph => {
|
||||
const {cellsDesired, cells, points} = graph;
|
||||
heights = cells.h ? Uint8Array.from(cells.h) : createTypedArray({maxValue: 100, length: points.length});
|
||||
blobPower = getBlobPower(cellsDesired);
|
||||
linePower = getLinePower(cellsDesired);
|
||||
grid = graph; // Store graph as grid for internal use
|
||||
};
|
||||
```
|
||||
|
||||
**Current setGrid Pattern:**
|
||||
```javascript
|
||||
function setGrid(grid, utils) {
|
||||
const { createTypedArray } = utils;
|
||||
const { cellsDesired, cells, points } = grid;
|
||||
const heights = cells.h ? Uint8Array.from(cells.h) : createTypedArray({ maxValue: 100, length: points.length });
|
||||
const blobPower = getBlobPower(cellsDesired);
|
||||
const linePower = getLinePower(cellsDesired);
|
||||
return { heights, blobPower, linePower }; // Returns computed values instead of storing state
|
||||
}
|
||||
```
|
||||
|
||||
**Critical Differences:**
|
||||
1. **State Storage:** Original stores state in closure, current returns computed values
|
||||
2. **Naming:** Original uses `graph` parameter name, current uses `grid`
|
||||
3. **Function Name:** `setGraph` vs `setGrid` - breaks the original naming logic
|
||||
4. **Return Pattern:** Original modifies closure state, current returns data for functional approach
|
||||
|
||||
### 3. **Architectural Pattern Shift Analysis**
|
||||
|
||||
**Original Closure-Based State Management:**
|
||||
- State variables (`grid`, `heights`, `blobPower`, `linePower`) live in module closure
|
||||
- `setGraph(graph)` initializes state once per generation cycle
|
||||
- Helper functions access closure state directly (no parameters needed)
|
||||
- `clearData()` cleans up state after generation
|
||||
|
||||
**Current Pure Functional Approach:**
|
||||
- No persistent state - everything passed as parameters
|
||||
- Each function receives `(heights, grid, blobPower, config, utils, ...args)`
|
||||
- `setGrid(grid, utils)` computes values and returns them (no state storage)
|
||||
- Each helper function creates new arrays and returns modified results
|
||||
|
||||
**Impact Analysis:**
|
||||
- **Positive:** True functional purity enables better testing and no side effects
|
||||
- **Negative:** Massive parameter bloat (8+ parameters per function vs 0 in original)
|
||||
- **Performance:** Multiple array allocations vs single state initialization
|
||||
|
||||
### 4. **Parameter Propagation Problems**
|
||||
|
||||
**Missing Parameters:**
|
||||
- Line 90-92: `modify()` function call missing `power` parameter that's used in implementation
|
||||
- Line 92: `modify(heights, a3, +a2, 1, utils)` - missing `power` but function expects it
|
||||
|
||||
**Wrong Parameter Order:**
|
||||
- Functions expect `(heights, grid, ...)` but some calls pass different structures
|
||||
- Type mismatches between expected `grid` object and passed `graph` references
|
||||
|
||||
### 5. **Return Value Handling Issues**
|
||||
|
||||
**Critical Deviation:**
|
||||
- Original functions modified global `heights` array in place
|
||||
- Current functions create new `Uint8Array(heights)` copies but don't always maintain referential consistency
|
||||
- This could lead to performance issues and memory overhead
|
||||
|
||||
### 6. **Utility Dependencies**
|
||||
|
||||
**Incomplete Migration:**
|
||||
- Line 51: `fromPrecreated` function is completely stubbed out
|
||||
- Missing critical browser-to-headless migration for image processing
|
||||
- DOM dependencies (`document.createElement`, `canvas`, `Image`) not replaced
|
||||
|
||||
## Specific Runtime Failures
|
||||
|
||||
### Bug 1: Undefined Variable References (CRITICAL)
|
||||
```javascript
|
||||
// Line 178 - Function parameter name
|
||||
export function addPit(heights, graph, blobPower, config, utils, count, height, rangeX, rangeY) {
|
||||
// Line 199 - Internal usage tries to access 'grid' (UNDEFINED)
|
||||
start = findGridCell(x, y, grid); // ReferenceError: grid is not defined
|
||||
```
|
||||
|
||||
### Bug 2: Parameter/Variable Mismatch Pattern
|
||||
**Broken Functions:**
|
||||
- `addPit` (line 178): parameter `graph`, usage `grid` (line 199, 209)
|
||||
- `addRange` (line 221): parameter `grid`, but calls `findGridCell(x, y, graph)` (lines 254-255)
|
||||
- `addTrough` (line 320): parameter `grid`, but calls `findGridCell(x, y, graph)` (lines 347, 359)
|
||||
- `addStrait` (line 425): parameter `grid`, but calls `findGridCell(x, y, graph)` (lines 444-445)
|
||||
|
||||
### Bug 3: Missing Parameter in Function Calls
|
||||
```javascript
|
||||
// Line 90 - Call site
|
||||
if (tool === "Add") return modify(heights, a3, +a2, 1, utils);
|
||||
|
||||
// Line 490 - Function signature expects 6 parameters, gets 5
|
||||
export function modify(heights, range, add, mult, power, utils) {
|
||||
// ^^^^^ undefined
|
||||
```
|
||||
|
||||
### Bug 4: Inconsistent Array Handling
|
||||
```javascript
|
||||
// Every helper function does:
|
||||
heights = new Uint8Array(heights); // Unnecessary copying if already Uint8Array
|
||||
// Original pattern: direct mutation of closure variable
|
||||
```
|
||||
|
||||
## Performance Impact Assessment
|
||||
|
||||
1. **Memory Overhead:** Each helper function creates new Uint8Array copies
|
||||
2. **Parameter Bloat:** Functions now take 6-8 parameters instead of accessing closure variables
|
||||
3. **Reduced Efficiency:** Multiple array allocations per generation step
|
||||
|
||||
## Recommendations
|
||||
|
||||
### Critical Fixes (Must Fix Immediately)
|
||||
|
||||
#### 1. **Restore Original Naming Convention**
|
||||
**All functions must use the original pattern:**
|
||||
- **Parameter name:** `graph` (not `grid`)
|
||||
- **Internal usage:** `grid` (converted from `graph` parameter)
|
||||
- **Function name:** `setGraph` (not `setGrid`)
|
||||
|
||||
```javascript
|
||||
// CORRECT pattern matching original:
|
||||
export function addPit(heights, graph, blobPower, config, utils, count, height, rangeX, rangeY) {
|
||||
const grid = graph; // Convert parameter to internal variable name
|
||||
// ... use grid throughout function body
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. **Fix Parameter/Variable Mismatches**
|
||||
**Every function with graph/grid issues:**
|
||||
- Line 178: `addPit` - change parameter from `graph` to `grid` OR add `const grid = graph;`
|
||||
- Lines 254-255, 347, 359, 444-445: Change `graph` to `grid` in `findGridCell` calls
|
||||
- Line 92: Add missing `power` parameter to `modify()` call
|
||||
|
||||
#### 3. **Standardize Function Signatures**
|
||||
**All helper functions should follow this pattern:**
|
||||
```javascript
|
||||
export function addHill(heights, graph, blobPower, config, utils, ...specificArgs) {
|
||||
const grid = graph; // Mirror original internal conversion
|
||||
// ... implementation using grid
|
||||
}
|
||||
```
|
||||
|
||||
### Architecture Decision Points
|
||||
|
||||
#### Option A: Pure Functional (Current Broken Approach)
|
||||
**Pros:** No side effects, better testability
|
||||
**Cons:** 8+ parameters per function, performance overhead, complexity
|
||||
**Fix Required:** Complete parameter standardization
|
||||
|
||||
#### Option B: Hybrid Closure Pattern (Recommended)
|
||||
**Restore original naming but keep functional returns:**
|
||||
```javascript
|
||||
function setGraph(graph, utils) { // Restore original name
|
||||
const grid = graph; // Original internal conversion
|
||||
const { cellsDesired, cells, points } = grid;
|
||||
// ... compute values
|
||||
return { heights, blobPower, linePower, grid }; // Include grid in return
|
||||
}
|
||||
```
|
||||
|
||||
#### Option C: Context Object Pattern
|
||||
**Bundle related parameters:**
|
||||
```javascript
|
||||
export function addHill(context, count, height, rangeX, rangeY) {
|
||||
const { heights, graph, blobPower, config, utils } = context;
|
||||
const grid = graph; // Maintain original pattern
|
||||
// ... implementation
|
||||
}
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
The heightmap generator refactoring represents an **incomplete and broken migration** from the original closure-based pattern. While the functional approach has merit, the implementation violates the original naming convention and introduces multiple runtime failures. The core issue is that the refactoring was performed without understanding the original `graph` → `grid` naming logic.
|
||||
|
||||
**Root Cause:** The original code used `graph` as the input parameter name and `grid` as the internal variable name after calling `setGraph(graph)`. The current version inconsistently mixes these names, creating undefined variable references.
|
||||
|
||||
**Severity:** HIGH - Multiple functions will fail at runtime due to undefined variable access.
|
||||
|
||||
## Priority Actions (In Order)
|
||||
|
||||
### Immediate (Blocking)
|
||||
1. **Fix undefined variable references** - All `findGridCell(x, y, graph)` calls where `graph` is undefined
|
||||
2. **Standardize parameter names** - Either all `graph` or all `grid`, but consistently applied
|
||||
3. **Restore setGraph naming** - Change `setGrid` back to `setGraph` to match original pattern
|
||||
4. **Fix missing parameters** - Add `power` parameter to `modify()` function calls
|
||||
|
||||
### Short Term
|
||||
1. **Choose architectural pattern** - Pure functional vs hybrid vs context object
|
||||
2. **Optimize array handling** - Eliminate unnecessary Uint8Array copying
|
||||
3. **Complete parameter standardization** - Ensure all functions follow chosen pattern
|
||||
|
||||
### Long Term
|
||||
1. **Complete fromPrecreated migration** - Implement headless image processing
|
||||
2. **Performance benchmarking** - Compare against original implementation
|
||||
3. **Add comprehensive testing** - Prevent regression of these naming issues
|
||||
|
||||
**Recommendation:** Restore the original `graph` parameter → `grid` internal variable pattern throughout the entire module to maintain consistency with the original design intent.
|
||||
460
procedural/docs/MAP_GENERATION_TRACE.md
Normal file
460
procedural/docs/MAP_GENERATION_TRACE.md
Normal file
|
|
@ -0,0 +1,460 @@
|
|||
# Exhaustive Step-by-Step Trace of Fantasy Map Generator Execution Flow
|
||||
|
||||
## Starting Point: "Generate Map" Button Click
|
||||
|
||||
**File**: `/home/user/Fantasy-Map-Generator/procedural/index.html`
|
||||
- **Line 262**: `<button id="newMapButton" class="primary">🗺️ Generate Map</button>`
|
||||
- **Line 263**: `<button id="generateButton" class="primary">Generate (Alt)</button>`
|
||||
|
||||
## Phase 1: Event Handler Registration and Initialization
|
||||
|
||||
**File**: `/home/user/Fantasy-Map-Generator/procedural/src/viewer/main.js`
|
||||
|
||||
### Step 1: DOM Content Loaded
|
||||
- **Line 210**: `window.addEventListener('DOMContentLoaded', () => { ... })`
|
||||
- **Data Created**: DOM content loaded event listener
|
||||
- **Function Called**: Anonymous function for initialization
|
||||
|
||||
### Step 2: Button Event Handler Registration
|
||||
- **Lines 222-225**: Button event handler registration
|
||||
```javascript
|
||||
const generateBtn = byId("newMapButton") || byId("generateButton");
|
||||
if (generateBtn) {
|
||||
generateBtn.addEventListener("click", handleGenerateClick);
|
||||
}
|
||||
```
|
||||
- **Data Created**: Event listener for click event
|
||||
- **Function Called**: `handleGenerateClick` when clicked
|
||||
|
||||
## Phase 2: Configuration Building and Validation
|
||||
|
||||
**File**: `/home/user/Fantasy-Map-Generator/procedural/src/viewer/main.js`
|
||||
|
||||
### Step 3: Generate Click Handler Starts
|
||||
- **Line 32**: `async function handleGenerateClick()` starts execution
|
||||
- **Function Called**: `handleGenerateClick()`
|
||||
|
||||
### Step 4: Build Configuration from UI
|
||||
- **Line 36**: `const config = buildConfigFromUI();`
|
||||
- **File**: `/home/user/Fantasy-Map-Generator/procedural/src/viewer/config-builder.js`
|
||||
- **Function Called**: `buildConfigFromUI()` (Line 8)
|
||||
- **Data Created**: Complete configuration object with sections:
|
||||
|
||||
### Step 5: Configuration Object Structure Created
|
||||
- **Lines 9-31** in config-builder.js: Configuration object structure created
|
||||
```javascript
|
||||
const config = {
|
||||
seed: getSeed(), // Line 61: Gets seed from UI or generates new one
|
||||
graph: buildGraphConfig(), // Line 67: { width, height, cellsDesired }
|
||||
map: buildMapConfig(), // Line 79: { coordinatesSize, latitude }
|
||||
heightmap: buildHeightmapConfig(), // Line 86: { templateId }
|
||||
temperature: buildTemperatureConfig(), // Line 93: { heightExponent, temperatureScale, temperatureBase }
|
||||
precipitation: buildPrecipitationConfig(), // Line 101: { winds, moisture }
|
||||
features: {},
|
||||
biomes: {},
|
||||
lakes: buildLakesConfig(), // Line 110: { lakeElevationLimit, heightExponent }
|
||||
rivers: buildRiversConfig(), // Line 119: { resolveDepressionsSteps, cellsCount }
|
||||
oceanLayers: buildOceanLayersConfig(), // Line 129: { outline }
|
||||
cultures: buildCulturesConfig(), // Line 137: { culturesInput, culturesSet, emblemShape, etc. }
|
||||
burgs: buildBurgsConfig(), // Line 162: { statesNumber, manorsInput, growthRate, etc. }
|
||||
religions: buildReligionsConfig(), // Line 178: { religionsNumber, growthRate }
|
||||
provinces: buildProvincesConfig(), // Line 185: { provincesRatio }
|
||||
military: buildMilitaryConfig(), // Line 192: { year, eraShort, era }
|
||||
markers: buildMarkersConfig(), // Line 196: { culturesSet }
|
||||
zones: buildZonesConfig(), // Line 202: { globalModifier }
|
||||
debug: buildDebugConfig() // Line 208: { TIME, WARN, INFO, ERROR }
|
||||
};
|
||||
```
|
||||
|
||||
### Step 6: Configuration Validation
|
||||
- **Line 39** in main.js: `const { fixed, originalValidation, fixedValidation, wasFixed } = validateAndFix(config);`
|
||||
- **File**: `/home/user/Fantasy-Map-Generator/procedural/src/viewer/config-validator.js`
|
||||
- **Function Called**: `validateAndFix(config)`
|
||||
- **Data Created**: Validation results and fixed configuration
|
||||
|
||||
### Step 7: Validation Logging
|
||||
- **Lines 42-60** in main.js: Validation logging and error handling
|
||||
- **Objects Received**: `originalValidation`, `fixedValidation`, `wasFixed` boolean
|
||||
- **Expected**: Validation objects with `errors`, `warnings`, `valid` properties
|
||||
|
||||
### Step 8: Save Configuration to LocalStorage
|
||||
- **Line 64**: `localStorage.setItem('fmg-last-config', saveConfigToJSON(fixed));`
|
||||
- **Function Called**: `saveConfigToJSON(fixed)` from config-builder.js
|
||||
- **Data Created**: JSON string representation of configuration stored in localStorage
|
||||
|
||||
## Phase 3: Engine Generation Call
|
||||
|
||||
**File**: `/home/user/Fantasy-Map-Generator/procedural/src/viewer/main.js`
|
||||
|
||||
### Step 9: Call Engine Generate Function
|
||||
- **Line 70**: `const mapData = await generateMapEngine(fixed);`
|
||||
- **File**: `/home/user/Fantasy-Map-Generator/procedural/src/engine/main.js`
|
||||
- **Function Called**: `generate(config)` (imported as `generateMapEngine`, Line 33)
|
||||
- **Data Passed**: Validated and fixed configuration object
|
||||
|
||||
## Phase 4: Engine Initialization
|
||||
|
||||
**File**: `/home/user/Fantasy-Map-Generator/procedural/src/engine/main.js`
|
||||
|
||||
### Step 10: Start Performance Timer
|
||||
- **Line 34**: `const timeStart = performance.now();`
|
||||
- **Data Created**: Timestamp for performance measurement
|
||||
|
||||
### Step 11: Extract Debug Flags
|
||||
- **Line 37**: `const { TIME, WARN, INFO } = config.debug;`
|
||||
- **Data Extracted**: Debug flags from configuration
|
||||
|
||||
### Step 12: Seed Initialization
|
||||
- **Line 40**: `const seed = config.seed || Utils.generateSeed();`
|
||||
- **Function Called**: `Utils.generateSeed()` if no seed provided
|
||||
- **Data Created**: Final seed value for generation
|
||||
|
||||
### Step 13: Initialize Seeded Random Number Generator
|
||||
- **Line 41**: `Math.random = Utils.aleaPRNG(seed);`
|
||||
- **Function Called**: `Utils.aleaPRNG(seed)` from `/home/user/Fantasy-Map-Generator/procedural/src/engine/utils/alea.js`
|
||||
- **Data Modified**: Global Math.random function replaced with seeded PRNG
|
||||
|
||||
### Step 14: Console Group Start
|
||||
- **Line 44**: `INFO && console.group("Generating Map with Seed: " + seed);`
|
||||
- **Action**: Console group started if INFO debug flag is true
|
||||
|
||||
## Phase 5: Grid Generation
|
||||
|
||||
**File**: `/home/user/Fantasy-Map-Generator/procedural/src/engine/main.js`
|
||||
|
||||
### Step 15: Generate Initial Grid
|
||||
- **Line 48**: `let grid = Graph.generateGrid(config.graph);`
|
||||
- **File**: `/home/user/Fantasy-Map-Generator/procedural/src/engine/utils/graph.js`
|
||||
- **Function Called**: `generateGrid(config)` (Line 13)
|
||||
- **Data Passed**: `config.graph` object containing `{ width, height, cellsDesired }`
|
||||
- **Data Created**: Initial grid object
|
||||
|
||||
### Step 16: Grid Generation Process
|
||||
- **Lines 15-17** in graph.js: Grid generation process
|
||||
```javascript
|
||||
const { spacing, cellsDesired, boundary, points, cellsX, cellsY } = placePoints(config);
|
||||
const { cells, vertices } = calculateVoronoi(points, boundary);
|
||||
return { spacing, cellsDesired, boundary, points, cellsX, cellsY, cells, vertices };
|
||||
```
|
||||
- **Functions Called**: `placePoints(config)` (Line 21), `calculateVoronoi(points, boundary)` (Line 34)
|
||||
- **Data Created**: Voronoi diagram with cells and vertices
|
||||
|
||||
### Step 17: Point Placement
|
||||
- **Lines 21-31** in graph.js: Point placement
|
||||
- **Function Called**: `getBoundaryPoints()`, `getJitteredGrid()`
|
||||
- **Data Created**: Array of points for Voronoi calculation
|
||||
- **Objects Created**: `{ spacing, cellsDesired, boundary, points, cellsX, cellsY }`
|
||||
|
||||
### Step 18: Voronoi Calculation
|
||||
- **Lines 34-50** in graph.js: Voronoi calculation
|
||||
- **External Library**: Delaunator for Delaunay triangulation
|
||||
- **Function Called**: `new Voronoi(delaunay, allPoints, points.length)`
|
||||
- **Data Created**: `cells` and `vertices` objects with neighbor relationships
|
||||
|
||||
## Phase 6: Heightmap Generation
|
||||
|
||||
**File**: `/home/user/Fantasy-Map-Generator/procedural/src/engine/main.js`
|
||||
|
||||
### Step 19: Generate Heightmap
|
||||
- **Line 51**: `grid.cells.h = await Heightmap.generate(grid, config, Utils);`
|
||||
- **File**: `/home/user/Fantasy-Map-Generator/procedural/src/engine/modules/heightmap-generator.js`
|
||||
- **Function Called**: `generate(graph, config, utils)` (Line 3)
|
||||
- **Data Passed**: `grid` object, `config` object, `Utils` module
|
||||
- **Data Created**: Height values array assigned to `grid.cells.h`
|
||||
|
||||
### Step 20: Heightmap Processing
|
||||
- **Lines 3-18** in heightmap-generator.js: Heightmap generation
|
||||
- **Data Extracted**: `templateId` from `config.heightmap`
|
||||
- **Function Called**: `fromTemplate(graph, templateId, config, utils)` (Line 32)
|
||||
- **Data Created**: Heights array for all grid cells
|
||||
|
||||
### Step 21: Template Processing
|
||||
- **Lines 32-48** in heightmap-generator.js: Template processing
|
||||
- **Data Accessed**: `heightmapTemplates[id].template` string
|
||||
- **Function Called**: `setGraph(graph, utils)`, `addStep()` for each template step
|
||||
- **Data Created**: Final heights array with template-based terrain
|
||||
|
||||
## Phase 7: Features Markup
|
||||
|
||||
**File**: `/home/user/Fantasy-Map-Generator/procedural/src/engine/main.js`
|
||||
|
||||
### Step 22: Markup Grid Features
|
||||
- **Line 52**: `grid = Features.markupGrid(grid, config, Utils);`
|
||||
- **File**: `/home/user/Fantasy-Map-Generator/procedural/src/engine/modules/features.js`
|
||||
- **Function Called**: `markupGrid(grid, config, utils)` (Line 28)
|
||||
- **Data Passed**: Grid with heights, config, utils
|
||||
- **Data Modified**: Grid object enhanced with feature information
|
||||
|
||||
### Step 23: Grid Markup Process
|
||||
- **Lines 28-50** in features.js: Grid markup process
|
||||
- **Data Created**: `distanceField` (Int8Array), `featureIds` (Uint16Array), `features` array
|
||||
- **Algorithm**: Flood-fill to identify connected land/water regions
|
||||
- **Data Added to Grid**: Distance fields and feature classifications
|
||||
|
||||
## Phase 8: Geography and Climate
|
||||
|
||||
**File**: `/home/user/Fantasy-Map-Generator/procedural/src/engine/main.js`
|
||||
|
||||
### Step 24: Define Map Size
|
||||
- **Line 55**: `const { mapCoordinates } = Geography.defineMapSize(grid, config, Utils);`
|
||||
- **File**: `/home/user/Fantasy-Map-Generator/procedural/src/engine/utils/geography.js`
|
||||
- **Function Called**: `defineMapSize(grid, config, Utils)`
|
||||
- **Data Created**: `mapCoordinates` object with geographic bounds
|
||||
|
||||
### Step 25: Add Lakes in Deep Depressions
|
||||
- **Line 56**: `grid = Geography.addLakesInDeepDepressions(grid, config.lakes, Utils);`
|
||||
- **Function Called**: `addLakesInDeepDepressions(grid, config.lakes, Utils)`
|
||||
- **Data Modified**: Grid enhanced with lake information
|
||||
|
||||
### Step 26: Open Near-Sea Lakes
|
||||
- **Line 57**: `grid = Geography.openNearSeaLakes(grid, config.lakes, Utils);`
|
||||
- **Function Called**: `openNearSeaLakes(grid, config.lakes, Utils)`
|
||||
- **Data Modified**: Lake connectivity to ocean processed
|
||||
|
||||
### Step 27: Calculate Temperatures
|
||||
- **Line 60**: `const { temp } = Geography.calculateTemperatures(grid, mapCoordinates, config.temperature, Utils);`
|
||||
- **Function Called**: `calculateTemperatures()`
|
||||
- **Data Created**: Temperature array for all cells
|
||||
- **Line 61**: `grid.cells.temp = temp;` - Temperature data assigned to grid
|
||||
|
||||
### Step 28: Generate Precipitation
|
||||
- **Line 62**: `const { prec } = Geography.generatePrecipitation(grid, mapCoordinates, config.precipitation, Utils);`
|
||||
- **Function Called**: `generatePrecipitation()`
|
||||
- **Data Created**: Precipitation array for all cells
|
||||
- **Line 63**: `grid.cells.prec = prec;` - Precipitation data assigned to grid
|
||||
|
||||
## Phase 9: Pack Generation (Refined Mesh)
|
||||
|
||||
**File**: `/home/user/Fantasy-Map-Generator/procedural/src/engine/main.js`
|
||||
|
||||
### Step 29: Generate Refined Mesh (Pack)
|
||||
- **Line 66**: `let pack = Graph.reGraph(grid, Utils);`
|
||||
- **File**: `/home/user/Fantasy-Map-Generator/procedural/src/engine/utils/graph.js`
|
||||
- **Function Called**: `reGraph(grid, Utils)`
|
||||
- **Data Created**: Refined mesh (`pack`) with higher resolution than grid
|
||||
- **Purpose**: Creates detailed mesh for final map features
|
||||
|
||||
### Step 30: Markup Pack Features
|
||||
- **Line 67**: `pack = Features.markupPack(pack, grid, config, Utils, { Lakes });`
|
||||
- **Function Called**: `Features.markupPack()`
|
||||
- **Data Passed**: Pack mesh, original grid, config, utils, Lakes module
|
||||
- **Data Modified**: Pack enhanced with feature information
|
||||
|
||||
## Phase 10: River Generation
|
||||
|
||||
**File**: `/home/user/Fantasy-Map-Generator/procedural/src/engine/main.js`
|
||||
|
||||
### Step 31: Generate Rivers
|
||||
- **Line 70**: `const riverResult = Rivers.generate(pack, grid, config.rivers, Utils, { Lakes, Names });`
|
||||
- **File**: `/home/user/Fantasy-Map-Generator/procedural/src/engine/modules/river-generator.js`
|
||||
- **Function Called**: `generate(pack, grid, config.rivers, Utils, { Lakes, Names })`
|
||||
- **Data Passed**: Pack mesh, grid, river config, utilities, Lakes and Names modules
|
||||
- **Data Created**: River system data
|
||||
|
||||
### Step 32: Update Pack with Rivers
|
||||
- **Line 71**: `pack = riverResult.pack;`
|
||||
- **Data Modified**: Pack object updated with river information
|
||||
|
||||
## Phase 11: Biome Assignment
|
||||
|
||||
**File**: `/home/user/Fantasy-Map-Generator/procedural/src/engine/main.js`
|
||||
|
||||
### Step 33: Define Biomes
|
||||
- **Line 74**: `const { biome } = Biomes.define(pack, grid, config.biomes, Utils);`
|
||||
- **File**: `/home/user/Fantasy-Map-Generator/procedural/src/engine/modules/biomes.js`
|
||||
- **Function Called**: `define(pack, grid, config.biomes, Utils)`
|
||||
- **Data Created**: Biome classifications for each cell
|
||||
|
||||
### Step 34: Assign Biomes to Pack
|
||||
- **Line 75**: `pack.cells.biome = biome;`
|
||||
- **Data Modified**: Biome data assigned to pack cells
|
||||
|
||||
## Phase 12: Cell Ranking and Population
|
||||
|
||||
**File**: `/home/user/Fantasy-Map-Generator/procedural/src/engine/main.js`
|
||||
|
||||
### Step 35: Rank Cells
|
||||
- **Line 78**: `const { s, pop } = Cell.rankCells(pack, Utils, { biomesData: Biomes.getDefault() });`
|
||||
- **File**: `/home/user/Fantasy-Map-Generator/procedural/src/engine/utils/cell.js`
|
||||
- **Function Called**: `rankCells(pack, Utils, { biomesData })`
|
||||
- **Data Passed**: Pack, utilities, default biomes data
|
||||
- **Data Created**: Cell suitability rankings (`s`) and population values (`pop`)
|
||||
|
||||
### Step 36: Assign Cell Rankings
|
||||
- **Lines 79-80**: Cell data assignment
|
||||
```javascript
|
||||
pack.cells.s = s;
|
||||
pack.cells.pop = pop;
|
||||
```
|
||||
|
||||
## Phase 13: Culture Generation
|
||||
|
||||
**File**: `/home/user/Fantasy-Map-Generator/procedural/src/engine/main.js`
|
||||
|
||||
### Step 37: Generate Cultures
|
||||
- **Line 83**: `const culturesResult = Cultures.generate(pack, grid, config.cultures, Utils, { Names });`
|
||||
- **File**: `/home/user/Fantasy-Map-Generator/procedural/src/engine/modules/cultures-generator.js`
|
||||
- **Function Called**: `generate(pack, grid, config.cultures, Utils, { Names })`
|
||||
- **Data Created**: Cultures data and culture assignments
|
||||
|
||||
### Step 38: Integrate Culture Data
|
||||
- **Lines 84-85**: Culture data integration
|
||||
```javascript
|
||||
let packWithCultures = { ...pack, cultures: culturesResult.cultures };
|
||||
packWithCultures.cells.culture = culturesResult.culture;
|
||||
```
|
||||
|
||||
### Step 39: Expand Cultures
|
||||
- **Line 87**: `const expandedCulturesData = Cultures.expand(packWithCultures, config.cultures, Utils, { biomesData: Biomes.getDefault() });`
|
||||
- **Function Called**: `Cultures.expand()`
|
||||
- **Data Created**: Expanded culture territories
|
||||
|
||||
### Step 40: Update Pack with Expanded Cultures
|
||||
- **Line 88**: `pack = { ...packWithCultures, ...expandedCulturesData };`
|
||||
- **Data Modified**: Pack updated with expanded culture data
|
||||
|
||||
## Phase 14: Burgs and States Generation
|
||||
|
||||
**File**: `/home/user/Fantasy-Map-Generator/procedural/src/engine/main.js`
|
||||
|
||||
### Step 41: Generate Burgs and States
|
||||
- **Line 90**: `const burgsAndStatesResult = BurgsAndStates.generate(pack, grid, config.burgs, Utils);`
|
||||
- **File**: `/home/user/Fantasy-Map-Generator/procedural/src/engine/modules/burgs-and-states.js`
|
||||
- **Function Called**: `generate(pack, grid, config.burgs, Utils)`
|
||||
- **Data Created**: Settlements (burgs) and political entities (states)
|
||||
|
||||
### Step 42: Integrate Burgs and States Data
|
||||
- **Lines 91-97**: Burgs and states data integration
|
||||
```javascript
|
||||
pack = {
|
||||
...pack,
|
||||
burgs: burgsAndStatesResult.burgs,
|
||||
states: burgsAndStatesResult.states
|
||||
};
|
||||
pack.cells.burg = burgsAndStatesResult.burg;
|
||||
pack.cells.state = burgsAndStatesResult.state;
|
||||
```
|
||||
|
||||
## Phase 15: Additional Features Generation
|
||||
|
||||
**File**: `/home/user/Fantasy-Map-Generator/procedural/src/engine/main.js`
|
||||
|
||||
### Step 43: Generate Routes
|
||||
- **Line 99**: `const routesResult = Routes.generate(pack, grid, Utils, []);`
|
||||
- **File**: `/home/user/Fantasy-Map-Generator/procedural/src/engine/modules/routes-generator.js`
|
||||
- **Function Called**: `generate(pack, grid, Utils, [])`
|
||||
- **Data Created**: Trade and travel routes
|
||||
|
||||
### Step 44: Generate Religions
|
||||
- **Line 102**: `const religionsResult = Religions.generate(pack, grid, config.religions, Utils);`
|
||||
- **File**: `/home/user/Fantasy-Map-Generator/procedural/src/engine/modules/religions-generator.js`
|
||||
- **Function Called**: `generate(pack, grid, config.religions, Utils)`
|
||||
- **Data Created**: Religious systems and distributions
|
||||
|
||||
### Step 45: Define State Forms
|
||||
- **Line 105**: `const stateFormsResult = BurgsAndStates.defineStateForms(undefined, pack, Utils);`
|
||||
- **Function Called**: `BurgsAndStates.defineStateForms()`
|
||||
- **Data Created**: Government forms for states
|
||||
|
||||
### Step 46: Generate Provinces
|
||||
- **Line 108**: `const provincesResult = Provinces.generate(pack, config.provinces, Utils);`
|
||||
- **File**: `/home/user/Fantasy-Map-Generator/procedural/src/engine/modules/provinces-generator.js`
|
||||
- **Function Called**: `generate(pack, config.provinces, Utils)`
|
||||
- **Data Created**: Provincial subdivisions
|
||||
|
||||
### Step 47: Define Burg Features
|
||||
- **Line 111**: `const burgFeaturesResult = BurgsAndStates.defineBurgFeatures(undefined, pack, Utils);`
|
||||
- **Function Called**: `BurgsAndStates.defineBurgFeatures()`
|
||||
- **Data Created**: Detailed settlement features
|
||||
|
||||
### Step 48: Specify Rivers
|
||||
- **Line 114**: `const specifiedRiversResult = Rivers.specify(pack, { Names }, Utils);`
|
||||
- **Function Called**: `Rivers.specify()`
|
||||
- **Data Created**: Named and detailed river information
|
||||
|
||||
### Step 49: Specify Features
|
||||
- **Line 117**: `const specifiedFeaturesResult = Features.specify(pack, grid, { Lakes });`
|
||||
- **Function Called**: `Features.specify()`
|
||||
- **Data Created**: Detailed geographic feature information
|
||||
|
||||
### Step 50: Initialize Notes Array
|
||||
- **Line 121**: `const notes = [];`
|
||||
- **Data Created**: Notes array for modules requiring annotation
|
||||
|
||||
### Step 51: Generate Military
|
||||
- **Line 123**: `const militaryResult = Military.generate(pack, config.military, Utils, notes);`
|
||||
- **File**: `/home/user/Fantasy-Map-Generator/procedural/src/engine/modules/military-generator.js`
|
||||
- **Function Called**: `generate(pack, config.military, Utils, notes)`
|
||||
- **Data Created**: Military units and fortifications
|
||||
|
||||
### Step 52: Generate Markers
|
||||
- **Line 126**: `const markersResult = Markers.generateMarkers(pack, config.markers, Utils);`
|
||||
- **File**: `/home/user/Fantasy-Map-Generator/procedural/src/engine/modules/markers-generator.js`
|
||||
- **Function Called**: `generateMarkers(pack, config.markers, Utils)`
|
||||
- **Data Created**: Map markers and labels
|
||||
|
||||
### Step 53: Generate Zones
|
||||
- **Line 129**: `const zonesResult = Zones.generate(pack, notes, Utils, config.zones);`
|
||||
- **File**: `/home/user/Fantasy-Map-Generator/procedural/src/engine/modules/zones-generator.js`
|
||||
- **Function Called**: `generate(pack, notes, Utils, config.zones)`
|
||||
- **Data Created**: Special zones and areas
|
||||
|
||||
## Phase 16: Generation Completion
|
||||
|
||||
**File**: `/home/user/Fantasy-Map-Generator/procedural/src/engine/main.js`
|
||||
|
||||
### Step 54: Log Performance Timing
|
||||
- **Line 133**: `WARN && console.warn(\`TOTAL GENERATION TIME: ${Utils.rn((performance.now() - timeStart) / 1000, 2)}s\`);`
|
||||
- **Action**: Performance timing logged if WARN debug flag is true
|
||||
|
||||
### Step 55: Close Console Group
|
||||
- **Line 134**: `INFO && console.groupEnd("Generated Map " + seed);`
|
||||
- **Action**: Console group ended if INFO debug flag is true
|
||||
|
||||
### Step 56: Return Generated Map Data
|
||||
- **Line 137**: `return { seed, grid, pack, mapCoordinates };`
|
||||
- **Data Returned**: Complete map data object containing:
|
||||
- `seed`: Generation seed
|
||||
- `grid`: Coarse Voronoi mesh with basic geographic data
|
||||
- `pack`: Refined mesh with all generated features
|
||||
- `mapCoordinates`: Geographic coordinate system
|
||||
|
||||
## Phase 17: Return to Viewer
|
||||
|
||||
**File**: `/home/user/Fantasy-Map-Generator/procedural/src/viewer/main.js`
|
||||
|
||||
### Step 57: Log Map Generation Complete
|
||||
- **Line 72**: `console.log("Engine finished. Map data generated:", mapData);`
|
||||
- **Data Received**: Complete `mapData` object from engine
|
||||
- **Objects Available**: `{ seed, grid, pack, mapCoordinates }`
|
||||
|
||||
### Step 58: Render Map (Currently Commented Out)
|
||||
- **Line 74**: `// renderMap(mapData);` (commented out)
|
||||
- **Expected Next Step**: Rendering system would take the mapData and create visual representation
|
||||
- **Current State**: Generation complete, awaiting rendering implementation
|
||||
|
||||
## Summary of Data Flow
|
||||
|
||||
### Key Data Transformations:
|
||||
1. **UI → Configuration**: HTML form values → structured config object
|
||||
2. **Configuration → Grid**: Config parameters → Voronoi mesh
|
||||
3. **Grid → Heightmap**: Mesh structure → elevation data
|
||||
4. **Grid → Features**: Heights → land/water classification
|
||||
5. **Grid → Pack**: Coarse mesh → refined mesh
|
||||
6. **Pack → Biomes**: Climate data → biome assignments
|
||||
7. **Pack → Cultures**: Suitability → cultural territories
|
||||
8. **Pack → Complete Map**: Sequential module processing → final map data
|
||||
|
||||
### Module Interaction Pattern:
|
||||
- Each module receives: `(pack/grid, config_section, Utils, dependencies)`
|
||||
- Each module returns: New data to merge into pack/grid
|
||||
- Modules are stateless and pure (no side effects beyond returned data)
|
||||
- Sequential processing builds up complexity from basic terrain to complete civilization
|
||||
|
||||
### Common Data Mismatches:
|
||||
1. **Modules expecting properties that don't exist yet** (e.g., expecting `cells.culture` before Cultures module runs)
|
||||
2. **Config sections missing expected fields** (validation tries to fix this)
|
||||
3. **Pack/Grid structure differences** (pack is refined version of grid)
|
||||
4. **Module dependencies** (Rivers needs Lakes, Cultures needs Names)
|
||||
|
||||
This trace shows the complete execution flow from button click to final map data generation, with each step clearly identifying the files, functions, data transformations, and object relationships involved in the Fantasy Map Generator's procedural generation process.
|
||||
277
procedural/docs/Port_Project_Plan.md
Normal file
277
procedural/docs/Port_Project_Plan.md
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
# Port Project
|
||||
|
||||
This project is decoupling a tightly-integrated browser application into a core procedural engine and a separate presentation layer. The goal of maintaining full cross-compatibility is not only achievable but is the natural outcome of a well-architected port.
|
||||
|
||||
Recommended design for porting the Fantasy Map Generator to a headless system.
|
||||
|
||||
### High-Level Architectural Vision
|
||||
|
||||
The fundamental flaw of the current architecture for headless operation is the **tight coupling of generation logic and presentation (UI/rendering) logic**. Everything relies on the global `window` object, reads configuration from DOM elements, and directly manipulates SVG with libraries like `d3`.
|
||||
|
||||
Our primary goal is to refactor this into two distinct, well-defined components:
|
||||
|
||||
1. **The FMG Core Engine**: A pure, environment-agnostic JavaScript library. It will contain all the procedural generation logic. It will have no knowledge of browsers, DOM, or SVG. Its only job is to take a configuration object and produce a serializable data object representing the map. This engine will be the heart of both the headless system and the refactored web application.
|
||||
|
||||
2. **The FMG Viewer/Client**: The existing web application, refactored to act as a client to the FMG Core Engine. It will handle user input, manage the SVG canvas, and call renderer modules to visualize the data produced by the engine.
|
||||
|
||||
This separation is the key to achieving your cross-compatibility goal. The serialized data object (the `.map` file) becomes the universal "source of truth" that both systems can produce and consume.
|
||||
|
||||
---
|
||||
|
||||
### Phase 1: Designing the FMG Core Engine
|
||||
|
||||
This is the most critical phase. The engine must be a collection of pure JavaScript modules that can run in any modern JS environment (Node.js, Deno, Bun, or a browser).
|
||||
|
||||
#### 1.1. Input: The Configuration Object
|
||||
|
||||
All functions that currently read from the DOM (`byId("statesNumber").value`, `manorsInput.valueAsNumber`, etc.) must be refactored. The main generation function of the engine will accept a single `config` object.
|
||||
|
||||
**Example `config` object:**
|
||||
|
||||
```javascript
|
||||
const config = {
|
||||
seed: "123456789",
|
||||
graph: {
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
points: 10000
|
||||
},
|
||||
generation: {
|
||||
template: "continents",
|
||||
cultures: 12,
|
||||
culturesSet: "european",
|
||||
states: 10,
|
||||
provincesRatio: 40,
|
||||
manors: 1000,
|
||||
neutralRate: 1.2
|
||||
},
|
||||
display: {
|
||||
populationRate: 10,
|
||||
urbanization: 1,
|
||||
// ... other options from the options panel
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
#### 1.2. Output: The `MapData` Object
|
||||
|
||||
The engine's primary function, let's call it `generateMap(config)`, will return a single, serializable object. This object will contain everything that is currently stored in global variables like `grid`, `pack`, `notes`, `options`, `seed`, etc.
|
||||
|
||||
**Example `MapData` structure:**
|
||||
|
||||
```javascript
|
||||
{
|
||||
meta: {
|
||||
version: "1.9", // FMG version for compatibility
|
||||
generated: new Date().toISOString()
|
||||
},
|
||||
seed: "123456789",
|
||||
config: { /* the config object used for generation */ },
|
||||
grid: { /* grid data */ },
|
||||
pack: { /* pack data */ },
|
||||
notes: [ /* notes array */ ],
|
||||
// ... any other top-level state
|
||||
}
|
||||
```
|
||||
|
||||
#### 1.3. Module Refactoring Strategy
|
||||
|
||||
Each of your provided `.js` files will be converted into an ES module within the engine.
|
||||
|
||||
* **Remove IIFE and `window`:** Replace `window.MyModule = (function () { ... })();` with standard `export` statements.
|
||||
* **Dependency Injection & Pure Functions:** Modules should not rely on or modify a global `pack` or `grid` object. Instead, they should receive the current state of the map data as an argument and return the new data they've generated.
|
||||
|
||||
**Example Refactoring: `biomes.js`**
|
||||
|
||||
**Before (biomes.js):**
|
||||
|
||||
```javascript
|
||||
"use strict";
|
||||
window.Biomes = (function () {
|
||||
// ...
|
||||
function define() {
|
||||
// ... reads from global `pack` and `grid`
|
||||
pack.cells.biome = new Uint8Array(pack.cells.i.length); // Direct modification
|
||||
// ...
|
||||
}
|
||||
return {getDefault, define, getId};
|
||||
})();
|
||||
```
|
||||
|
||||
**After (engine/modules/biomes.js):**
|
||||
|
||||
```javascript
|
||||
"use strict";
|
||||
// No IIFE, no window dependency
|
||||
|
||||
export function getDefaultBiomesData() {
|
||||
// ... returns default biome data
|
||||
}
|
||||
|
||||
export function defineBiomes(pack, grid) {
|
||||
const {fl, r, h, c, g} = pack.cells;
|
||||
const {temp, prec} = grid.cells;
|
||||
const biome = new Uint8Array(pack.cells.i.length);
|
||||
|
||||
for (let cellId = 0; cellId < h.length; cellId++) {
|
||||
// ... calculations ...
|
||||
biome[cellId] = getId(/* ... */);
|
||||
}
|
||||
|
||||
// Return only the data this module is responsible for
|
||||
return { biome };
|
||||
}
|
||||
|
||||
// ... other helper functions (getId, etc.)
|
||||
```
|
||||
|
||||
#### 1.4. The Orchestrator (`engine/main.js`)
|
||||
|
||||
A new "main" module in the engine will orchestrate the entire generation process, calling the refactored modules in the correct sequence and composing the final `MapData` object.
|
||||
|
||||
```javascript
|
||||
// engine/main.js
|
||||
import { generateGrid } from './grid.js';
|
||||
import * as Heightmap from './heightmap.js';
|
||||
import * as Biomes from './biomes.js';
|
||||
// ... import all other engine modules
|
||||
|
||||
export function generateMap(config) {
|
||||
const seed = setSeed(config.seed);
|
||||
|
||||
let grid = generateGrid(config.graph);
|
||||
grid.cells.h = Heightmap.generate(grid, config.generation.template);
|
||||
|
||||
// ... other initial grid steps (features, temperatures, etc.)
|
||||
|
||||
let pack = reGraph(grid); // Assume reGraph is refactored
|
||||
|
||||
// Sequentially build the pack object
|
||||
const { biome } = Biomes.defineBiomes(pack, grid);
|
||||
pack.cells.biome = biome;
|
||||
|
||||
const { cultures, cellsCulture } = Cultures.generate(pack, grid, config);
|
||||
pack.cultures = cultures;
|
||||
pack.cells.culture = cellsCulture;
|
||||
|
||||
// ... continue for states, burgs, rivers, etc.
|
||||
|
||||
return { meta, seed, config, grid, pack, notes: [] };
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 2: Refactoring the Web Application (Viewer)
|
||||
|
||||
The web app becomes a consumer of the Core Engine.
|
||||
|
||||
#### 2.1. Separating Renderers
|
||||
|
||||
All modules with rendering logic (`coa-renderer.js`, `ocean-layers.js`, parts of `routes-generator.js`, `markers-generator.js`) must be moved out of the engine and into a `viewer/renderers/` directory.
|
||||
|
||||
* `COArenderer.draw` is already well-designed for this. It takes a `coa` object and renders it. Perfect.
|
||||
* The `Markers.generate` function should be split. The logic for *selecting candidate cells* (`listVolcanoes`, etc.) goes into the engine. The logic for *drawing the marker icon* (`addMarker` which creates SVG elements) goes into a `viewer/renderers/markers.js` module.
|
||||
* All `d3.select` and direct SVG manipulation must live exclusively in the Viewer.
|
||||
|
||||
#### 2.2. New Web App Workflow
|
||||
|
||||
1. **UI Interaction:** The user changes options in the UI.
|
||||
2. **Build Config:** A function gathers all UI settings and builds the `config` object.
|
||||
3. **Call Engine:** It calls `FMG_Engine.generateMap(config)`. This happens entirely in memory, with no DOM updates.
|
||||
4. **Receive `MapData`:** It receives the complete `MapData` object.
|
||||
5. **Render:** It calls a main `renderMap(MapData)` function, which in turn calls all the specific renderers (`renderBiomes`, `renderStates`, `renderRoutes`, etc.) to draw the SVG.
|
||||
|
||||
---
|
||||
|
||||
### Phase 3: Building the Headless System
|
||||
|
||||
This now becomes incredibly simple. The headless application is just a new entry point that uses the FMG Core Engine.
|
||||
|
||||
#### 3.1. Node.js CLI Application
|
||||
|
||||
A simple command-line tool.
|
||||
|
||||
**`package.json` dependencies:**
|
||||
`"dependencies": { "d3-quadtree": "...", "d3-polygon": "...", "delaunator": "..." }`
|
||||
(Note the absence of browser-specific libraries).
|
||||
|
||||
**`generate.js`:**
|
||||
|
||||
```javascript
|
||||
import { generateMap } from './engine/main.js';
|
||||
import fs from 'fs';
|
||||
|
||||
// Load config from a JSON file passed as an argument
|
||||
const configFile = process.argv[2];
|
||||
const config = JSON.parse(fs.readFileSync(configFile, 'utf8'));
|
||||
|
||||
console.log(`Generating map with seed: ${config.seed}...`);
|
||||
const mapData = generateMap(config);
|
||||
|
||||
// Save the output
|
||||
const outputFile = config.output || `map_${config.seed}.map`;
|
||||
fs.writeFileSync(outputFile, JSON.stringify(mapData));
|
||||
console.log(`Map data saved to ${outputFile}`);
|
||||
```
|
||||
|
||||
You could run it like: `node generate.js my_config.json`
|
||||
|
||||
#### 3.2. REST API Server
|
||||
|
||||
Similarly, you could wrap the engine in a web server (e.g., using Express.js) to provide map generation as a service.
|
||||
|
||||
---
|
||||
|
||||
### Data Persistence and Cross-Compatibility
|
||||
|
||||
The `.map` file is the lynchpin.
|
||||
|
||||
* **Format:** It should be a **JSON serialization of the `MapData` object**. This is human-readable, universally compatible, and simple. For large maps, consider compressing it with Gzip (resulting in a `.map.gz` file), which is standard practice.
|
||||
* **Workflow:**
|
||||
* **Web App Save:** `const mapFileContent = JSON.stringify(currentMapData);` -> User downloads this content as `my_world.map`.
|
||||
* **Headless Generation:** The CLI tool saves the `JSON.stringify(mapData)` output.
|
||||
* **Web App Load:** User uploads a `.map` file -> `const mapData = JSON.parse(fileContent);` -> `renderMap(mapData);`.
|
||||
|
||||
Since both systems use the **exact same FMG Core Engine** to generate the data structure, and they both save/load this structure in the same format (JSON), they are **guaranteed to be cross-compatible**.
|
||||
|
||||
### Proposed Project Structure
|
||||
|
||||
```
|
||||
/fmg
|
||||
├── /src
|
||||
│ ├── /engine # FMG Core Engine (headless, browser-agnostic)
|
||||
│ │ ├── main.js # Orchestrator (generateMap function)
|
||||
│ │ ├── modules/ # Refactored modules (biomes.js, cultures.js, etc.)
|
||||
│ │ └── utils/ # Agnostic utilities (math, array, etc.)
|
||||
│ │
|
||||
│ ├── /viewer # Web Application (UI and rendering)
|
||||
│ │ ├── main.js # Main UI logic, event handlers, orchestrates rendering
|
||||
│ │ ├── renderers/ # SVG rendering modules (mapRenderer.js, coaRenderer.js)
|
||||
│ │ └── ui/ # UI components, dialogs, etc.
|
||||
│ │
|
||||
│ └── /headless # Headless Application
|
||||
│ ├── cli.js # Command-line interface entry point
|
||||
│ └── server.js # (Optional) REST API server entry point
|
||||
│
|
||||
├── /assets # SVGs for charges, icons, etc.
|
||||
├── index.html
|
||||
├── package.json
|
||||
└── ...
|
||||
```
|
||||
|
||||
### Step-by-Step Roadmap
|
||||
|
||||
1. **Setup & Scaffolding:** Create the new project structure. Set up a build process (like Vite or Webpack) to handle modules for the browser.
|
||||
2. **Isolate Utilities:** [x] *DONE* Move all environment-agnostic utility functions (`arrayUtils`, `colorUtils`, `probabilityUtils`, etc.) into `engine/utils`.
|
||||
3. **Create the `config` Object:** Define the structure of the `config` object and modify the web app to build this object from the UI instead of having modules read from the DOM directly.
|
||||
4. **Refactor Incrementally:**
|
||||
* Start with the simplest, most self-contained modules (e.g., `biomes`, `names-generator`). Convert them to the new engine module pattern (take data, return new data).
|
||||
* Create the `engine/main.js` orchestrator and have it call these first few refactored modules.
|
||||
* Modify the `viewer/main.js` to call the new `generateMap` function and then manually merge the results back into the global `pack`/`grid` objects for now.
|
||||
* Separate the corresponding renderer for the refactored module.
|
||||
5. **Iterate:** Continue this process for all modules, one by one. `BurgsAndStates` and `Routes` will be the most complex due to their interdependencies and mixed logic.
|
||||
6. **Build Headless Entry Point:** Once the engine is fully decoupled, create the `headless/cli.js` file. It should be trivial at this point.
|
||||
7. **Finalize Viewer:** Complete the refactoring of the web app to fully rely on the `MapData` object returned by the engine, calling a master `renderMap` function instead of many individual `draw*` functions.
|
||||
|
||||
This phased approach ensures that you can test and validate at each step, maintaining a working application throughout the process while moving methodically toward the final, robust, and portable architecture.
|
||||
52
procedural/docs/findGridCell.md
Normal file
52
procedural/docs/findGridCell.md
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
Looking more carefully at the code flow:
|
||||
|
||||
1. findGridCell vs Voronoi Cell Indices
|
||||
|
||||
The key insight is that findGridCell in graphUtils.js:100-105 is used for coordinate-to-grid mapping, but the
|
||||
actual iteration in heightmap-generator.js happens over different indices.
|
||||
|
||||
2. Actual Iteration Patterns
|
||||
|
||||
In heightmap-generator.js, the problematic iterations are:
|
||||
|
||||
- Line 165: for (const c of grid.cells.c[q]) - but q comes from queue.shift() where queue starts with [start]
|
||||
- Line 209: grid.cells.c[q].forEach(...) - same pattern
|
||||
- Line 268: grid.cells.c[cur].forEach(...) - cur is tracked through valid cell traversal
|
||||
- Line 516: grid.cells.c[i].forEach(...) - i is the map index over heights
|
||||
|
||||
3. The Critical Constraint
|
||||
|
||||
Looking at voronoi.js:22-27, cells.c[p] is only populated when:
|
||||
if (p < this.pointsN && !this.cells.c[p]) {
|
||||
// populate cells.c[p]
|
||||
}
|
||||
|
||||
4. Why No Errors Occur
|
||||
|
||||
The code works because:
|
||||
|
||||
1. heights array length matches valid cells: In the original ../modules/heightmap-generator.js:11, heights is created with length:
|
||||
points.length, which equals pointsN
|
||||
```javascript
|
||||
const setGraph = graph => {
|
||||
const {cellsDesired, cells, points} = graph;
|
||||
heights = cells.h ? Uint8Array.from(cells.h) : createTypedArray({maxValue: 100, length: points.length});
|
||||
blobPower = getBlobPower(cellsDesired);
|
||||
linePower = getLinePower(cellsDesired);
|
||||
grid = graph;
|
||||
};
|
||||
```
|
||||
1. Queue propagation stays within bounds: When iterating grid.cells.c[q], the q values come from:
|
||||
- Initial valid starting points from findGridCell
|
||||
- Subsequent values from grid.cells.c[previous_q] which only contains valid adjacent cell indices
|
||||
2. Voronoi adjacency constraint: The cells.c[p] arrays only contain indices of valid neighboring cells (<
|
||||
pointsN), so the iteration naturally stays within the populated sparse array bounds
|
||||
3. Map iteration bounds: In line 468's heights.map((h, i) => ...), i ranges from 0 to heights.length-1, which
|
||||
equals pointsN-1, so grid.cells.c[i] is always within the populated range.
|
||||
|
||||
The sparse array works because the algorithm's traversal patterns are naturally constrained to only access
|
||||
indices that were populated during Voronoi construction.
|
||||
|
||||
## TASKS
|
||||
|
||||
1. analyze ../modules heightmap-generator.js and src/engine/modules/heightmap-generator.js to determine why the logic has deviated. Produce docs/HEIGHTMAP_ASSESSMENT.md with findings and recommendations
|
||||
Loading…
Add table
Add a link
Reference in a new issue