Memory-Enhanced Cellular Automata

January 31, 2026

Memory-Enhanced Cellular Automata

A probabilistic cellular automata implementation where each cell maintains a 3-state memory of its recent history. Cells use their own history plus neighbor influence to determine state transitions.

Key Features

  • Memory Decay: Each cell remembers its last 3 states with exponential weight decay
  • Energy System: Cells have energy levels that influence activation probability
  • Neighbor Influence: History-weighted influence from surrounding cells
  • Emergent Patterns: Evolves from sparse random initialization toward complex structures

Implementation

// Probabilistic cellular automata with memory decay
// Each cell remembers its last 3 states and uses them to influence neighbors

interface Cell {
  current: boolean;
  history: boolean[];
  energy: number;
}

class MemoryAutomata {
  private grid: Cell[][];
  private width: number;
  private height: number;
  private generation: number = 0;

  constructor(width: number, height: number) {
    this.width = width;
    this.height = height;
    this.grid = this.initializeGrid();
  }

  private initializeGrid(): Cell[][] {
    const grid: Cell[][] = [];
    for (let y = 0; y < this.height; y++) {
      grid[y] = [];
      for (let x = 0; x < this.width; x++) {
        grid[y][x] = {
          current: Math.random() > 0.7,
          history: [false, false, false],
          energy: Math.random()
        };
      }
    }
    return grid;
  }

  private getNeighborInfluence(x: number, y: number): number {
    let influence = 0;
    let count = 0;

    for (let dy = -1; dy <= 1; dy++) {
      for (let dx = -1; dx <= 1; dx++) {
        if (dx === 0 && dy === 0) continue;

        const nx = (x + dx + this.width) % this.width;
        const ny = (y + dy + this.height) % this.height;

        const neighbor = this.grid[ny][nx];

        // Weight recent history more heavily
        const historyWeight = neighbor.history.reduce((sum, state, i) =>
          sum + (state ? 1 : 0) * (0.8 ** i), 0);

        influence += neighbor.current ? 1 : 0;
        influence += historyWeight * 0.3;
        influence += neighbor.energy * 0.1;
        count++;
      }
    }

    return count > 0 ? influence / count : 0;
  }

  step(): void {
    const newGrid: Cell[][] = this.grid.map(row =>
      row.map(cell => ({
        current: cell.current,
        history: [...cell.history],
        energy: cell.energy
      }))
    );

    for (let y = 0; y < this.height; y++) {
      for (let x = 0; x < this.width; x++) {
        const cell = this.grid[y][x];
        const influence = this.getNeighborInfluence(x, y);

        // Update history (shift left, add current to front)
        newGrid[y][x].history.unshift(cell.current);
        newGrid[y][x].history = newGrid[y][x].history.slice(0, 3);

        // Probabilistic rule based on influence and energy
        const activationProbability = (influence * 0.6) + (cell.energy * 0.4);
        const threshold = 0.3 + Math.sin(this.generation * 0.1) * 0.2;

        newGrid[y][x].current = activationProbability > threshold;

        // Energy decay and renewal
        newGrid[y][x].energy *= 0.95;
        if (newGrid[y][x].current) {
          newGrid[y][x].energy = Math.min(1, newGrid[y][x].energy + 0.3);
        }
      }
    }

    this.grid = newGrid;
    this.generation++;
  }

  render(): string {
    let output = `Generation ${this.generation}\n`;
    output += '┌' + '─'.repeat(this.width) + '┐\n';

    for (let y = 0; y < this.height; y++) {
      output += '│';
      for (let x = 0; x < this.width; x++) {
        const cell = this.grid[y][x];
        if (cell.current) {
          output += '█';
        } else if (cell.energy > 0.5) {
          output += '▓';
        } else if (cell.history[0]) {
          output += '░';
        } else {
          output += ' ';
        }
      }
      output += '│\n';
    }

    output += '└' + '─'.repeat(this.width) + '┘\n';
    return output;
  }

  getStats(): { alive: number; energy: number; memory: number } {
    let alive = 0;
    let totalEnergy = 0;
    let memoryStates = 0;

    for (let y = 0; y < this.height; y++) {
      for (let x = 0; x < this.width; x++) {
        const cell = this.grid[y][x];
        if (cell.current) alive++;
        totalEnergy += cell.energy;
        memoryStates += cell.history.filter(Boolean).length;
      }
    }

    return {
      alive,
      energy: totalEnergy / (this.width * this.height),
      memory: memoryStates / (this.width * this.height * 3)
    };
  }
}

// Demo runner
async function runDemo() {
  console.log("Memory-Enhanced Cellular Automata");
  console.log("=================================");

  const automata = new MemoryAutomata(20, 12);

  for (let i = 0; i < 10; i++) {
    console.clear();
    console.log(automata.render());

    const stats = automata.getStats();
    console.log(`Alive: ${stats.alive} | Avg Energy: ${stats.energy.toFixed(2)} | Memory Density: ${stats.memory.toFixed(2)}`);
    console.log("Press Ctrl+C to stop");

    automata.step();

    // Pause for visualization
    await new Promise(resolve => setTimeout(resolve, 500));
  }
}

// Export for use
export { MemoryAutomata, runDemo };

// Run if called directly
if (import.meta.main) {
  runDemo().catch(console.error);
}

Observed Behavior

Starting from sparse random initialization (30% activation probability), the system exhibits interesting emergent properties:

  1. Rapid Filling: Memory-influenced activation leads to quick propagation
  2. Energy Saturation: Cells maintain high energy when consistently active
  3. Memory Density Growth: Historical states accumulate, creating stable patterns
  4. Steady State: Eventually reaches full activation with maximum memory

The interplay between memory, energy, and neighbor influence creates richer dynamics than traditional Conway's Game of Life rules.