Fitness
Fitness is how you tell evolution what “success” looks like - it is the only guidance the evolution engine receives. The fitness block defines three things: gates (hard constraints), metrics (how to measure performance), and weights (how much each metric matters). Getting this right is the most impactful decision in any experiment. See the Fitness Design guide for practical advice.
fitness OperatorAssessment { gate alive gate route_progress = agent.reached_end ? 1.0 : agent.position / world.length
-- How many waypoints were properly served metric waypoint_service { per record waypoint_visit: stopped aggregate: avg }
-- Route completion metric completion = agent.position / world.length
-- Speed compliance (lower is better) metric overspeed = agent.speed > 0 ? 0.0 : 0.0
terminate when agent.reached_end
maximize completion: 100.0 maximize waypoint_service: 60.0 penalize engine.complexity: 0.001}fitness DetectionAssessment { gate alive
-- Detection accuracy: per-record scoring metric accuracy { per record classification: correct aggregate: avg }
-- Detection rate (true positives / total threats) metric detection_rate = agent.connections_seen > 0 ? agent.true_positives / max(1, agent.true_positives + agent.threats_missed) : 0
-- False positive rate (lower is better) metric false_positive_rate = agent.connections_seen > 0 ? agent.false_positives / max(1, agent.connections_seen) : 0
terminate when agent.position >= world.length
maximize accuracy: 100.0 maximize detection_rate: 80.0 penalize false_positive_rate: 60.0 penalize engine.complexity: 0.001}fitness SurvivalAssessment { gate alive
-- Survival duration metric survival = agent.ticks_alive
-- Health maintenance metric health_avg = agent.health
-- Resource gathering metric foraging = agent.food_eaten + agent.water_drunk
-- Idleness penalty (lower is better) metric idle_rate = agent.ticks_alive > 0 ? agent.idle_ticks / agent.ticks_alive : 1.0
maximize survival: 1.0 reward health_avg: 5.0 reward foraging: 2.0 penalize idle_rate: 3.0 penalize engine.complexity: 0.001}Gates are hard prerequisites checked before any metric computation. If a gate fails, the consequences depend on the gate type.
Boolean Gates
Section titled “Boolean Gates”-- Used in all three demosgate aliveA boolean gate checks a single agent state. If the agent is dead (alive = false), total fitness is zero - all metrics and penalties are discarded. This is a hard zero, not a multiplier.
Expression Gates
Section titled “Expression Gates”-- Human Factors: route progress as a multipliergate route_progress = agent.reached_end ? 1.0 : agent.position / world.lengthAn expression gate defines a multiplier applied to all maximize and reward metrics. The expression is evaluated at scenario end. Penalties bypass gates and always apply in full.
Multiple Gates
Section titled “Multiple Gates”When multiple gates are defined, their values multiply together: gate_product = gate1 * gate2 * .... Boolean gates contribute 0.0 (fail) or 1.0 (pass). Expression gates contribute their computed value.
The Human Factors demo uses both a boolean gate (alive) and an expression gate (route_progress). The Network Security and Survival demos use only the boolean gate.
Metrics
Section titled “Metrics”Metrics define how performance is measured. There are several patterns for defining metrics.
Simple Expression
Section titled “Simple Expression”Evaluated once at scenario end from agent/world state.
-- Human Factorsmetric completion = agent.position / world.length
-- Network Securitymetric detection_rate = agent.connections_seen > 0 ? agent.true_positives / max(1, agent.true_positives + agent.threats_missed) : 0
-- Survivalmetric survival = agent.ticks_alivemetric health_avg = agent.healthmetric foraging = agent.food_eaten + agent.water_drunkPer-Record Aggregation
Section titled “Per-Record Aggregation”Iterates over event records emitted by entity interaction handlers during the scenario.
-- Human Factors: average whether waypoints were stopped atmetric waypoint_service { per record waypoint_visit: stopped aggregate: avg}
-- Network Security: average classification correctnessmetric accuracy { per record classification: correct aggregate: avg}Record fields are accessible by name inside the expression. The aggregate function is applied after iterating all records of the given type. If no records of the type were emitted, the metric evaluates to 0.0.
Per-Tick Aggregation
Section titled “Per-Tick Aggregation”Compiles to a running accumulator updated at step 7 of each tick. The aggregate function is applied at scenario end.
metric jerk { per tick: abs(agent.accel - agent.prev_accel) / dt aggregate: avg transform: clamp((value - 0.5) / 1.5, 0, 1)}Aggregate Functions
Section titled “Aggregate Functions”| Function | Description |
|---|---|
avg | Average over accumulated values |
sum | Sum of accumulated values |
min | Minimum of accumulated values |
max | Maximum of accumulated values |
Transform
Section titled “Transform”An optional post-processing step applied to the aggregated value before weighting. The keyword value refers to the aggregation result.
metric jerk { per tick: abs(agent.accel - agent.prev_accel) / dt aggregate: avg transform: clamp((value - 0.5) / 1.5, 0, 1)}Weight Verbs
Section titled “Weight Verbs”Weight verbs tell evolution how each metric contributes to fitness.
| Verb | Meaning | Gated? |
|---|---|---|
maximize | Higher values produce higher fitness | Yes (multiplied by gate product) |
reward | Accumulative count contributes positively | Yes (multiplied by gate product) |
penalize | Value is subtracted from fitness | No (always applies in full) |
maximize completion: 100.0maximize waypoint_service: 60.0penalize engine.complexity: 0.001maximize accuracy: 100.0maximize detection_rate: 80.0penalize false_positive_rate: 60.0penalize engine.complexity: 0.001maximize survival: 1.0reward health_avg: 5.0reward foraging: 2.0penalize idle_rate: 3.0penalize engine.complexity: 0.001The number after the metric name is the weight. The fitness computation is:
total = sum(maximize_metrics * weights) * gate_product + sum(reward_metrics * weights) * gate_product - sum(penalize_metrics * weights)Bare Metric Resolution
Section titled “Bare Metric Resolution”When a weight verb references a name without a preceding metric definition, the name resolves to agent.<name>. This means:
-- Survival uses this patternreward health_avg: 5.0is equivalent to:
metric health_avg = agent.health_avgreward health_avg: 5.0This shorthand is only valid for simple agent state lookups. Complex metrics must use explicit metric definitions.
Engine-Provided Metrics
Section titled “Engine-Provided Metrics”The engine provides built-in metrics that are always available regardless of domain. These use the engine.* namespace and do not need metric definitions.
| Metric | Description |
|---|---|
engine.complexity | Genome connection count |
engine.nodes | Genome node count |
All three demos penalize complexity to encourage simple brains:
penalize engine.complexity: 0.001Terminate Conditions
Section titled “Terminate Conditions”The fitness block can define conditions that end a scenario early. There are two mechanisms:
Death termination: Setting agent.alive = false (via entity handlers, action logic, or dynamics death conditions) kills the agent. The engine skips dead agents at the top of each tick. The gate alive multiplier zeroes the reward portion of the fitness score.
Success termination: The terminate when directive ends the scenario with the agent still alive. This is for conditions where continued evaluation is pointless - the agent has reached its goal.
-- Human Factorsterminate when agent.reached_end
-- Network Securityterminate when agent.position >= world.lengthTerminate conditions are checked after step 7 of each tick. If any terminate when expression returns a truthy (non-zero) value, the scenario ends immediately. The agent is considered alive for gate purposes, and all metrics evaluate against the final state.
The Survival demo does not use terminate when - scenarios run for the full tick budget, and the agent can die through the dynamics death block if health or energy reaches zero.
Validation Rules
Section titled “Validation Rules”- Metric names referenced by weight verbs must either be defined with
metricor resolve to agent state per recordreferences must match record types emitted by entity interaction handlersper tickexpressions are compiled to running accumulatorsgateexpressions are evaluated at scenario endterminate whenexpressions are checked every tick
Fitness Block Keyword Reference
Section titled “Fitness Block Keyword Reference”All keywords and constructs available inside a fitness block.
Gate Syntax
Section titled “Gate Syntax”| Form | Syntax | Description |
|---|---|---|
| Boolean gate | gate alive | Checks agent.alive. If false, total fitness is zero |
| Expression gate | gate name = expr | Evaluated at scenario end. Value multiplies all maximize and reward metrics |
Multiple gates multiply: gate_product = gate1 * gate2 * .... Boolean gates contribute 0.0 or 1.0.
Metric Types
Section titled “Metric Types”| Type | Syntax | Evaluation | Description |
|---|---|---|---|
| Simple | metric name = expr | Once at scenario end | Evaluates an expression against final agent/world state |
| Per-record | metric name { per record type: field ... aggregate: func } | After scenario | Iterates over event records emitted during the scenario |
| Per-tick | metric name { per tick: expr ... aggregate: func } | Running accumulator | Expression sampled every tick, aggregated at scenario end |
Simple Metric
Section titled “Simple Metric”metric completion = agent.position / world.lengthmetric survival = agent.ticks_alivemetric foraging = agent.food_eaten + agent.water_drunkEvaluated once at scenario end. Has access to the full expression language including ternary:
metric idle_rate = agent.ticks_alive > 0 ? agent.idle_ticks / agent.ticks_alive : 1.0Per-Record Metric
Section titled “Per-Record Metric”metric accuracy { per record decision: correct aggregate: avg}| Field | Required | Description |
|---|---|---|
per record <type>: <field> | Yes | Record type name and the field to extract |
aggregate | Yes | Aggregation function (avg, sum, min, max) |
transform | No | Post-processing expression applied to the aggregated value |
If no records of the given type were emitted, the metric evaluates to 0.0.
Per-Tick Metric
Section titled “Per-Tick Metric”metric jerk { per tick: abs(agent.accel - agent.prev_accel) / dt aggregate: avg transform: clamp((value - 0.5) / 1.5, 0, 1)}| Field | Required | Description |
|---|---|---|
per tick: <expr> | Yes | Expression sampled every tick |
aggregate | Yes | Aggregation function applied at scenario end |
transform | No | Post-processing; value refers to the aggregation result |
Aggregate Functions
Section titled “Aggregate Functions”| Function | Description |
|---|---|
avg | Mean of accumulated values |
sum | Sum of accumulated values |
min | Minimum of accumulated values |
max | Maximum of accumulated values |
Weight Verb Syntax
Section titled “Weight Verb Syntax”<verb> <metric_name>: <weight>| Verb | Effect | Gated? | Description |
|---|---|---|---|
maximize | + metric * weight * gate_product | Yes | Higher metric values produce higher fitness |
reward | + metric * weight * gate_product | Yes | Accumulative positive contribution to fitness |
penalize | - metric * weight | No | Value subtracted from fitness (bypasses gates) |
The fitness formula:
total = sum(maximize_metrics * weights) * gate_product + sum(reward_metrics * weights) * gate_product - sum(penalize_metrics * weights)Terminate Syntax
Section titled “Terminate Syntax”terminate when <expr>Checked after step 7 of each tick. If the expression returns a truthy (non-zero) value, the scenario ends immediately. The agent is considered alive for gate purposes.
Engine-Provided Metrics
Section titled “Engine-Provided Metrics”Always available without explicit metric definitions.
| Metric | Description |
|---|---|
engine.complexity | Genome connection count |
engine.nodes | Genome node count |
penalize engine.complexity: 0.001Bare Metric Resolution
Section titled “Bare Metric Resolution”When a weight verb references a name without a preceding metric definition, it resolves to agent.<name>:
reward health_avg: 5.0-- equivalent to: metric health_avg = agent.health_avg