Fantasy-Map-Generator/procedural/docs/HEIGHTMAP_ASSESSMENT.md
2025-08-05 13:12:07 -04:00

10 KiB

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:

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:

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)

// 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

// 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

// 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)
// 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:

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

Restore original naming but keep functional returns:

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:

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 graphgrid 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.