# Architecture

This document describes LifeGrid's internal architecture and module relationships.

---

## High-Level Overview

```
┌──────────────────────────────────────────────────┐
│                  Entry Points                    │
│  main.py (GUI)   cli.py (CLI)   api/app.py (API) │
└──────┬──────────────┬──────────────┬─────────────┘
       │              │              │
       ▼              ▼              ▼
┌──────────────────────────────────────────────────┐
│               Core Engine Layer                  │
│  Simulator  ←→  SimulatorConfig                  │
│      │                                           │
│      ├── UndoManager      (state history)        │
│      ├── BoundaryMode     (wrap/fixed/reflect)   │
│      └── CellularAutomaton (abstract base)       │
└──────────────────────────────────────────────────┘
       │
       ▼
┌──────────────────────────────────────────────────┐
│             Automata Implementations             │
│  ConwayGameOfLife  │  HighLife  │  Wireworld     │
│  BriansBrain       │  LangtonsAnt │ Generations  │
│  ImmigrationGame   │  RainbowGame │ HexagonalGoL │
│  LifeLikeAutomaton (custom B/S rules)            │
└──────────────────────────────────────────────────┘
       │
       ▼
┌──────────────────────────────────────────────────┐
│              Support Modules                     │
│  ExportManager    │  PatternManager              │
│  PluginManager    │  ConfigManager (AppConfig)   │
│  ThemeManager     │  StatisticsCollector          │
│  EnhancedStatistics │ PatternAnalyzer            │
│  RLEParser/Encoder  │ CellAgeTracker             │
│  HeatmapGenerator   │ SymmetryAnalyzer           │
│  RuleDiscovery      │ GPUSimulator               │
└──────────────────────────────────────────────────┘
```

---

## Module Breakdown

### Entry Points

| Module | Purpose |
|--------|---------|
| `src/main.py` | Launches the Tkinter GUI via `AutomatonApp` |
| `src/cli.py` | Headless CLI with argparse. Creates a `Simulator`, runs N steps, exports results. |
| `src/api/app.py` | FastAPI server with REST endpoints and WebSocket streams |

### Core (`src/core/`)

| Module | Key Classes | Responsibility |
|--------|-------------|----------------|
| `simulator.py` | `Simulator` | Orchestrates automaton stepping, undo/redo, metrics, callbacks |
| `config.py` | `SimulatorConfig` | Dataclass holding grid size, speed, mode, rule sets, feature flags |
| `undo_manager.py` | `UndoManager` | Bounded stack of grid snapshots (default 100) |
| `boundary.py` | `BoundaryMode`, `convolve_with_boundary()`, `roll_with_boundary()` | Edge-handling strategies |
| `utils.py` | Utility functions | Shared helpers |

The `Simulator` owns a `CellularAutomaton` instance and an `UndoManager`. On each `step()` call it:

1. Pushes the current grid to the undo stack.
2. Calls `automaton.step()`.
3. Records metrics (generation, population, density).
4. Fires the on-step callback.

### Automata (`src/automata/`)

All automata inherit from `CellularAutomaton` (defined in `base.py`) which provides:

- `step()` — advance one generation
- `get_grid() -> np.ndarray` — return the current grid
- `set_cell(x, y, value)` — set a cell value
- `reset()` — clear the grid
- `boundary: str` — edge mode (`"wrap"` | `"fixed"` | `"reflect"`), default `"wrap"`

All convolution-based automata use `core.boundary.convolve_with_boundary()` (wrapping scipy) instead of calling `scipy.signal.convolve2d` directly. This routes every neighbour count through the correct boundary handler.

`LifeLikeAutomaton` additionally caches the Moore-neighbourhood kernel as a class-level constant (`_KERNEL`) to avoid re-allocation on every step.

`LangtonsAnt` exposes `get_population_grid()` which returns the raw cell grid without the ant-marker overlay (used for population statistics).

`LifeLikeAutomaton` accepts arbitrary B/S rules via `parse_bs()`, making it the backbone of custom-rule support and the Rule Explorer.

### GUI (`src/gui/`)

| Module | Purpose |
|--------|---------|
| `app.py` | `AutomatonApp` — main window, menus, toolbar, event loop. Owns `AutoSaveManager` (started on init, stopped on close). |
| `rendering.py` | Canvas drawing: cells, grid lines, overlays. Uses a PIL/Pillow fast-path (`_draw_pil_fast`) when Pillow is installed and grid lines are hidden — a single `PhotoImage` replace instead of per-cell `create_rectangle` calls. |
| `ui.py` | UI builder: creates widgets, binds callbacks |
| `config.py` | GUI constants: `MODE_FACTORIES`, `MODE_PATTERNS`, colors |
| `state.py` | `SimulationState` — mutable runtime state (grid, speed, history deques). `export_metrics_csv()` dynamically discovers all metric keys; `_calculate_complexity()` is vectorised with `numpy.lib.stride_tricks.sliding_window_view`; `seen_hashes` is capped at 2 000 entries with LRU-style eviction. |
| `tools.py` | `ToolManager` — pencil/eraser/stamp/selection, brush settings. Drag gestures push exactly one undo checkpoint regardless of stroke length. |
| `new_features.py` | `GenerationTimeline`, `PopulationGraph`, `BreakpointManager`, `RuleExplorer`, `CommandPalette`, `ThemeEditorDialog`, `PatternShapeSearch` |
| `enhanced_features.py` | Additional feature widgets |
| `enhanced_rendering.py` | Enhanced rendering utilities |
| `icon_factory.py` | Procedural icon generation |
| `layouts.py` | Layout management helpers |
| `modern_ui.py` | Modern UI components (styled buttons, frames) |
| `ui_polish.py` | Visual refinements |

