# Algorithmic Brushes - Complete Guide

This directory contains JavaScript brush scripts for Pixcen's Algorithmic Brush tool. Algorithmic brushes allow you to create dynamic, procedurally-generated brush patterns that can respond to position, animation frames, user parameters, and more.

## Table of Contents

1. [Quick Start](#quick-start)
2. [Brush Structure](#brush-structure)
3. [Metadata Fields](#metadata-fields)
4. [Parameters](#parameters)
5. [The Generate Function](#the-generate-function)
6. [Return Value](#return-value)
7. [Context Parameters](#context-parameters)
8. [Advanced Techniques](#advanced-techniques)
9. [Examples](#examples)
10. [Best Practices](#best-practices)
11. [Troubleshooting](#troubleshooting)

---

## Quick Start

Create a new `.js` file in this directory with the following minimal structure:

```javascript
const brush = {
    name: "My Brush",
    description: "A simple brush example",
    category: "basic",
    version: "1.0.0",
    author: "Your Name",
    
    generate: function(width, height, frame, x, y, color) {
        const pixels = [];
        const mask = [];
        
        for (let py = 0; py < height; py++) {
            const pixelRow = [];
            const maskRow = [];
            for (let px = 0; px < width; px++) {
                pixelRow.push(color);
                maskRow.push(true);
            }
            pixels.push(pixelRow);
            mask.push(maskRow);
        }
        
        return { width, height, pixels, mask };
    }
};

pixcen.registerBrush(brush);
```

Save the file, restart Pixcen, and select **Tools → Algorithmic Brush** to use your new brush!

---

## Brush Structure

Every brush script must define a brush object and register it with `pixcen.registerBrush()`. The brush object contains:

- **Metadata** (required): `name`, `description`, `category`, `version`, `author`
- **Preview Settings** (optional): `previewAnimateOnMove`
- **Parameters** (optional): `params` object with user-configurable settings
- **Generate Function** (required): `generate()` function that creates the brush pattern

```javascript
const brush = {
    // === METADATA (Required) ===
    name: "Brush Name",
    description: "What this brush does",
    category: "procedural",
    version: "1.0.0",
    author: "Your Name",
    
    // === PREVIEW SETTINGS (Optional) ===
    previewAnimateOnMove: false,  // See "Preview Behavior" section
    
    // === PARAMETERS (Optional) ===
    params: {
        // Parameter definitions here
    },
    
    // === GENERATE FUNCTION (Required) ===
    generate: function(width, height, frame, x, y, color) {
        // Brush generation code here
    }
};

pixcen.registerBrush(brush);
```

---

## Metadata Fields

### Required Fields

- **`name`** (string): Display name shown in the brush gallery
- **`description`** (string): Tooltip/help text describing the brush
- **`category`** (string): Organization category (see [Categories](#categories) below)
- **`version`** (string): Version number (e.g., "1.0.0")
- **`author`** (string): Your name or handle

### Optional Fields

- **`previewAnimateOnMove`** (boolean, default: `false`): Controls preview behavior
  - `false`: Preview shows frame 0 (static preview)
  - `true`: Preview updates as cursor moves (useful for position-aware brushes like gradients)

### Categories

Use these standard categories for consistent organization:

- **`"basic"`**: Simple, fundamental brushes (circles, squares, solid fills)
- **`"procedural"`**: Mathematically generated patterns (mazes, noise, fractals)
- **`"textured"`**: Noise-based textures and patterns
- **`"particle"`**: Particle system simulations
- **`"organic"`**: Natural/organic patterns (cells, clouds, terrain)

---

## Parameters

Parameters allow users to customize brush behavior. They appear in the brush parameters dialog when the brush is selected.

### Parameter Types

#### Number Parameters

For numeric values with min/max constraints:

```javascript
params: {
    speed: {
        type: "number",
        default: 5,
        label: "Animation Speed",
        min: 1,
        max: 10
    },
    size: {
        type: "number",
        default: 50,
        label: "Size (%)",
        min: 10,
        max: 100
    }
}
```

**Access in `generate()`:**
```javascript
const speed = brush.params.speed.default;
const size = brush.params.size.default;
```

#### Color Parameters

For C64 color selection (0-15):

```javascript
params: {
    starColor: {
        type: "color",
        default: 1,  // White (C64 color index)
        label: "Star Color"
    }
}
```

**Access in `generate()`:**
```javascript
const starColor = brush.params.starColor.default;  // Returns 0-15
```

### Parameter Access Pattern

All parameters are accessed via `brush.params.paramName.default`. The `default` property contains the current user-selected value (which may differ from the initial default).

**Important:** Always read parameter values inside the `generate()` function, as they may change between calls when the user adjusts settings.

---

## The Generate Function

The `generate()` function is called every time the brush paints a pixel. It must be fast and deterministic.

### Function Signature

```javascript
generate: function(width, height, frame, x, y, color) {
    // width: Requested brush width in pixels (1-64)
    // height: Requested brush height in pixels (1-64)
    // frame: Animation frame counter (0, 1, 2, ...)
    // x: Canvas X coordinate where brush center will be painted
    // y: Canvas Y coordinate where brush center will be painted
    // color: Current foreground color (0-15, C64 palette)
    
    // Return brush data object
}
```

### Implementation Pattern

```javascript
generate: function(width, height, frame, x, y, color) {
    // 1. Read parameters
    const speed = brush.params.speed.default;
    
    // 2. Initialize output arrays
    const pixels = [];
    const mask = [];
    
    // 3. Generate brush pattern
    for (let py = 0; py < height; py++) {
        const pixelRow = [];
        const maskRow = [];
        
        for (let px = 0; px < width; px++) {
            // Calculate pixel color and transparency
            const colorIndex = /* your logic */;
            const isOpaque = /* your logic */;
            
            pixelRow.push(colorIndex);  // 0-15
            maskRow.push(isOpaque);     // true/false
        }
        
        pixels.push(pixelRow);
        mask.push(maskRow);
    }
    
    // 4. Return brush data
    return {
        width: width,
        height: height,
        pixels: pixels,
        mask: mask
    };
}
```

---

## Return Value

The `generate()` function **must** return an object with these exact properties:

```javascript
{
    width: number,      // Actual brush width (typically same as input width)
    height: number,     // Actual brush height (typically same as input height)
    pixels: number[][], // 2D array [y][x] of C64 color indices (0-15)
    mask: boolean[][]   // 2D array [y][x] of transparency flags
}
```

### Return Value Details

- **`width`** / **`height`**: Usually return the same values passed in. Must be 1-320 for width, 1-200 for height.
- **`pixels`**: 2D array where `pixels[y][x]` is the color index (0-15) for that pixel
- **`mask`**: 2D array where `mask[y][x]` is `true` to paint the pixel, `false` to make it transparent

**Array Structure:**
- Arrays are indexed as `[row][column]` or `[y][x]`
- First index is the row (Y coordinate), second is the column (X coordinate)
- `pixels[0][0]` is the top-left pixel
- `pixels[height-1][width-1]` is the bottom-right pixel

---

## Context Parameters

The `generate()` function receives context about the painting operation:

### `width` / `height`

- Requested brush size in pixels
- Typically 1-64 pixels (user-configurable)
- Use these to determine brush bounds and center point

**Example:**
```javascript
const centerX = Math.floor(width / 2);
const centerY = Math.floor(height / 2);
const maxRadius = Math.min(centerX, centerY);
```

### `frame`

- Animation frame counter (integer, starts at 0)
- Increments by 1 on each brush imprint
- **Does NOT reset** between strokes (continues counting)
- Use for animated effects, time-based patterns, or deterministic randomness

**Example - Animated Pulse:**
```javascript
const pulseValue = 0.5 + 0.5 * Math.sin(frame * 0.1);
const radius = maxRadius * pulseValue;
```

**Example - Deterministic Randomness:**
```javascript
// Seed RNG with frame only for consistent results
// DO NOT use x,y in the seed - this causes preview to change on cursor movement
let rng = frame * 12345;
function random() {
    rng = (rng * 1103515245 + 12345) % 2147483648;
    return rng / 2147483648;
}
```

**Frame Navigation:**
- Users can navigate frames manually with **Ctrl+Comma** (previous) and **Ctrl+Period** (next)
- Frame counter persists across painting sessions until brush is changed

### `x` / `y`

- Canvas coordinates where the brush center will be painted
- Use for position-aware effects (gradients, patterns that vary by location)

**Example - Position-Based Gradient:**
```javascript
// Calculate global pixel position
const globalX = x + px - centerX;
const globalY = y + py - centerY;
const distFromOrigin = Math.sqrt(globalX * globalX + globalY * globalY);
const colorIndex = Math.floor(distFromOrigin / scale) % 16;
```

### `color`

- Current foreground color (0-15, C64 palette index)
- Use this for brushes that should respect the selected color

**Example:**
```javascript
// Use selected color directly
pixelRow.push(color);

// Or blend with parameter color
const brushColor = brush.params.brushColor.default;
pixelRow.push((color + brushColor) % 16);
```

---

## Advanced Techniques

### Deterministic Randomness

For consistent, reproducible patterns, use a seeded random number generator:

```javascript
// Seed based ONLY on frame - DO NOT use x,y in the seed
// Using x,y causes the preview to change as cursor moves
let rng = frame * 12345;

function lcgRandom() {
    rng = (rng * 1103515245 + 12345) % 2147483648;
    return rng / 2147483648;
}

// Use in generation
if (lcgRandom() < density) {
    pixelRow.push(starColor);
    maskRow.push(true);
}
```

**Important:** The `x` and `y` parameters are for **position-aware effects** (like gradients that vary based on canvas location), **NOT for seeding random number generators**. Using x,y in RNG seeding causes the brush preview to change every time the cursor moves, which is undesirable for most brushes.

### Soft Edges / Anti-Aliasing

Create smooth brush edges using distance-based alpha:

```javascript
const dist = Math.sqrt(dx * dx + dy * dy);
const edgeSoftness = 2.0;
const alpha = Math.max(0, Math.min(1, 1.0 - (dist - radius) / edgeSoftness));

if (alpha > 0) {
    pixelRow.push(color);
    maskRow.push(alpha > 0.5);  // Threshold for visibility
}
```

### Complex Algorithms

For complex patterns (mazes, noise, fractals), break generation into helper functions:

```javascript
generate: function(width, height, frame, x, y, color) {
    // Initialize arrays
    const pixels = [];
    const mask = [];
    // ... initialize ...
    
    // Generate pattern using helper functions
    const pattern = generateMaze(width, height, frame, x, y);
    
    // Convert pattern to pixels
    for (let py = 0; py < height; py++) {
        for (let px = 0; px < width; px++) {
            if (pattern[py][px]) {
                pixelRow.push(color);
                maskRow.push(true);
            } else {
                pixelRow.push(0);
                maskRow.push(false);
            }
        }
    }
    
    return { width, height, pixels, mask };
}

function generateMaze(width, height, frame, x, y) {
    // Complex algorithm here
    // Return 2D boolean array
}
```

### Performance Optimization

- **Pre-calculate constants** outside loops
- **Use integer math** when possible (avoid floating-point in tight loops)
- **Cache repeated calculations** (e.g., `Math.sqrt()` results)
- **Minimize array allocations** (reuse arrays when possible)

```javascript
// Good: Pre-calculate
const centerX = Math.floor(width / 2);
const centerY = Math.floor(height / 2);
const maxRadiusSq = centerX * centerX;  // Avoid sqrt in loop

for (let py = 0; py < height; py++) {
    const dy = py - centerY;
    const dySq = dy * dy;
    for (let px = 0; px < width; px++) {
        const dx = px - centerX;
        const distSq = dx * dx + dySq;  // Compare squared distance
        if (distSq <= maxRadiusSq) {
            // Inside circle
        }
    }
}
```

---

## Examples

### Example 1: Simple Solid Circle

```javascript
const brush = {
    name: "Solid Circle",
    description: "A simple filled circle brush",
    category: "basic",
    version: "1.0.0",
    author: "Your Name",
    
    generate: function(width, height, frame, x, y, color) {
        const centerX = Math.floor(width / 2);
        const centerY = Math.floor(height / 2);
        const radius = Math.min(centerX, centerY);
        
        const pixels = [];
        const mask = [];
        
        for (let py = 0; py < height; py++) {
            const pixelRow = [];
            const maskRow = [];
            const dy = py - centerY;
            
            for (let px = 0; px < width; px++) {
                const dx = px - centerX;
                const dist = Math.sqrt(dx * dx + dy * dy);
                
                if (dist <= radius) {
                    pixelRow.push(color);
                    maskRow.push(true);
                } else {
                    pixelRow.push(0);
                    maskRow.push(false);
                }
            }
            
            pixels.push(pixelRow);
            mask.push(maskRow);
        }
        
        return { width, height, pixels, mask };
    }
};

pixcen.registerBrush(brush);
```

### Example 2: Animated Pulsing Circle

```javascript
const brush = {
    name: "Pulse",
    description: "Circular brush that pulses with each stroke",
    category: "procedural",
    version: "1.0.0",
    author: "Your Name",
    previewAnimateOnMove: false,
    
    params: {
        speed: {
            type: "number",
            default: 3,
            label: "Pulse Speed",
            min: 1,
            max: 10
        },
        minSize: {
            type: "number",
            default: 50,
            label: "Min Size (%)",
            min: 10,
            max: 90
        }
    },
    
    generate: function(width, height, frame, x, y, color) {
        const speed = brush.params.speed.default;
        const minSize = brush.params.minSize.default / 100.0;
        
        const centerX = Math.floor(width / 2);
        const centerY = Math.floor(height / 2);
        const maxRadius = Math.min(centerX, centerY);
        
        // Pulse effect using frame counter
        const pulseValue = 0.5 + 0.5 * Math.sin(frame * speed * 0.1);
        const currentRadius = maxRadius * (minSize + (1.0 - minSize) * pulseValue);
        
        const pixels = [];
        const mask = [];
        
        for (let py = 0; py < height; py++) {
            const pixelRow = [];
            const maskRow = [];
            const dy = py - centerY;
            
            for (let px = 0; px < width; px++) {
                const dx = px - centerX;
                const dist = Math.sqrt(dx * dx + dy * dy);
                
                if (dist <= currentRadius) {
                    pixelRow.push(color);
                    maskRow.push(true);
                } else {
                    pixelRow.push(0);
                    maskRow.push(false);
                }
            }
            
            pixels.push(pixelRow);
            mask.push(maskRow);
        }
        
        return { width, height, pixels, mask };
    }
};

pixcen.registerBrush(brush);
```

### Example 3: Position-Aware Gradient

```javascript
const brush = {
    name: "Position Gradient",
    description: "Creates gradients based on canvas position",
    category: "procedural",
    version: "1.0.0",
    author: "Your Name",
    previewAnimateOnMove: true,  // Preview updates on cursor move
    
    params: {
        colorRange: {
            type: "number",
            default: 8,
            label: "Color Range",
            min: 2,
            max: 16
        },
        scale: {
            type: "number",
            default: 20,
            label: "Gradient Scale",
            min: 5,
            max: 50
        }
    },
    
    generate: function(width, height, frame, x, y, color) {
        const colorRange = brush.params.colorRange.default;
        const scale = brush.params.scale.default;
        
        const centerX = Math.floor(width / 2);
        const centerY = Math.floor(height / 2);
        const maxRadius = Math.min(centerX, centerY);
        
        const pixels = [];
        const mask = [];
        
        for (let py = 0; py < height; py++) {
            const pixelRow = [];
            const maskRow = [];
            const dy = py - centerY;
            
            for (let px = 0; px < width; px++) {
                const dx = px - centerX;
                const dist = Math.sqrt(dx * dx + dy * dy);
                
                // Only paint within circular brush shape
                if (dist <= maxRadius) {
                    // Use canvas position for gradient
                    const globalX = x + px - centerX;
                    const globalY = y + py - centerY;
                    const distFromOrigin = Math.sqrt(globalX * globalX + globalY * globalY);
                    const colorIndex = Math.floor(distFromOrigin / scale) % colorRange;
                    
                    pixelRow.push(colorIndex);
                    maskRow.push(true);
                } else {
                    pixelRow.push(0);
                    maskRow.push(false);
                }
            }
            
            pixels.push(pixelRow);
            mask.push(maskRow);
        }
        
        return { width, height, pixels, mask };
    }
};

pixcen.registerBrush(brush);
```

### Example 4: Random Starfield

```javascript
const brush = {
    name: "Starfield",
    description: "Generates random stars with varying sizes",
    category: "procedural",
    version: "1.0.0",
    author: "Your Name",
    
    params: {
        density: {
            type: "number",
            default: 20,
            label: "Star Density (%)",
            min: 5,
            max: 50
        },
        brightness: {
            type: "color",
            default: 1,
            label: "Star Color"
        }
    },
    
    generate: function(width, height, frame, x, y, color) {
        const density = brush.params.density.default;
        const starColor = brush.params.brightness.default;
        
        const pixels = [];
        const mask = [];
        
        // Deterministic RNG seeded ONLY by frame
        // DO NOT use x,y - that causes preview to change on cursor movement
        let seed = frame * 12345;
        
        function pseudoRandom() {
            seed = (seed * 1103515245 + 12345) % 2147483648;
            return seed / 2147483648;
        }
        
        for (let py = 0; py < height; py++) {
            const pixelRow = [];
            const maskRow = [];
            
            for (let px = 0; px < width; px++) {
                const rand = pseudoRandom();
                
                if (rand < density / 100.0) {
                    pixelRow.push(starColor);
                    maskRow.push(true);
                } else {
                    pixelRow.push(0);
                    maskRow.push(false);
                }
            }
            
            pixels.push(pixelRow);
            mask.push(maskRow);
        }
        
        return { width, height, pixels, mask };
    }
};

pixcen.registerBrush(brush);
```

---

## Best Practices

### Performance

1. **Keep `generate()` fast** - Aim for < 16ms execution time for smooth painting
2. **Pre-calculate constants** - Move calculations outside loops when possible
3. **Use integer math** - Avoid floating-point operations in tight loops
4. **Minimize allocations** - Reuse arrays and objects when possible

### Code Quality

1. **Validate inputs** - Check that width/height are reasonable
2. **Handle edge cases** - Test with small (8×8) and large (64×64) brush sizes
3. **Use meaningful names** - Clear variable names improve readability
4. **Add comments** - Document complex algorithms and non-obvious logic

### User Experience

1. **Provide good defaults** - Parameters should have sensible default values
2. **Use clear labels** - Parameter labels should be self-explanatory
3. **Set appropriate ranges** - Min/max values should prevent invalid inputs
4. **Write helpful descriptions** - Explain what the brush does and when to use it

### Determinism

1. **Use seeded randomness** - For reproducible results, seed RNG with frame/position
2. **Avoid global state** - Don't rely on variables outside the `generate()` function
3. **Test consistency** - Same inputs should produce same outputs

---

## Error Handling & Debugging

Robust error handling ensures your brushes fail gracefully and provide useful feedback when problems occur.

### The Logging API

Pixcen provides a logging API accessible via `pixcen.log` that writes messages to the Debug Output window:

```javascript
pixcen.log.trace("Detailed trace message");  // Very detailed debugging
pixcen.log.debug("Debug information");        // Development debugging
pixcen.log.info("Informational message");     // General information
pixcen.log.warn("Warning message");           // Non-critical issues
pixcen.log.error("Error message");            // Errors and failures
```

**Viewing Logs:**
- Open **Tools → Debug Output** to see logged messages
- Messages are timestamped and categorized by level
- `[Script]` prefix identifies messages from JavaScript

### Error Handling Pattern

Wrap your `generate()` function in a try-catch block and validate inputs:

```javascript
generate: function(width, height, frame, x, y, color) {
    try {
        // 1. Validate inputs
        if (width < 1 || height < 1) {
            pixcen.log.error("My Brush: Invalid dimensions: " + width + "x" + height);
            return null;
        }

        if (width > 320 || height > 200) {
            pixcen.log.warn("My Brush: Large dimensions (" + width + "x" + height + ") may affect performance");
        }

        // 2. Validate parameters
        const scale = brush.params.scale.default;
        if (scale <= 0) {
            pixcen.log.error("My Brush: Invalid scale value: " + scale);
            return null;
        }

        // 3. Your brush generation code
        // ...

        // 4. Return valid brush data
        return {
            width: width,
            height: height,
            pixels: pixels,
            mask: mask
        };

    } catch (e) {
        // Catch unexpected errors
        pixcen.log.error("My Brush: Exception in generate(): " + e.message);
        return null;  // Return null to signal error
    }
}
```

### Returning null on Error

When an error occurs, **return `null`** instead of throwing an exception:
- The C++ host handles null returns gracefully
- An error indicator will be shown on the canvas
- The error message is logged for debugging
- Painting can continue with other strokes

**Never throw exceptions** from `generate()`—they will halt brush execution.

### Common Validations

#### Input Validation
```javascript
// Check dimensions
if (width < 1 || height < 1 || width > 320 || height > 200) {
    pixcen.log.error("Brush: Invalid dimensions");
    return null;
}

// Check color is valid
if (color < 0 || color > 15) {
    pixcen.log.error("Brush: Invalid color index: " + color);
    return null;
}
```

#### Parameter Validation
```javascript
// Check numeric parameter range
const density = brush.params.density.default;
if (density < 0 || density > 100) {
    pixcen.log.error("Brush: Density out of range: " + density);
    return null;
}

// Check parameter exists
if (!brush.params.scale) {
    pixcen.log.error("Brush: Missing required parameter 'scale'");
    return null;
}
```

#### API Call Validation
```javascript
// Check API returns valid data
const rangeColors = pixcen.colors.getColorRangeColors(rangeIndex);
if (!rangeColors || rangeColors.length === 0) {
    pixcen.log.error("Brush: No colors in selected range " + rangeIndex);
    return null;
}
```

### Debugging Tips

#### 1. Add Debug Logging

Use `pixcen.log.debug()` to trace execution:

```javascript
generate: function(width, height, frame, x, y, color) {
    pixcen.log.debug("Generate called: " + width + "x" + height + " frame=" + frame);

    const density = brush.params.density.default;
    pixcen.log.debug("Density parameter: " + density);

    // ... rest of code ...
}
```

#### 2. Log Parameter Values

When parameters seem wrong:

```javascript
const scale = brush.params.scale.default;
pixcen.log.debug("Scale: " + scale + " (min:" + brush.params.scale.min + " max:" + brush.params.scale.max + ")");
```

#### 3. Validate Array Dimensions

Before returning, verify your arrays are correct:

```javascript
if (pixels.length !== height) {
    pixcen.log.error("Brush: pixels array has wrong height: " + pixels.length + " expected: " + height);
    return null;
}

for (let py = 0; py < height; py++) {
    if (pixels[py].length !== width) {
        pixcen.log.error("Brush: pixels row " + py + " has wrong width: " + pixels[py].length);
        return null;
    }
}
```

#### 4. Check for NaN and Infinity

Math errors can produce invalid values:

```javascript
const result = Math.sqrt(value);
if (isNaN(result) || !isFinite(result)) {
    pixcen.log.error("Brush: Math error - invalid result: " + result);
    return null;
}
```

### Performance Monitoring

Log performance warnings for slow operations:

```javascript
generate: function(width, height, frame, x, y, color) {
    const startTime = Date.now();

    // ... your generation code ...

    const elapsed = Date.now() - startTime;
    if (elapsed > 16) {  // Longer than one frame at 60fps
        pixcen.log.warn("Brush: Slow generation (" + elapsed + "ms) for " + width + "x" + height);
    }

    return { width, height, pixels, mask };
}
```

### Example: Complete Error-Safe Brush

```javascript
const brush = {
    name: "Error-Safe Example",
    description: "Demonstrates comprehensive error handling",
    category: "basic",
    version: "1.0.0",
    author: "Your Name",

    params: {
        density: {
            type: "number",
            default: 50,
            label: "Density (%)",
            min: 0,
            max: 100
        }
    },

    generate: function(width, height, frame, x, y, color) {
        try {
            // Validate inputs
            if (width < 1 || height < 1) {
                pixcen.log.error("Error-Safe: Invalid dimensions: " + width + "x" + height);
                return null;
            }

            if (width > 320 || height > 200) {
                pixcen.log.warn("Error-Safe: Large dimensions may affect performance");
            }

            // Validate parameters
            const density = brush.params.density.default;
            if (density < 0 || density > 100) {
                pixcen.log.error("Error-Safe: Density out of range: " + density);
                return null;
            }

            // Validate color
            if (color < 0 || color > 15) {
                pixcen.log.error("Error-Safe: Invalid color index: " + color);
                return null;
            }

            // Initialize arrays
            const pixels = [];
            const mask = [];

            // Generate pattern
            for (let py = 0; py < height; py++) {
                const pixelRow = [];
                const maskRow = [];

                for (let px = 0; px < width; px++) {
                    // Simple pattern generation
                    const shouldPaint = (px + py) % 2 === 0;
                    pixelRow.push(shouldPaint ? color : 0);
                    maskRow.push(shouldPaint);
                }

                pixels.push(pixelRow);
                mask.push(maskRow);
            }

            // Final validation before return
            if (pixels.length !== height) {
                pixcen.log.error("Error-Safe: Generated wrong number of rows");
                return null;
            }

            return {
                width: width,
                height: height,
                pixels: pixels,
                mask: mask
            };

        } catch (e) {
            pixcen.log.error("Error-Safe: Unexpected error: " + e.message);
            if (e.stack) {
                pixcen.log.debug("Stack trace: " + e.stack);
            }
            return null;
        }
    }
};

pixcen.registerBrush(brush);
```

### Best Practices for Error Handling

1. **Always use try-catch** - Wrap the entire `generate()` function body
2. **Validate all inputs** - Check dimensions, colors, and parameters
3. **Return null on error** - Never throw exceptions or return invalid data
4. **Log descriptive messages** - Include context like parameter values and dimensions
5. **Use appropriate log levels**:
   - `error` for failures that prevent brush generation
   - `warn` for issues that might affect quality/performance
   - `debug` for development/troubleshooting information
6. **Prefix log messages** - Use your brush name so messages are identifiable
7. **Check API responses** - Validate data from `pixcen.colors.*` and other APIs

---

## Troubleshooting

### Brush Not Appearing

- **Check file location**: Must be in `scripts/brushes/` directory
- **Check syntax**: JavaScript syntax errors prevent loading
- **Check registration**: Must call `pixcen.registerBrush(brush)` at the end
- **Restart Pixcen**: Scripts are loaded at startup

### Brush Not Generating Correctly

- **Check return value**: Must return `{ width, height, pixels, mask }`
- **Check array dimensions**: `pixels` and `mask` must be `height × width`
- **Check array indexing**: Use `pixels[y][x]` not `pixels[x][y]`
- **Check color values**: Must be integers 0-15

### Performance Issues

- **Profile your code**: Identify slow operations
- **Optimize loops**: Reduce calculations inside nested loops
- **Cache results**: Store repeated calculations
- **Simplify algorithms**: Consider simpler approaches for real-time generation

### Parameter Issues

- **Check parameter access**: Use `brush.params.paramName.default`
- **Check parameter types**: Number vs Color must match definition
- **Check default values**: Ensure defaults are within min/max range

### Preview Issues

- **Check `previewAnimateOnMove`**: Set to `true` only if preview should update on cursor move
- **Check frame usage**: Preview uses current frame counter (can be navigated with Ctrl+Comma/Ctrl+Period)

---

## Included Brushes

Pixcen comes with several example brushes:

- **gradient.js**: Position-based color gradients
- **pulse.js**: Animated pulsing circle
- **starfield.js**: Random starfield generator
- **maze.js**: Procedural maze generator
- **particle-burst.js**: Physics-based particle explosion
- **lightning.js**: Procedural lightning bolts
- **wave-interference.js**: Wave interference patterns
- **spiral-galaxy.js**: Rotating spiral galaxy
- **plasma.js**: Classic demoscene plasma effect
- **voronoi.js**: Voronoi diagram patterns
- **perlin-noise.js**: Perlin noise terrain
- **3d-objects.js**: Rotating 3D wireframe objects

Study these examples to learn advanced techniques!

---

## Keyboard Shortcuts

When using an algorithmic brush:

- **Ctrl+Comma (Ctrl+,)**: Navigate to previous frame (decrease frame counter)
- **Ctrl+Period (Ctrl+.)**: Navigate to next frame (increase frame counter)

These shortcuts allow you to preview different animation frames before painting.

---

## Creating Your Own Brush

1. **Create a new `.js` file** in `scripts/brushes/` directory
2. **Follow the structure** outlined in this guide
3. **Test your brush** with various sizes and parameters
4. **Restart Pixcen** to load your brush
5. **Select "Algorithmic Brush"** from the Tools menu
6. **Your brush will appear** in the brush gallery

Happy brushing! 🎨
