Evolution Configuration
The evolve block’s top-level fields control the main evolution loop. All fields have sensible defaults - you only need to specify values you want to override.
This is the complete reference for all the knobs you can turn on the evolution engine. Most experiments work fine with defaults; this page is for when you need to understand what a parameter actually does, tune speciation for a particular search space, or squeeze more performance out of a long run.
Core Parameters
Section titled “Core Parameters”.quale Field | Config Field | Default | Description |
|---|---|---|---|
population | PopulationSize | 200 | Number of genomes maintained each generation |
generations | MaxGenerations | 5000 | Maximum generations before stopping |
scenarios | ScenariosPerEval | 5 | Independent random scenarios per genome evaluation. Each scenario uses a different random layout. Results are averaged for robustness. |
ticks | MaxTicks | 300 | Simulation steps per scenario. Longer runs give agents more time to demonstrate behavior but slow evaluation. |
seed | Seed | 0 (random) | Random seed for reproducibility. 0 means the runtime generates a random seed using rand.Uint64() (Go’s automatically-seeded global RNG). |
agents | AgentsPerScenario | 1 | Brain instances per scenario. Set to 2 for multi-agent social experiments. |
Internal Parameters
Section titled “Internal Parameters”These parameters are not exposed in the .quale syntax but exist in config.EvolutionConfig:
| Config Field | Default | Description |
|---|---|---|
TournamentSize | 5 | Number of candidates in tournament selection |
ElitismRate | 0.05 | Declared in config but not currently used by reproduce(). Actual elitism is hardcoded: the top genome of each species with 5+ members is preserved unchanged. |
CrossoverRate | 0.7 | Probability of crossover vs. mutation-only reproduction |
DiversityInterval | 100 | Generations between diversity injection events |
DiversityRate | 0.05 | Fraction of population replaced with fresh genomes during diversity injection |
Speciation Configuration
Section titled “Speciation Configuration”NEAT-style speciation groups genomes with similar topologies into species, protecting structural innovation from being outcompeted before it has time to optimize.
speciation { threshold: 3.0 // compatibility distance cutoff target_species: 15 // desired number of species stagnation: 15 generations // generations without improvement before removal}Fields
Section titled “Fields”.quale Field | Config Field | Default | Description |
|---|---|---|---|
threshold | CompatibilityThreshold | 3.0 | Compatibility distance below which genomes are placed in the same species. Adjusted dynamically toward target_species. |
target_species | TargetSpecies | 15 | The runtime adjusts the threshold by +/-0.1 per generation, clamped to [0.5, 10.0], to maintain approximately this many species. |
stagnation | StagnationLimit | 15 | Generations without fitness improvement before a species is removed. The top 2 species by BestFitness are always exempt from stagnation removal regardless of how long they have stagnated. |
Compatibility Distance
Section titled “Compatibility Distance”The compatibility distance between two genomes is computed using three components:
| Component | Coefficient (default) | Description |
|---|---|---|
| Excess genes | 1.0 | Connection genes beyond the range of the other genome |
| Disjoint genes | 1.0 | Connection genes within range but not shared |
| Weight difference | 0.4 | Average absolute weight difference of matching genes |
distance = (excess_coeff * excess_count + disjoint_coeff * disjoint_count) / max(genome_size, 1) + weight_coeff * avg_weight_diffReproduction
Section titled “Reproduction”Within each species:
- Offspring count is proportional to the species’ share of total adjusted fitness
- Adjusted fitness = raw fitness / species size (this prevents large species from dominating)
- Every species gets at least one offspring slot
- Species with 5+ members preserve the top genome as an elite (unchanged)
- Remaining slots are filled by tournament selection + crossover (70% chance) or mutation-only (30% chance)
Convergence Detection
Section titled “Convergence Detection”convergence { plateau: 200 generations // window to check threshold: 0.5 // minimum improvement required}Fields
Section titled “Fields”.quale Field | Config Field | Default | Description |
|---|---|---|---|
plateau | PlateauGenerations | 200 | Number of generations to look back over |
threshold | ConvergenceThreshold | 0.5 | Minimum best-fitness improvement over the plateau window |
Detection Logic
Section titled “Detection Logic”After each generation, the engine compares the best fitness at the beginning and end of the plateau window:
converged = (best_fitness_now - best_fitness_N_generations_ago) < thresholdWhen converged, the engine prints a message and exits the evolution loop. A final checkpoint is always saved regardless of convergence.
Fitness Computation
Section titled “Fitness Computation”Fitness is computed entirely by the domain layer, not the engine. The engine provides the framework (evaluate genomes, track results, use fitness for selection), but the domain decides what fitness means.
EvalResult
Section titled “EvalResult”Each Domain.Evaluate() call returns an EvalResult:
type EvalResult struct { Fitness float64 Metrics map[string]float64}Fitnessis the scalar score used for selection, reproduction, and convergence detectionMetricsis a domain-specific map of named behavioral measurements (e.g.,"survival": 0.95,"food_eaten": 3.2,"idle": 0.1)
The domain computes fitness by applying the fitness objectives from the .quale file:
fitness = sum(verb_sign * metric_value * weight) for each fitness objectiveWhere verb_sign is +1 for maximize and reward, and -1 for penalize.
GenerationStats
Section titled “GenerationStats”After evaluating all genomes, the engine aggregates results into GenerationStats:
type GenerationStats struct { Generation int BestFitness float64 AvgFitness float64 WorstFitness float64 SpeciesCount int BestNodes int BestConns int BestMetrics map[string]float64 // metrics from the best genome AvgMetrics map[string]float64 // population-averaged metrics}The BestMetrics and AvgMetrics maps carry domain-specific keys. The engine does not interpret these - it passes them to Domain.PrintStats() for display and records them in the history for analysis.
Built-in Metric: complexity
Section titled “Built-in Metric: complexity”The complexity metric (genome enabled connection count) is always available regardless of domain. It is passed to every Domain.Evaluate() call as the connectionCount parameter.
Parallel Evaluation
Section titled “Parallel Evaluation”Genome evaluation is the system’s primary bottleneck. The engine parallelizes it:
- One goroutine per genome, bounded by a semaphore equal to
runtime.NumCPU() - Deterministic RNG: Per-genome seeds are generated sequentially from the engine’s RNG before the parallel section begins. This ensures identical results regardless of goroutine scheduling order.
- No shared mutable state: Each goroutine builds its own
Brainfrom the genome, creates its own world, and runs independently. TheDomain.Evaluate()method receives its own*rand.Randand*core.Brain.
This means a 16-core machine evaluates up to 16 genomes simultaneously. For a population of 200 with 5 scenarios of 300 ticks each, this typically reduces generation time by 10-15x compared to sequential evaluation.