The GUI uses Tkinter's `grid` geometry manager throughout. The main layout is:

```
root
└── content_frame (grid)
    ├── toolbar (row 0, sticky EW)
    ├── sidebar (row 1, column 0)
    ├── canvas  (row 1, column 1)
    ├── timeline (row 2, columnspan 2)
    ├── graph   (row 3, columnspan 2)
    └── statusbar (row 4, columnspan 2)
```

### API (`src/api/`)

| Module | Purpose |
|--------|---------|
| `app.py` | FastAPI application with session CRUD, stepping, state retrieval, pattern loading, WebSocket streaming |
| `collab.py` | `CollaborativeSession` — multi-client shared grid with asyncio lock |

Sessions are stored in a module-level dict keyed by UUID. The collaborative endpoint creates sessions on demand.

### Advanced Analytics (`src/advanced/`)

| Module | Key Classes | Purpose |
|--------|-------------|---------|
| `statistics.py` | `StatisticsCollector`, `StatisticsExporter` | Per-generation metrics collection via `.collect(step, grid)`, CSV/plot export |
| `enhanced_statistics.py` | `EnhancedStatistics` | Entropy, complexity, fractal dimension, connected components, cluster stats, symmetry, centre of mass, radial distribution (static-method API) |
| `pattern_analysis.py` | `PatternAnalyzer` | Bounding box, period detection, displacement detection |
| `pattern_manager.py` | `PatternManager`, `PatternEntry` | Favourites/history backed by JSON, tag-based search, similarity matching. Add patterns with `add_favorite(PatternEntry.from_grid(...))`. |
| `rle_format.py` | `RLEParser`, `RLEEncoder` | Run-Length Encoded I/O. `RLEParser.parse(rle_string)` returns `(grid: np.ndarray, metadata: dict)`. |
| `rule_discovery.py` | `RuleDiscovery` | Vectorised `observe_transition(before, after)` using `np.roll` stacking — no Python nested loop over cells. Infers B/S rules from transitions. |
| `cell_tracker.py` | `CellAgeTracker(width, height)`, `CellHistoryTracker` | Track cell age, birth/death history |
| `visualization.py` | `HeatmapGenerator`, `SymmetryAnalyzer` | Activity/age heatmaps, symmetry detection and scoring |

### Performance (`src/performance/`)

| Module | Key Classes | Purpose |
|--------|-------------|---------|
| `gpu.py` | `GPUSimulator`, `xp` backend | CuPy-accelerated Life-like simulation with NumPy fallback |
| `benchmarking.py` | Benchmarking utilities | Performance measurement and reporting |

### Plugin System (`src/plugin_system.py`)

`PluginManager` scans a directory for `.py` files, imports them, finds `AutomatonPlugin` subclasses, and registers them. Plugins are discovered at GUI startup from the `plugins/` directory.

### Export & Persistence (`src/export_manager.py`, `src/autosave_manager.py`)

`ExportManager` handles all output formats (PNG, GIF, MP4, WebM, JSON). It accumulates frames for animation export and supports 4 colour themes (light, dark, blue, warm).

Key method signatures:

```python
em.export_png(grid, filepath, cell_size=8)      # grid is first arg
em.export_gif(filepath, cell_size=8, duration=100)
em.export_json(filepath, grid, metadata=None)
em.export_video(filepath, cell_size=8, fps=10, codec="mp4")
```

`AutoSaveManager` runs a background thread that calls a user-supplied callback and writes the return value as JSON to a configurable directory. It is started with `am.start()` and stopped cleanly with `am.stop()`.

```python
# Default: saves to ./autosave/ every 300 seconds, keeps 5 backups
am = AutoSaveManager(save_dir="autosave", interval=300, max_backups=5)
am.set_save_callback(lambda: {"generation": sim.generation, "grid": sim.get_grid().tolist()})
am.start()
```

### Configuration

| Module | Class | Storage |
|--------|-------|---------|
| `src/core/config.py` | `SimulatorConfig` | In-memory dataclass |
| `src/config_manager.py` | `AppConfig` | Persisted to `settings.json` |

---

## Data Flow

### GUI Simulation Loop

```
User clicks Start
    → AutomatonApp sets running = True
    → Tkinter after() callback fires every (101 - speed) ms
        → Simulator.step()
            → UndoManager.push_state(grid)
            → CellularAutomaton.step()  ← uses convolve_with_boundary()
            → Metrics recorded
            → on_step callback fires
        → Canvas re-rendered (PIL fast-path if Pillow available + grid lines off)
        → Timeline updated
        → PopulationGraph updated
        → Breakpoints checked
        → AutoSaveManager fires on its own background thread (every 60s)
```

### CLI Pipeline

```
argparse → build SimulatorConfig → Simulator.initialize()
    → loop N steps, collecting frames
    → ExportManager.export_*(frames, output_path)
```

### API Request Flow

```
HTTP POST /session → create Simulator → store in sessions dict → return UUID
HTTP POST /session/{id}/step → lookup Simulator → step() → return generation
WS /session/{id}/stream → lookup Simulator → loop: step + send JSON state
WS /collab/{id} → lookup or create CollaborativeSession → broadcast mutations
```
